#target aftereffects
/*  JIZURA \u2014 lyric motion panel for Adobe After Effects  (v0.9.0)
    Put this file in:  After Effects <version>/Support Files/Scripts/ScriptUI Panels/
    Restart AE, then open  Window > JIZURA_AE.jsx
    (Or run it once via File > Scripts > Run Script File... as a floating window.)
    License: see LICENSE in the source repository.
*/
(function (thisObj) {
var JZ_DATA = {"version": 2, "durGrid": [0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 2.9, 3, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 4], "styles": {"noir": {"name": "Noir Chroma", "desc": "Black and white with cyan and amber color offsets", "schemes": [{"bg": "#060607", "fg": "#F5EEEA", "sub": "#BDB6B2", "accent": "#F5A50C", "accent2": "#16F4D4", "ink": "#F5EEEA", "dim": "#2A2A2E", "ghostA": "#F5A50C", "ghostB": "#16F4D4"}, {"bg": "#F2EDE8", "fg": "#0B0B0C", "sub": "#4A4644", "accent": "#E0600C", "accent2": "#0FAE98", "ink": "#0B0B0C", "dim": "#D9D2CC", "ghostA": "#F5A50C", "ghostB": "#16C4B4", "swap": true}], "fonts": {"display": ["gothic_black", "dela", "zenkaku"], "serif": ["mincho_light", "mincho"], "body": ["gothic_med"], "mono": ["mono"]}, "texture": {"grain": 0.9, "paper": 0, "scan": 0}, "ghost": 1, "bias": {"layout": {"vcols": 2, "condensed": 2, "marquee": 1.6, "tile": 1.4, "center": 1.2}, "enter": {"assemble": 2.2, "slice": 1.8, "stretch": 1.4}, "exit": {"explode": 1.8, "fall": 1.2, "drift": 1.4}}, "decor": {"rings": 0.8, "hud": 0.4, "slash": 0.6}, "hud": false}, "crimson": {"name": "Crimson Signal", "desc": "Deep red, monochrome type and damaged data", "schemes": [{"bg": "#C8103F", "fg": "#FFFFFF", "sub": "#FFD9E2", "accent": "#140509", "accent2": "#39F2C8", "ink": "#140509", "dim": "#B00D37", "ghostA": "#FFFFFF", "ghostB": "#39F2C8"}, {"bg": "#FF6F98", "fg": "#FFFFFF", "sub": "#FFE3EB", "accent": "#1A0710", "accent2": "#39F2C8", "ink": "#1A0710", "dim": "#F25C87", "ghostA": "#FFFFFF", "ghostB": "#1A0710"}, {"bg": "#150509", "fg": "#FF3D6E", "sub": "#FF9DB6", "accent": "#FFFFFF", "accent2": "#39F2C8", "ink": "#FF3D6E", "dim": "#2A0B14", "ghostA": "#FF3D6E", "ghostB": "#39F2C8"}], "fonts": {"display": ["gothic_black", "zenkaku"], "serif": ["mincho"], "body": ["gothic_med", "sansui"], "mono": ["mono"]}, "texture": {"grain": 0.6, "paper": 0, "scan": 0.4}, "ghost": 0.8, "bias": {"layout": {"huge": 2, "marquee": 1.6, "scatter": 1.5, "stack": 1.3, "type": 1.3}, "enter": {"scramble": 1.6, "slice": 1.6, "type": 1.3}, "exit": {"glitch": 2, "slice": 1.6}}, "decor": {"hud": 1, "arrows": 0.8, "rings": 0.8}, "hud": true, "glitchBoost": 1.4}, "caution": {"name": "Caution", "desc": "Yellow, red and blue with instrument graphics", "schemes": [{"bg": "#F4D21F", "fg": "#141414", "sub": "#3A3510", "accent": "#E0231C", "accent2": "#1F3FD8", "ink": "#141414", "dim": "#E6C413", "ghostA": "#E0231C", "ghostB": "#1F3FD8"}, {"bg": "#E0231C", "fg": "#F4D21F", "sub": "#FFE9A0", "accent": "#141414", "accent2": "#FFFFFF", "ink": "#141414", "dim": "#C81E17", "ghostA": "#141414", "ghostB": "#F4D21F"}, {"bg": "#18181A", "fg": "#F4D21F", "sub": "#DDD6B0", "accent": "#E0231C", "accent2": "#FFFFFF", "ink": "#F4D21F", "dim": "#26262A", "ghostA": "#E0231C", "ghostB": "#1F3FD8"}], "fonts": {"display": ["mincho_black", "gothic_black"], "serif": ["mincho_black", "mincho_bold"], "body": ["gothic_bold"], "mono": ["mono"]}, "texture": {"grain": 0.5, "paper": 0.25, "scan": 0}, "ghost": 0.55, "bias": {"layout": {"ring": 2.2, "mixed": 2, "circle": 1.4, "gloss": 1.2}, "enter": {"pop": 1.6, "spin": 1.5, "wipe": 1.2}, "exit": {"scatter": 1.5, "shrink": 1.2}}, "decor": {"hud": 1, "rings": 1, "arrows": 1, "counter": 0.8, "barcode": 0.8}, "hud": true}, "magenta": {"name": "Pop Magenta", "desc": "Hot pink, round bold type and callout lines", "schemes": [{"bg": "#FF0A8C", "fg": "#FFFFFF", "sub": "#FFD2EA", "accent": "#FFFFFF", "accent2": "#2B2BD9", "ink": "#FFFFFF", "dim": "#F0077F", "ghostA": "#FF8CC8", "ghostB": "#2B2BD9"}, {"bg": "#FFFFFF", "fg": "#FF0A8C", "sub": "#FF6DB6", "accent": "#2B2BD9", "accent2": "#FF0A8C", "ink": "#FF0A8C", "dim": "#FFE4F2", "ghostA": "#2B2BD9", "ghostB": "#FF8CC8"}, {"bg": "#2B2BD9", "fg": "#FFFFFF", "sub": "#C9C9FF", "accent": "#FF0A8C", "accent2": "#FFFFFF", "ink": "#FFFFFF", "dim": "#2424C4", "ghostA": "#FF0A8C", "ghostB": "#FFFFFF"}], "fonts": {"display": ["round", "pop", "gothic_black"], "serif": ["mincho_bold"], "body": ["round", "gothic_bold"], "mono": ["mono"]}, "texture": {"grain": 0.3, "paper": 0, "scan": 0}, "ghost": 0.35, "bias": {"layout": {"wave": 2.2, "gloss": 1.6, "huge": 1.6, "pill": 1.4, "scatter": 1.2}, "enter": {"pop": 2, "drop": 1.6, "spin": 1.3, "blur": 1.2}, "exit": {"scatter": 1.6, "shrink": 1.4, "blur": 1.2}}, "decor": {"leaders": 1, "counter": 1, "sparks": 0.8, "shapes": 0.6}, "hud": false}, "paper": {"name": "Paper and Ink", "desc": "Paper texture, indigo, magenta and serif echoes", "schemes": [{"bg": "#ECE9E3", "fg": "#1B2350", "sub": "#4D5270", "accent": "#C2185B", "accent2": "#111111", "ink": "#111111", "dim": "#DAD6CE", "ghostA": "#C2185B", "ghostB": "#1B2350", "paper": true}, {"bg": "#151515", "fg": "#F0EDE7", "sub": "#B8B4AC", "accent": "#C2185B", "accent2": "#1B2350", "ink": "#F0EDE7", "dim": "#232323", "ghostA": "#C2185B", "ghostB": "#3A4690", "paper": true}, {"bg": "#C2185B", "fg": "#FFFFFF", "sub": "#F6C6D8", "accent": "#1B2350", "accent2": "#111111", "ink": "#1B2350", "dim": "#B5154F", "ghostA": "#1B2350", "ghostB": "#FFFFFF", "paper": true}, {"bg": "#1B2350", "fg": "#F0EDE7", "sub": "#AEB2CC", "accent": "#C2185B", "accent2": "#FFFFFF", "ink": "#F0EDE7", "dim": "#1F2858", "ghostA": "#C2185B", "ghostB": "#FFFFFF", "paper": true}], "fonts": {"display": ["mincho_black", "tokumin"], "serif": ["mincho_black", "mincho_bold"], "body": ["mincho"], "mono": ["mono"]}, "texture": {"grain": 0.7, "paper": 1, "scan": 0}, "ghost": 0.5, "bias": {"layout": {"stack": 2.2, "mixed": 1.8, "huge": 1.6, "vcols": 1.4, "circle": 1.2}, "enter": {"wipe": 1.6, "stretch": 1.4, "blur": 1.2, "slice": 1.2}, "exit": {"drift": 1.6, "wipe": 1.4}}, "decor": {"bars": 1, "blobs": 0.8, "shapes": 0.6, "waveform": 0.4}, "hud": false}, "hud": {"name": "Dark HUD", "desc": "Charcoal, fine frames, orange accents and eclipses", "schemes": [{"bg": "#131315", "fg": "#EFEDEA", "sub": "#8E8B88", "accent": "#F25A2B", "accent2": "#FFFFFF", "ink": "#EFEDEA", "dim": "#1E1E21", "ghostA": "#F25A2B", "ghostB": "#7FD7FF"}, {"bg": "#0B0B0C", "fg": "#FFFFFF", "sub": "#9A9796", "accent": "#F25A2B", "accent2": "#FFFFFF", "ink": "#F25A2B", "dim": "#18181A", "ghostA": "#F25A2B", "ghostB": "#FFFFFF"}], "fonts": {"display": ["gothic_black", "zenkaku"], "serif": ["mincho_bold", "mincho_light"], "body": ["gothic_med"], "mono": ["mono"]}, "texture": {"grain": 1, "paper": 0, "scan": 0.2}, "ghost": 0.6, "bias": {"layout": {"circle": 2, "ring": 1.6, "vcols": 1.4, "center": 1.2, "gloss": 1}, "enter": {"blur": 1.6, "type": 1.4, "assemble": 1.3}, "exit": {"blur": 1.4, "drift": 1.4, "explode": 1.2}}, "decor": {"hud": 1, "rings": 1, "arrows": 1, "grid": 0.8, "slash": 0.6}, "hud": true, "glow": 1.4}, "mint": {"name": "Mint Terminal", "desc": "Black, teal and lime with scan effects", "schemes": [{"bg": "#0A0E0D", "fg": "#E6FFF5", "sub": "#7FB9A8", "accent": "#9CFF3A", "accent2": "#2E8C74", "ink": "#E6FFF5", "dim": "#142420", "ghostA": "#FF3B6B", "ghostB": "#2EE6C8"}, {"bg": "#3FAE93", "fg": "#0A0E0D", "sub": "#123A31", "accent": "#FFFFFF", "accent2": "#9CFF3A", "ink": "#0A0E0D", "dim": "#39A087", "ghostA": "#FFFFFF", "ghostB": "#0A0E0D"}, {"bg": "#F2F2EE", "fg": "#0A0E0D", "sub": "#40504B", "accent": "#2E8C74", "accent2": "#9CFF3A", "ink": "#0A0E0D", "dim": "#E2E4DE", "ghostA": "#2E8C74", "ghostB": "#9CFF3A"}], "fonts": {"display": ["gothic_black", "dela"], "serif": ["mincho"], "body": ["gothic_med", "sansui"], "mono": ["mono", "dot"]}, "texture": {"grain": 0.8, "paper": 0, "scan": 0.6}, "ghost": 0.9, "bias": {"layout": {"labels": 2.4, "tile": 1.6, "marquee": 1.4, "type": 1.4, "diag": 1.2}, "enter": {"scramble": 1.8, "type": 1.6, "flicker": 1.4}, "exit": {"glitch": 1.6, "slice": 1.4}}, "decor": {"hud": 1, "grid": 0.8, "barcode": 0.8, "sparks": 0.5}, "hud": true}, "specimen": {"name": "Type Specimen", "desc": "Ink colored background with serif annotations", "schemes": [{"bg": "#1B1A1C", "fg": "#F2F0EC", "sub": "#A19E99", "accent": "#F2F0EC", "accent2": "#C8B98C", "ink": "#F2F0EC", "dim": "#2A292C", "ghostA": "#6E6A66", "ghostB": "#C8B98C"}, {"bg": "#F2F0EC", "fg": "#1B1A1C", "sub": "#5E5B57", "accent": "#1B1A1C", "accent2": "#8A7A4E", "ink": "#1B1A1C", "dim": "#E3E0DA", "ghostA": "#B9B4AD", "ghostB": "#8A7A4E"}], "fonts": {"display": ["mincho_bold", "mincho_black"], "serif": ["mincho", "mincho_light"], "body": ["mincho"], "mono": ["mono"]}, "texture": {"grain": 0.6, "paper": 0.3, "scan": 0}, "ghost": 0.25, "bias": {"layout": {"gloss": 2.6, "vcols": 1.8, "mixed": 1.4, "center": 1.2, "tile": 1}, "enter": {"type": 1.8, "blur": 1.6, "wipe": 1.2}, "exit": {"blur": 1.6, "drift": 1.2, "wipe": 1.2}}, "decor": {"leaders": 1, "slash": 0.8, "rings": 0.4}, "hud": false}, "transit": {"name": "Transit", "desc": "Olive and yellow with signage and halftone", "schemes": [{"bg": "#5B582B", "fg": "#FFFFFF", "sub": "#E6E2BC", "accent": "#E8C21A", "accent2": "#1A1A1A", "ink": "#E8C21A", "dim": "#67633A", "ghostA": "#E8C21A", "ghostB": "#1A1A1A"}, {"bg": "#1A1A1A", "fg": "#FFFFFF", "sub": "#B8B5A0", "accent": "#E8C21A", "accent2": "#FFFFFF", "ink": "#E8C21A", "dim": "#242424", "ghostA": "#E8C21A", "ghostB": "#7C7A55"}, {"bg": "#9C9A94", "fg": "#FFFFFF", "sub": "#F0EEE6", "accent": "#E8C21A", "accent2": "#1A1A1A", "ink": "#1A1A1A", "dim": "#A6A49E", "ghostA": "#E8C21A", "ghostB": "#1A1A1A"}], "fonts": {"display": ["zenkaku", "gothic_black"], "serif": ["mincho_bold"], "body": ["gothic_bold"], "mono": ["mono"]}, "texture": {"grain": 0.8, "paper": 0.2, "scan": 0}, "ghost": 0.5, "bias": {"layout": {"mixed": 2, "scatter": 1.6, "diag": 1.4, "huge": 1.2}, "enter": {"spin": 1.6, "drop": 1.4, "pop": 1.2, "stretch": 1.2}, "exit": {"scatter": 1.4, "stretch": 1.4}}, "decor": {"arrows": 1.4, "shapes": 1, "counter": 0.8, "rings": 0.6}, "hud": false}, "blueprint": {"name": "Blueprint", "desc": "Blue, white and black graphic collage", "schemes": [{"bg": "#1B1BE8", "fg": "#FFFFFF", "sub": "#C7C7FF", "accent": "#000000", "accent2": "#FFFFFF", "ink": "#000000", "dim": "#2323F0", "ghostA": "#000000", "ghostB": "#8C8CFF"}, {"bg": "#000000", "fg": "#FFFFFF", "sub": "#9A9AFF", "accent": "#1B1BE8", "accent2": "#FFFFFF", "ink": "#1B1BE8", "dim": "#0A0A30", "ghostA": "#1B1BE8", "ghostB": "#FFFFFF"}, {"bg": "#FFFFFF", "fg": "#1B1BE8", "sub": "#5A5AF0", "accent": "#000000", "accent2": "#1B1BE8", "ink": "#1B1BE8", "dim": "#EDEDFF", "ghostA": "#000000", "ghostB": "#8C8CFF"}], "fonts": {"display": ["dela", "gothic_black"], "serif": ["mincho_bold"], "body": ["gothic_bold"], "mono": ["mono", "dot"]}, "texture": {"grain": 0.4, "paper": 0, "scan": 0}, "ghost": 0.6, "bias": {"layout": {"diag": 2.2, "labels": 1.4, "huge": 1.4, "condensed": 1.2}, "enter": {"wipe": 1.6, "slice": 1.6, "stretch": 1.3}, "exit": {"wipe": 1.6, "slice": 1.4, "glitch": 1.2}}, "decor": {"shapes": 1.4, "stripes": 1, "slash": 1, "grid": 0.6}, "hud": false}, "rouge": {"name": "Rouge Gradient", "desc": "Light gray and red gradients with capsules", "schemes": [{"bg": "#E4E2E0", "fg": "#141414", "sub": "#6B6866", "accent": "#D40F1C", "accent2": "#141414", "ink": "#141414", "dim": "#D8D6D4", "ghostA": "#D40F1C", "ghostB": "#6B6866", "grad": ["#E3141F", "#4A0005"]}, {"bg": "#140405", "fg": "#FFFFFF", "sub": "#C98A8E", "accent": "#E3141F", "accent2": "#FFFFFF", "ink": "#E3141F", "dim": "#220A0C", "ghostA": "#E3141F", "ghostB": "#FFFFFF", "grad": ["#FF4A52", "#6A0008"]}], "fonts": {"display": ["gothic_black", "dela"], "serif": ["mincho_black"], "body": ["gothic_med"], "mono": ["mono"]}, "texture": {"grain": 0.4, "paper": 0, "scan": 0}, "ghost": 0.4, "bias": {"layout": {"huge": 2.2, "pill": 2, "mixed": 1.4, "labels": 1.2, "center": 1.2}, "enter": {"zoom": 1.6, "wipe": 1.4, "pop": 1.2}, "exit": {"shrink": 1.6, "wipe": 1.2}}, "decor": {"hud": 0.8, "leaders": 0.8, "stripes": 0.6}, "hud": true, "useGrad": true}, "mono": {"name": "Mono RGB", "desc": "Gray space, white serif and bold RGB separation", "schemes": [{"bg": "#3B3D41", "fg": "#FFFFFF", "sub": "#B9BBBF", "accent": "#FFFFFF", "accent2": "#FFE34D", "ink": "#1A1B1D", "dim": "#45474C", "ghostA": "#FF2A2A", "ghostB": "#2AA8FF"}, {"bg": "#141517", "fg": "#FFFFFF", "sub": "#9EA0A4", "accent": "#FFE34D", "accent2": "#FFFFFF", "ink": "#FFFFFF", "dim": "#1E1F22", "ghostA": "#FF2A2A", "ghostB": "#2AFF7A"}], "fonts": {"display": ["mincho_black", "mincho_bold"], "serif": ["mincho_bold"], "body": ["mincho"], "mono": ["mono"]}, "texture": {"grain": 0.9, "paper": 0, "scan": 0.3}, "ghost": 1.3, "bias": {"layout": {"circle": 1.8, "ring": 1.6, "pill": 1.4, "tile": 1.4, "vcols": 1.3}, "enter": {"assemble": 1.4, "blur": 1.4, "zoom": 1.3}, "exit": {"explode": 1.4, "glitch": 1.4, "blur": 1.2}}, "decor": {"rings": 1.4, "hud": 0.6, "dots": 1}, "hud": false}, "hrRuin": {"name": "Ruins", "desc": "Faded green-grey, rust red and echoing serif type", "moods": ["horror"], "set": "horror", "schemes": [{"bg": "#161B18", "fg": "#D3DACF", "sub": "#7D887E", "accent": "#B0473A", "accent2": "#6E8B6B", "ink": "#D3DACF", "dim": "#1F2621", "ghostA": "#5E7A62", "ghostB": "#8A3A30"}, {"bg": "#8C958A", "fg": "#141814", "sub": "#2E362F", "accent": "#6A1A14", "accent2": "#E4E8DF", "ink": "#141814", "dim": "#848D82", "ghostA": "#3F4D41", "ghostB": "#6A1A14"}, {"bg": "#0C0F0D", "fg": "#A9B8A6", "sub": "#5D6B5E", "accent": "#C24A3A", "accent2": "#A9B8A6", "ink": "#A9B8A6", "dim": "#161B17", "ghostA": "#2F4A36", "ghostB": "#5E1E18"}], "fonts": {"display": ["mincho_black", "shippori"], "serif": ["mincho", "mincho_light"], "body": ["mincho_light"], "mono": ["mono"]}, "texture": {"grain": 1, "paper": 0.15, "scan": 0.1}, "ghost": 0.55, "bias": {"layout": {"hrFlashlight": 1.6, "hrDoorGap": 1.3, "hrWallScrawl": 1.2, "hrCctv": 1.2, "hrOuija": 0.9, "hrMissing": 1, "hrWrongOne": 1.5, "hrRisingDark": 1.3, "hrRedacted": 1, "hrStaticTv": 1, "hrSpiritPhoto": 1, "hrWrongShadow": 1.3, "center": 1.1, "vcols": 1.2, "pop": 0.3}, "enter": {"hrBlinkCreep": 1.3, "hrJumpScare": 0.9, "hrUneasy": 1.6, "hrVhold": 1.1, "hrMirrorSnap": 1, "hrManifest": 1.6, "hrClawReveal": 1, "blur": 1.2, "flicker": 1.2, "pop": 0.2, "bounceBig": 0.1, "bubbles": 0.1}, "exit": {"hrPulledDown": 1.4, "hrLookBack": 1.3, "hrTurnAway": 1.2, "hrShiver": 1.2, "hrSwallow": 1.3, "hrFlickerDie": 1.3, "hrDrain": 1.2, "blur": 1.1, "popOut": 0.1, "balloonOff": 0.1}, "treat": {"hrEroded": 1.6, "hrInkBleed": 1.2, "hrDoubleExp": 1.2, "hrRedact": 0.6, "rainbow": 0.1, "sticker": 0.1}, "bg": {"hrFailingLamp": 1.6, "hrCorridor": 1.4, "hrMold": 1.4, "hrDeadTrees": 1.2, "polka": 0.1, "candy": 0.1}, "cam": {"hrNervous": 1.4, "hrDutchSnap": 1.1, "handheld": 1.2, "bounce": 0.1, "jelly": 0.1}, "fx": {"hrPassingShadow": 1.2, "hrSubliminal": 1, "hrSignalLoss": 0.8, "dustScratches": 1.2, "starGlint": 0.1}}, "decor": {"hrDustBeam": 1.6, "hrCracks": 1.2, "hrSigil": 0.8, "hrScratches": 1, "hrWatchEye": 0.6, "hrDrips": 0.8, "confetti": 0.05, "heartsStars": 0.05}, "hud": false, "glow": 0.4}, "hrNightRec": {"name": "Midnight Recording", "desc": "Pitch-black frames, CCTV white and red, static", "moods": ["horror"], "set": "horror", "schemes": [{"bg": "#050505", "fg": "#EDEDED", "sub": "#8A8A8A", "accent": "#E3261E", "accent2": "#FFFFFF", "ink": "#EDEDED", "dim": "#121212", "ghostA": "#6E6E6E", "ghostB": "#E3261E"}, {"bg": "#0B0E0B", "fg": "#D8F0D8", "sub": "#6F866F", "accent": "#FF3A2A", "accent2": "#D8F0D8", "ink": "#D8F0D8", "dim": "#141A14", "ghostA": "#3E6B3E", "ghostB": "#FF3A2A"}, {"bg": "#DADADA", "fg": "#0A0A0A", "sub": "#4A4A4A", "accent": "#C8140E", "accent2": "#0A0A0A", "ink": "#0A0A0A", "dim": "#CCCCCC", "ghostA": "#8A8A8A", "ghostB": "#C8140E"}], "fonts": {"display": ["gothic_bold", "gothic_black"], "serif": ["mincho"], "body": ["gothic_med"], "mono": ["mono", "dot"]}, "texture": {"grain": 1.2, "paper": 0, "scan": 0.7}, "ghost": 0.8, "bias": {"layout": {"hrFlashlight": 1.6, "hrDoorGap": 1.3, "hrWallScrawl": 1.2, "hrCctv": 2.2, "hrOuija": 0.9, "hrMissing": 1, "hrWrongOne": 1.5, "hrRisingDark": 1.3, "hrRedacted": 1.3, "hrStaticTv": 1.6, "hrSpiritPhoto": 1, "hrWrongShadow": 1.3, "center": 1.1, "vcols": 1.2, "pop": 0.3, "type": 1.2}, "enter": {"hrBlinkCreep": 1.3, "hrJumpScare": 0.9, "hrUneasy": 1.6, "hrVhold": 1.8, "hrMirrorSnap": 1, "hrManifest": 1.6, "hrClawReveal": 1, "blur": 1.2, "flicker": 1.2, "pop": 0.2, "bounceBig": 0.1, "bubbles": 0.1, "glitchIn": 1}, "exit": {"hrPulledDown": 1.4, "hrLookBack": 1.3, "hrTurnAway": 1.2, "hrShiver": 1.2, "hrSwallow": 1.3, "hrFlickerDie": 1.8, "hrDrain": 1.2, "blur": 1.1, "popOut": 0.1, "balloonOff": 0.1, "glitch": 1}, "treat": {"hrDoubleExp": 1.4, "hrRedact": 1.2, "glitchSplit": 1, "rainbow": 0.1}, "bg": {"hrFailingLamp": 1.4, "hrCorridor": 1.6, "vhsBand": 1.4, "noiseField": 1, "polka": 0.1}, "cam": {"hrNervous": 1.8, "hrDutchSnap": 1, "handheld": 1.2, "jelly": 0.1}, "fx": {"hrSignalLoss": 1.6, "hrSubliminal": 1.3, "hrPassingShadow": 1, "tvStatic": 1.4, "trackingNoise": 1.2, "vhsRoll": 1.2, "starGlint": 0.1}}, "decor": {"hrStaticPatch": 1.6, "hrWatchEye": 1, "hrCracks": 0.8, "hrScratches": 0.8, "timecodeBar": 1, "hud": 1, "confetti": 0.05}, "hud": true, "glow": 0.5, "glitchBoost": 1.2}, "hrCurse": {"name": "Cursed Letter", "desc": "Yellowed paper, faded ink and dark red handwriting", "moods": ["horror"], "set": "horror", "schemes": [{"bg": "#D8CBA4", "fg": "#2A2017", "sub": "#6B5B45", "accent": "#7E1410", "accent2": "#3F3326", "ink": "#2A2017", "dim": "#CDBF97", "ghostA": "#9A2A20", "ghostB": "#8A7A5E", "paper": true}, {"bg": "#1C140E", "fg": "#E3D5B0", "sub": "#9A8866", "accent": "#B8261C", "accent2": "#E3D5B0", "ink": "#E3D5B0", "dim": "#271D15", "ghostA": "#6E1510", "ghostB": "#5A4A34", "paper": true}, {"bg": "#C4B28A", "fg": "#3A0D0A", "sub": "#6E4A3A", "accent": "#1E1812", "accent2": "#7E1410", "ink": "#3A0D0A", "dim": "#B9A67D", "ghostA": "#7E1410", "ghostB": "#6E5E44", "paper": true}], "fonts": {"display": ["klee", "brush", "mincho_black"], "serif": ["klee", "mincho"], "body": ["klee", "mincho"], "mono": ["mono"]}, "texture": {"grain": 0.7, "paper": 1, "scan": 0}, "ghost": 0.4, "bias": {"layout": {"hrFlashlight": 1.6, "hrDoorGap": 1.3, "hrWallScrawl": 2, "hrCctv": 0.5, "hrOuija": 1.3, "hrMissing": 1.6, "hrWrongOne": 1.5, "hrRisingDark": 1.3, "hrRedacted": 1, "hrStaticTv": 1, "hrSpiritPhoto": 1.5, "hrWrongShadow": 1.3, "center": 1.1, "vcols": 1.2, "pop": 0.3, "letterPaper": 1.2}, "enter": {"hrBlinkCreep": 1.3, "hrJumpScare": 0.9, "hrUneasy": 1.6, "hrVhold": 1.1, "hrMirrorSnap": 1, "hrManifest": 1.6, "hrClawReveal": 1.4, "blur": 1.2, "flicker": 1.2, "pop": 0.2, "bounceBig": 0.1, "bubbles": 0.1, "inkBleed": 1.4}, "exit": {"hrPulledDown": 1.4, "hrLookBack": 1.3, "hrTurnAway": 1.2, "hrShiver": 1.2, "hrSwallow": 1.3, "hrFlickerDie": 1.3, "hrDrain": 1.8, "blur": 1.1, "popOut": 0.1, "balloonOff": 0.1, "burn": 1}, "treat": {"hrInkBleed": 1.8, "hrEroded": 1.3, "hrRedact": 0.8, "sticker": 0.1, "chrome": 0.1}, "bg": {"hrMold": 1.8, "hrFailingLamp": 1, "tornPaper": 1, "polka": 0.1}, "cam": {"hrNervous": 1, "hrDutchSnap": 1.2, "driftDiag": 1}, "fx": {"hrSubliminal": 1.2, "hrPassingShadow": 1, "filmBurn": 1, "dustScratches": 1.4, "starGlint": 0.1}}, "decor": {"hrDrips": 1.6, "hrScratches": 1.2, "hrSigil": 1.2, "hrCracks": 0.8, "crossOut": 1, "scribbleCircle": 0.8, "confetti": 0.05}, "hud": false, "glow": 0.3}, "sakura": {"name": "Sakura", "desc": "Soft cherry pink and plum with rounded and serif type", "moods": ["emotional", "calm"], "schemes": [{"bg": "#F8E4EB", "fg": "#4A1434", "sub": "#8A4A69", "accent": "#D93A74", "accent2": "#6F8F4E", "ink": "#4A1434", "dim": "#F0D3DE", "ghostA": "#EC6A9A", "ghostB": "#9A8AE6", "paper": true}, {"bg": "#26091B", "fg": "#FCE8F0", "sub": "#D69DB6", "accent": "#FF86B0", "accent2": "#B9E0A2", "ink": "#FCE8F0", "dim": "#351127", "ghostA": "#FF5C95", "ghostB": "#8A78FF"}, {"bg": "#E77FA3", "fg": "#3A0B26", "sub": "#65173F", "accent": "#3A0B26", "accent2": "#FFF3F7", "ink": "#3A0B26", "dim": "#DE7499", "ghostA": "#B4205A", "ghostB": "#6A4CC8"}, {"bg": "#FFFAF8", "fg": "#A01E4A", "sub": "#B45F7E", "accent": "#E0457F", "accent2": "#4A1434", "ink": "#A01E4A", "dim": "#F8ECEE", "ghostA": "#F07AA4", "ghostB": "#A898EA", "paper": true}], "fonts": {"display": ["kiwi", "shippori"], "serif": ["shippori", "mincho_light"], "body": ["kiwi"], "mono": ["mono"]}, "texture": {"grain": 0.35, "paper": 0.35, "scan": 0}, "ghost": 0.45, "bias": {"layout": {"vcols": 1.8, "columnsBig": 1.6, "hanging": 1.5, "center": 1.3, "arcTop": 1.3, "mirror": 1.2, "circle": 1.2, "kanjiFocus": 1.2, "huge": 0.8, "tile": 0.4, "condensed": 0.5}, "enter": {"blurStagger": 1.8, "fadeStagger": 1.6, "blur": 1.4, "trackIn": 1.3, "inkBleed": 1.2, "unroll": 1.1, "slice": 0.5, "scramble": 0.3}, "exit": {"dissolve": 1.8, "riseOut": 1.5, "drift": 1.5, "blurOutStagger": 1.4, "echoOut": 1.1, "glitch": 0.3, "explode": 0.5}, "treat": {"softShadow": 1.5, "emphasisDots": 1.2, "gradientV": 1.2, "echoOutline": 0.4, "hatch": 0.4}, "bg": {"bokehBg": 1.4, "particlesBg": 1.3, "gradientSweep": 1.2, "spotlight": 1, "tvBars": 0.2, "bigStripes": 0.4, "noiseField": 0.4}, "cam": {"driftDiag": 1.4, "pullOut": 1.2, "tiltUp": 1.2, "shakeHard": 0.3}, "fx": {"lightSweep": 1.4, "whiteFrame": 1.2, "filmBurn": 1, "pixelDrift": 0.3, "posterize": 0.4}}, "decor": {"petals": 1.6, "bokeh": 1, "twinkle": 0.8, "lightLeak": 0.7, "waveLine": 0.6, "glitchRects": 0.1, "barcode": 0.1}, "hud": false, "glow": 0.9, "extra": true, "wa": true}, "ocean": {"name": "Deep Sea", "desc": "Navy, glowing cyan, bubbles and light sans type", "moods": ["calm", "emotional"], "schemes": [{"bg": "#031A2E", "fg": "#E4FAFF", "sub": "#7FB2C8", "accent": "#1FD2E6", "accent2": "#4C7DFF", "ink": "#E4FAFF", "dim": "#0A2842", "ghostA": "#1FD2E6", "ghostB": "#3B5BFF"}, {"bg": "#0B5566", "fg": "#FFFFFF", "sub": "#A9E3EA", "accent": "#04182A", "accent2": "#7FF3FF", "ink": "#E4FAFF", "dim": "#0E6173", "ghostA": "#27E3F2", "ghostB": "#031A2E"}, {"bg": "#DCF1F2", "fg": "#06243A", "sub": "#3E6A7C", "accent": "#0A7F98", "accent2": "#3B5BFF", "ink": "#06243A", "dim": "#CBE6E8", "ghostA": "#16B4CC", "ghostB": "#5A6BFF"}, {"bg": "#01060D", "fg": "#7FF6FF", "sub": "#3E9AAE", "accent": "#FFFFFF", "accent2": "#2E6BFF", "ink": "#7FF6FF", "dim": "#081521", "ghostA": "#2E6BFF", "ghostB": "#00FFC2"}], "fonts": {"display": ["gothic_light", "zenkaku"], "serif": ["mincho_light", "mincho"], "body": ["sansui"], "mono": ["mono"]}, "texture": {"grain": 0.6, "paper": 0, "scan": 0}, "ghost": 0.75, "bias": {"layout": {"bubbles": 1.8, "depthStack": 1.5, "tunnel": 1.3, "rain": 1.2, "wave": 1.3, "center": 1.3, "vcols": 1.2, "perspective": 1.2, "orbit": 1.1, "labels": 0.5, "stickerBomb": 0.3}, "enter": {"riseMask": 1.6, "blur": 1.5, "waveIn": 1.4, "blurStagger": 1.3, "zoom": 1.1, "pop": 0.5, "stamp": 0.4}, "exit": {"riseOut": 1.7, "zoomFar": 1.5, "dissolve": 1.3, "melt": 1.2, "blur": 1.2, "popOut": 0.5}, "treat": {"glow": 1.6, "softShadow": 1.2, "gradientV": 1.1, "marker": 0.4, "boxed": 0.5}, "bg": {"ripples": 1.6, "particlesBg": 1.4, "gradientSweep": 1.2, "concentric": 1, "bokehBg": 1, "checker": 0.3, "polka": 0.4}, "cam": {"driftDiag": 1.4, "dollyIn": 1.2, "roll": 1.1, "tiltUp": 1.1, "bounce": 0.4}, "fx": {"waveWarp": 1.6, "lightSweep": 1.1, "rgbSplit": 0.8, "strobe": 0.4}}, "decor": {"risingParticles": 1.6, "bokeh": 1.1, "waveLine": 1.2, "orbitDots": 0.7, "beatRing": 0.6, "rings": 0.6, "sparks": 0.2}, "hud": false, "glow": 1.6, "extra": true}, "sunset": {"name": "Sunset Gradient", "desc": "Orange to violet, bold serif and backlight", "moods": ["emotional", "pop"], "schemes": [{"bg": "#2A0F44", "fg": "#FFF0DC", "sub": "#E6A98F", "accent": "#FF7A30", "accent2": "#FF4A86", "ink": "#FFB347", "dim": "#361456", "ghostA": "#FF7A30", "ghostB": "#B84BFF", "grad": ["#FFC15E", "#FF4A7A"]}, {"bg": "#FF8A3D", "fg": "#2A0F44", "sub": "#5A1F55", "accent": "#2A0F44", "accent2": "#FFF0DC", "ink": "#2A0F44", "dim": "#F57F32", "ghostA": "#E0306A", "ghostB": "#7A2BC0", "grad": ["#6A1B9A", "#2A0F44"]}, {"bg": "#B8325F", "fg": "#FFF3E4", "sub": "#FFC7A8", "accent": "#FFC15E", "accent2": "#2A0F44", "ink": "#FFF3E4", "dim": "#A92C56", "ghostA": "#FFC15E", "ghostB": "#2A0F44", "grad": ["#FFF0B8", "#FFC46E"]}, {"bg": "#FFE4CF", "fg": "#3A1250", "sub": "#8A4A6A", "accent": "#F2562E", "accent2": "#8A3FD1", "ink": "#3A1250", "dim": "#F7D6BE", "ghostA": "#FF6A3D", "ghostB": "#9A4BE0", "grad": ["#E24A22", "#7A2BBF"]}], "fonts": {"display": ["tokumin", "mincho_black", "dela"], "serif": ["mincho_black", "shippori"], "body": ["gothic_med"], "mono": ["mono"]}, "texture": {"grain": 0.55, "paper": 0, "scan": 0}, "ghost": 0.6, "bias": {"layout": {"huge": 1.8, "pill": 1.5, "arcTop": 1.5, "center": 1.3, "mixed": 1.3, "curtain": 1.3, "lowerThird": 1.2, "depthStack": 1.1, "tile": 0.5, "dotMatrix": 0.3}, "enter": {"zoom": 1.5, "riseMask": 1.4, "blur": 1.3, "echoIn": 1.2, "wipe": 1.2, "trackIn": 1.1, "scramble": 0.4, "glitchIn": 0.3}, "exit": {"zoomThrough": 1.4, "blur": 1.4, "drift": 1.3, "riseOut": 1.2, "shrink": 1.1, "glitchDissolve": 0.3}, "treat": {"gradientV": 1.8, "longShadow": 1.2, "softShadow": 1.1, "glow": 1, "halftone": 0.5, "hatch": 0.4}, "bg": {"gradientSweep": 1.8, "sunburst": 1.2, "spotlight": 1.2, "letterbox": 1.1, "halftoneFade": 0.8, "tvBars": 0.3}, "cam": {"dollyIn": 1.3, "pullOut": 1.2, "tiltUp": 1.2, "crashZoom": 0.8}, "fx": {"lightSweep": 1.6, "filmBurn": 1.4, "whiteFrame": 1, "hueShift": 0.8, "pixelDrift": 0.4}}, "decor": {"lightLeak": 1.4, "lineBurst": 1, "bokeh": 0.8, "twinkle": 0.7, "halftonePatch": 0.6, "waveLine": 0.5}, "hud": false, "glow": 1.3, "useGrad": true, "extra": true}, "forest": {"name": "Forest Notebook", "desc": "Moss, natural paper and pencil lettering", "moods": ["calm", "editorial", "emotional"], "schemes": [{"bg": "#1D291B", "fg": "#EFE9D6", "sub": "#A8B08A", "accent": "#B7C95A", "accent2": "#C4833F", "ink": "#EFE9D6", "dim": "#263423", "ghostA": "#8FB04A", "ghostB": "#C4833F"}, {"bg": "#EDE6D1", "fg": "#22301F", "sub": "#5D6647", "accent": "#4F7A35", "accent2": "#8A5A32", "ink": "#22301F", "dim": "#E1D9C2", "ghostA": "#6F9A45", "ghostB": "#B0703A", "paper": true}, {"bg": "#4A3526", "fg": "#F2EAD3", "sub": "#CDB894", "accent": "#B7C95A", "accent2": "#EFE9D6", "ink": "#B7C95A", "dim": "#55402F", "ghostA": "#8FB04A", "ghostB": "#D9A441", "paper": true}, {"bg": "#A9BC96", "fg": "#1A2618", "sub": "#34482F", "accent": "#1A2618", "accent2": "#F2EAD3", "ink": "#1A2618", "dim": "#9FB38C", "ghostA": "#4F7A35", "ghostB": "#8A5A32"}], "fonts": {"display": ["klee", "shippori"], "serif": ["shippori", "mincho"], "body": ["klee"], "mono": ["mono"]}, "texture": {"grain": 0.7, "paper": 0.5, "scan": 0}, "ghost": 0.45, "bias": {"layout": {"columnsBig": 1.6, "vcols": 1.5, "hanging": 1.4, "quote": 1.3, "dropCap": 1.3, "frameBox": 1.2, "lowerThird": 1.2, "justified": 1.1, "marquee": 0.5, "equalizer": 0.3}, "enter": {"strokeDraw": 1.7, "inkBleed": 1.4, "fadeStagger": 1.4, "riseMask": 1.2, "blur": 1.2, "unroll": 1.1, "scramble": 0.3, "glitchIn": 0.2}, "exit": {"dissolve": 1.6, "undraw": 1.5, "drift": 1.4, "sinkMask": 1.2, "blurOutStagger": 1.1, "glitch": 0.3}, "treat": {"underline": 1.3, "emphasisDots": 1.2, "dotted": 1.2, "softShadow": 1, "echoOutline": 0.3}, "bg": {"dotGrid": 1.3, "particlesBg": 1.2, "spotlight": 1, "splitH": 0.8, "noiseField": 0.6, "retroGrid": 0.2, "tvBars": 0.2}, "cam": {"handheld": 1.5, "driftDiag": 1.2, "tiltUp": 1.2, "whipIn": 0.4}, "fx": {"filmBurn": 1.2, "lightSweep": 1, "strobe": 0.3, "posterize": 0.3}}, "decor": {"scribbleUnder": 1.2, "scribbleCircle": 1, "waveLine": 1, "risingParticles": 0.9, "tapePieces": 0.7, "constellation": 0.6, "plusGrid": 0.5, "guides": 0.4}, "hud": false, "extra": true}, "vapor": {"name": "Vaporwave", "desc": "Pastel pink and blue with VHS bloom", "moods": ["pop", "emotional", "glitch"], "schemes": [{"bg": "#3A2A6E", "fg": "#FFFFFF", "sub": "#D6C8FF", "accent": "#FF8FD8", "accent2": "#7DF9FF", "ink": "#7DF9FF", "dim": "#45347C", "ghostA": "#FF71CE", "ghostB": "#01CDFE", "grad": ["#FF8FD8", "#7DF9FF"]}, {"bg": "#FFC6EC", "fg": "#3A2A6E", "sub": "#74489A", "accent": "#7A3BFF", "accent2": "#0F9FC8", "ink": "#3A2A6E", "dim": "#F7B8E2", "ghostA": "#2EC8F0", "ghostB": "#B04BFF", "grad": ["#7A3BFF", "#0B7FB0"]}, {"bg": "#8FEAF2", "fg": "#35246A", "sub": "#4A3C8A", "accent": "#35246A", "accent2": "#FF4FC0", "ink": "#35246A", "dim": "#82E0EA", "ghostA": "#FF5CC8", "ghostB": "#8A5CFF", "grad": ["#D0249A", "#5A2BD0"]}, {"bg": "#1A1030", "fg": "#FFFB96", "sub": "#B9A6E0", "accent": "#05FFA1", "accent2": "#FF71CE", "ink": "#FF71CE", "dim": "#241840", "ghostA": "#FF71CE", "ghostB": "#01CDFE", "grad": ["#FFFB96", "#FF71CE"]}], "fonts": {"display": ["mincho", "dot", "mincho_black"], "serif": ["mincho_light", "mincho"], "body": ["sansui"], "mono": ["dot", "mono"]}, "texture": {"grain": 0.5, "paper": 0, "scan": 0.55}, "ghost": 0.95, "bias": {"layout": {"mirror": 1.7, "perspective": 1.5, "sideways": 1.3, "arcTop": 1.3, "filmstrip": 1.2, "wave": 1.2, "center": 1.2, "tile": 1.1, "pill": 1.1, "genkou": 0.3, "justified": 0.4}, "enter": {"echoIn": 1.6, "zoomOut": 1.3, "flicker": 1.2, "trackOut": 1.2, "blur": 1.2, "stretch": 1.1, "strokeDraw": 0.4}, "exit": {"echoOut": 1.5, "zoomFar": 1.4, "stretch": 1.2, "melt": 1.2, "blur": 1.1, "burn": 0.4}, "treat": {"gradientV": 1.6, "echoOutline": 1.4, "italic": 1.4, "wide": 1.3, "glow": 1.1, "emphasisDots": 0.3}, "bg": {"retroGrid": 1.7, "checker": 1.3, "gradientSweep": 1.3, "scanBars": 1.1, "sunburst": 0.8, "borderFrame": 0.5}, "cam": {"roll": 1.3, "driftDiag": 1.2, "dutch": 1.1, "pullOut": 1}, "fx": {"vhsRoll": 1.7, "trackingNoise": 1.5, "hueShift": 1.3, "rgbSplit": 1.2, "smear": 1.1, "blackFrame": 0.5}}, "decor": {"checkerStrip": 1.3, "twinkle": 1.1, "triangleSpin": 1, "shapes": 0.9, "lightLeak": 0.7, "halftonePatch": 0.6, "orbitDots": 0.5}, "hud": false, "glow": 1.2, "useGrad": true, "extra": true}, "newsprint": {"name": "Newsprint", "desc": "Gray paper, black and red with halftone registration", "moods": ["editorial", "graphic"], "schemes": [{"bg": "#E6E5E0", "fg": "#111111", "sub": "#4E4E4C", "accent": "#D8141B", "accent2": "#0A82C8", "ink": "#111111", "dim": "#D8D7D1", "ghostA": "#E4007F", "ghostB": "#00A0E9", "paper": true}, {"bg": "#111111", "fg": "#F2F2EE", "sub": "#A5A5A0", "accent": "#F5D300", "accent2": "#E4007F", "ink": "#F2F2EE", "dim": "#1F1F1F", "ghostA": "#E4007F", "ghostB": "#00A0E9", "paper": true}, {"bg": "#D8141B", "fg": "#FFFFFF", "sub": "#FFD6D0", "accent": "#111111", "accent2": "#F5D300", "ink": "#FFFFFF", "dim": "#C71118", "ghostA": "#111111", "ghostB": "#F5D300", "paper": true}], "fonts": {"display": ["mincho_black", "gothic_bold"], "serif": ["mincho_bold", "mincho"], "body": ["mincho"], "mono": ["mono"]}, "texture": {"grain": 0.9, "paper": 0.9, "scan": 0}, "ghost": 0.45, "bias": {"layout": {"justified": 2, "columnsBig": 1.7, "vcols": 1.6, "dropCap": 1.5, "quote": 1.4, "splitScreen": 1.3, "typeSpecimen": 1.2, "genkou": 1.1, "flipBoard": 1.1, "lowerThird": 1, "stack": 1, "bubbles": 0.3, "bounceLine": 0.3, "neon": 0.3}, "enter": {"type": 1.5, "cursorSweep": 1.4, "riseMask": 1.3, "shutter": 1.3, "stamp": 1.2, "wipe": 1.2, "spin": 0.4, "bounceBig": 0.4}, "exit": {"sweepCover": 1.6, "sinkMask": 1.3, "wipe": 1.3, "backspace": 1.2, "blindsClose": 1, "spinOut": 0.3, "popOut": 0.4}, "treat": {"underline": 1.5, "boxed": 1.4, "emphasisDots": 1.4, "halftone": 1.3, "marker": 1.1, "glow": 0.3, "gradientV": 0.3}, "bg": {"halftoneFade": 1.5, "bigChar": 1.3, "borderFrame": 1.2, "splitH": 1.1, "dotGrid": 0.8, "bokehBg": 0.2, "retroGrid": 0.2}, "cam": {"panL": 1.3, "panR": 1.3, "stepZoom": 1.1, "roll": 0.3}, "fx": {"posterize": 1.2, "blackFrame": 1.2, "panelWipe": 1.1, "hueShift": 0.3, "vhsRoll": 0.3}}, "decor": {"halftonePatch": 1.4, "indexNum": 1.2, "rulerEdge": 1, "verticalStrip": 1, "cropMarks": 0.9, "dateStamp": 0.8, "crossOut": 0.6, "highlightMark": 0.6, "bokeh": 0.1, "confetti": 0.1}, "hud": false, "extra": true}, "synth80": {"name": "Synth 80s", "desc": "Black with neon magenta, cyan and scanlines", "moods": ["pop", "glitch", "emotional"], "schemes": [{"bg": "#0B0414", "fg": "#FF4FD8", "sub": "#A98BFF", "accent": "#22E6FF", "accent2": "#FFE45C", "ink": "#22E6FF", "dim": "#1A0B2E", "ghostA": "#22E6FF", "ghostB": "#6A3BFF"}, {"bg": "#0B0414", "fg": "#22E6FF", "sub": "#8FA8FF", "accent": "#E62EBE", "accent2": "#FFE45C", "ink": "#FF4FD8", "dim": "#140A28", "ghostA": "#FF4FD8", "ghostB": "#FFE45C"}, {"bg": "#1C0A3A", "fg": "#FFFFFF", "sub": "#FFB0E8", "accent": "#E62EBE", "accent2": "#22E6FF", "ink": "#FFE45C", "dim": "#26104C", "ghostA": "#FF2E88", "ghostB": "#22E6FF", "grad": ["#FFE45C", "#FF2E88"]}, {"bg": "#FF2E88", "fg": "#0B0414", "sub": "#3A0A30", "accent": "#0B0414", "accent2": "#22E6FF", "ink": "#0B0414", "dim": "#F0287E", "ghostA": "#22E6FF", "ghostB": "#FFE45C"}], "fonts": {"display": ["rampart", "dela"], "serif": ["mincho_bold"], "body": ["gothic_bold"], "mono": ["mono", "dot"]}, "texture": {"grain": 0.4, "paper": 0, "scan": 0.75}, "ghost": 1, "bias": {"layout": {"neon": 2, "perspective": 1.6, "tunnel": 1.4, "equalizer": 1.3, "zoomRepeat": 1.3, "marquee": 1.3, "huge": 1.3, "sideways": 1.1, "crossBands": 1.1, "genkou": 0.3, "hanko": 0.3, "quote": 0.4}, "enter": {"neonOn": 2, "zoomOut": 1.4, "echoIn": 1.3, "whip": 1.2, "stretch": 1.2, "flicker": 1.1, "inkBleed": 0.3, "strokeDraw": 0.5}, "exit": {"zoomThrough": 1.5, "echoOut": 1.3, "stretch": 1.2, "whipOut": 1.1, "glitch": 1.1, "dissolve": 0.4}, "treat": {"glow": 1.8, "echoOutline": 1.3, "extrude": 1.2, "longShadow": 1.1, "italic": 1, "emphasisDots": 0.3, "underline": 0.5}, "bg": {"retroGrid": 2, "sunburst": 1.1, "eqBars": 1.1, "tvBars": 1, "speedLines": 0.8, "dotGrid": 0.4, "halftoneFade": 0.5}, "cam": {"beatPunch": 1.4, "stepZoom": 1.2, "dollyIn": 1.1, "whipIn": 1, "handheld": 0.4}, "fx": {"crtOff": 1.5, "strobe": 1.3, "rgbSplit": 1.3, "lightSweep": 1.2, "zoomPunch": 1.2, "filmBurn": 0.3}}, "decor": {"triangleSpin": 1.2, "beatRing": 1.1, "lineBurst": 1, "twinkle": 0.8, "speedCorner": 0.8, "reticle": 0.6, "petals": 0.1, "tapePieces": 0.1}, "hud": true, "glow": 1.9, "glitchBoost": 1.15, "extra": true}, "kraft": {"name": "Kraft Paper", "desc": "Warm paper, red and indigo stamps and washi tape", "moods": ["pop", "editorial", "graphic"], "schemes": [{"bg": "#C49A6C", "fg": "#1A1410", "sub": "#46301E", "accent": "#B8361B", "accent2": "#F3E9D2", "ink": "#1A1410", "dim": "#B98F62", "ghostA": "#C33A1F", "ghostB": "#2E5E8C", "paper": true}, {"bg": "#F1E6CF", "fg": "#1A1410", "sub": "#6A5540", "accent": "#C33A1F", "accent2": "#2E5E8C", "ink": "#1A1410", "dim": "#E6D8BC", "ghostA": "#C33A1F", "ghostB": "#2E5E8C", "paper": true}, {"bg": "#1E1A17", "fg": "#F1E6CF", "sub": "#B8A68A", "accent": "#D9642E", "accent2": "#C49A6C", "ink": "#C49A6C", "dim": "#2A2521", "ghostA": "#C49A6C", "ghostB": "#C33A1F", "paper": true}, {"bg": "#2E5E8C", "fg": "#F6EEDD", "sub": "#C9D6E2", "accent": "#F1E6CF", "accent2": "#C33A1F", "ink": "#F1E6CF", "dim": "#34669A", "ghostA": "#C49A6C", "ghostB": "#1A1410", "paper": true}], "fonts": {"display": ["dela", "zenkaku"], "serif": ["tokumin", "mincho_bold"], "body": ["klee"], "mono": ["mono"]}, "texture": {"grain": 0.7, "paper": 1, "scan": 0}, "ghost": 0.5, "bias": {"layout": {"tape": 2, "labels": 1.6, "stickerBomb": 1.5, "ticket": 1.4, "hanko": 1.2, "panels": 1.2, "frameBox": 1.1, "keycaps": 1, "mixed": 1, "neon": 0.3, "tunnel": 0.3, "rain": 0.4}, "enter": {"stamp": 1.8, "dropMask": 1.3, "squashDrop": 1.2, "slideL": 1.1, "pop": 1.1, "flipX": 1, "neonOn": 0.3, "glitchIn": 0.3}, "exit": {"popOut": 1.2, "flipOutY": 1.2, "slideOutL": 1.1, "sweepCover": 1.1, "foldOut": 1.1, "glitchDissolve": 0.3, "scrambleOut": 0.4}, "treat": {"boxed": 1.4, "hardShadow": 1.3, "marker": 1.2, "stripes": 1, "outlineFill": 1, "glow": 0.3}, "bg": {"borderFrame": 1.3, "halftoneFade": 1.1, "polka": 1, "checker": 0.9, "splitDiag": 0.8, "retroGrid": 0.2, "noiseField": 0.3}, "cam": {"handheld": 1.3, "stepZoom": 1.1, "bounce": 1, "roll": 0.4}, "fx": {"panelWipe": 1.2, "posterize": 1, "whiteFrame": 0.8, "vhsRoll": 0.3, "crtOff": 0.2}}, "decor": {"tapePieces": 1.8, "seal": 1.1, "scribbleCircle": 1, "dateStamp": 1, "bracketsJP": 0.8, "cropMarks": 0.6, "qrBlock": 0.5, "arrows": 0.6, "glitchRects": 0.1}, "hud": false, "extra": true}, "candy": {"name": "Candy", "desc": "Mint and fruit pastel colors with bouncing letters", "moods": ["pop"], "schemes": [{"bg": "#BDF0E2", "fg": "#3E2A8C", "sub": "#5A4A9A", "accent": "#EE3A88", "accent2": "#FFB020", "ink": "#3E2A8C", "dim": "#AEE8D8", "ghostA": "#FF5FA2", "ghostB": "#8A6BFF"}, {"bg": "#FFDDEB", "fg": "#7A2BB8", "sub": "#A2468E", "accent": "#EE3A88", "accent2": "#2FC2B8", "ink": "#7A2BB8", "dim": "#FDD0E2", "ghostA": "#FF5FA2", "ghostB": "#3FB8FF"}, {"bg": "#FFF0A0", "fg": "#B01E5E", "sub": "#9A4A2A", "accent": "#3F8FFF", "accent2": "#FF5FA2", "ink": "#B01E5E", "dim": "#F8E68C", "ghostA": "#FF5FA2", "ghostB": "#3FB8FF"}, {"bg": "#6A33B0", "fg": "#FFF3FA", "sub": "#E3C8FF", "accent": "#FF8FC8", "accent2": "#7FF0E0", "ink": "#7FF0E0", "dim": "#7439BD", "ghostA": "#FF8FC8", "ghostB": "#7FF0E0"}], "fonts": {"display": ["potta", "pop", "round"], "serif": ["kiwi"], "body": ["round", "kiwi"], "mono": ["mono"]}, "texture": {"grain": 0.15, "paper": 0, "scan": 0}, "ghost": 0.4, "bias": {"layout": {"bubbles": 1.8, "bounceLine": 1.6, "elastic": 1.4, "stickerBomb": 1.4, "bubble": 1.4, "keycaps": 1.2, "zigzag": 1.2, "wave": 1.2, "hanging": 1.1, "slotMachine": 1, "justified": 0.3, "genkou": 0.3, "tunnel": 0.3, "rain": 0.3}, "enter": {"bounceBig": 1.6, "squashDrop": 1.6, "rubber": 1.5, "pop": 1.4, "waveIn": 1.2, "spiralIn": 1.1, "magnet": 1, "glitchIn": 0.2, "scramble": 0.3, "inkBleed": 0.3}, "exit": {"popOut": 1.6, "gravity": 1.3, "collapse": 1.2, "spinOut": 1.1, "scatter": 1.1, "burn": 0.2, "glitchDissolve": 0.2, "melt": 0.4}, "treat": {"outlineFill": 1.5, "doubleOutline": 1.5, "extrude": 1.2, "hardShadow": 1.1, "alternate": 1.1, "strike": 0.2, "hatch": 0.3}, "bg": {"polka": 1.7, "sunburst": 1.2, "checker": 1, "bigStripes": 1, "splitDiag": 0.9, "tvBars": 0.2, "noiseField": 0.2, "scanBars": 0.3}, "cam": {"bounce": 1.4, "beatPunch": 1.2, "stepZoom": 1, "dutch": 0.4, "shakeHard": 0.4}, "fx": {"zoomPunch": 1.2, "whiteFrame": 1.1, "gridRepeat": 1.1, "panelWipe": 1, "pixelDrift": 0.3, "crtOff": 0.2, "trackingNoise": 0.2}}, "decor": {"confetti": 1.6, "heartsStars": 1.6, "dots": 1, "twinkle": 1, "shapes": 1, "sparks": 0.8, "bokeh": 0.5, "glitchRects": 0.1, "barcode": 0.1}, "hud": false, "glow": 0.4, "extra": true}, "acid": {"name": "Acid", "desc": "Black, acid green and magenta with distressed type", "moods": ["glitch", "graphic"], "schemes": [{"bg": "#050505", "fg": "#C6FF00", "sub": "#86A800", "accent": "#FF2BD6", "accent2": "#FFFFFF", "ink": "#C6FF00", "dim": "#111A00", "ghostA": "#FF2BD6", "ghostB": "#3D5BFF"}, {"bg": "#C6FF00", "fg": "#050505", "sub": "#2A3A00", "accent": "#050505", "accent2": "#FF2BD6", "ink": "#050505", "dim": "#B8F000", "ghostA": "#FF2BD6", "ghostB": "#2B3DFF"}, {"bg": "#FF2BD6", "fg": "#050505", "sub": "#3A0030", "accent": "#C6FF00", "accent2": "#050505", "ink": "#050505", "dim": "#F024C8", "ghostA": "#C6FF00", "ghostB": "#2B3DFF"}, {"bg": "#0A0A0A", "fg": "#FFFFFF", "sub": "#9A9A9A", "accent": "#C6FF00", "accent2": "#FF2BD6", "ink": "#FF2BD6", "dim": "#171717", "ghostA": "#C6FF00", "ghostB": "#FF2BD6"}], "fonts": {"display": ["reggae", "dot", "dela"], "serif": ["mincho_black"], "body": ["sansui"], "mono": ["dot", "mono"]}, "texture": {"grain": 1, "paper": 0, "scan": 0.45}, "ghost": 1.25, "bias": {"layout": {"dotMatrix": 1.6, "rain": 1.5, "crossBands": 1.4, "zoomRepeat": 1.4, "tile": 1.4, "splitHalves": 1.3, "huge": 1.3, "slotMachine": 1.1, "equalizer": 1.1, "condensed": 1.1, "hanging": 0.3, "bubble": 0.3, "quote": 0.3, "credits": 0.4}, "enter": {"glitchIn": 1.8, "resolve": 1.5, "flicker": 1.4, "checker": 1.3, "vSlice": 1.3, "scramble": 1.3, "randomOrder": 1.2, "slice": 1.2, "fadeStagger": 0.3, "unroll": 0.3}, "exit": {"glitchDissolve": 1.7, "scrambleOut": 1.4, "glitch": 1.4, "checkerOut": 1.2, "melt": 1.2, "vSliceDrop": 1.1, "dissolve": 0.3, "riseOut": 0.4}, "treat": {"echoOutline": 1.5, "hatch": 1.3, "halftone": 1.2, "hardShadow": 1.2, "strike": 1.1, "softShadow": 0.3, "gradientV": 0.4}, "bg": {"noiseField": 1.5, "tvBars": 1.3, "eqBars": 1.1, "bigStripes": 1, "scanBars": 1, "bokehBg": 0.2, "gradientSweep": 0.4}, "cam": {"shakeHard": 1.4, "whipIn": 1.2, "stepZoom": 1.2, "crashZoom": 1.1, "handheld": 0.5, "tiltUp": 0.5}, "fx": {"pixelDrift": 1.5, "posterize": 1.4, "tileShift": 1.4, "smear": 1.2, "invert": 1.2, "strobe": 1.1, "filmBurn": 0.2, "lightSweep": 0.3}}, "decor": {"glitchRects": 1.6, "qrBlock": 1.2, "reticle": 1, "barcode": 1, "bars": 1, "crosshair": 0.8, "timecodeBar": 0.7, "petals": 0.1, "heartsStars": 0.1, "confetti": 0.1}, "hud": true, "glow": 0.8, "glitchBoost": 1.6, "extra": true}, "sumi": {"name": "Ink and Vermilion", "desc": "Japanese paper, brush lettering and red seals", "moods": ["calm", "emotional", "editorial"], "schemes": [{"bg": "#EFE5CF", "fg": "#16130F", "sub": "#5E574C", "accent": "#B83A22", "accent2": "#16130F", "ink": "#16130F", "dim": "#E3D8BF", "ghostA": "#9A9284", "ghostB": "#CC4A2E", "paper": true}, {"bg": "#121110", "fg": "#EFE8D8", "sub": "#9A9286", "accent": "#D9402A", "accent2": "#EFE8D8", "ink": "#EFE8D8", "dim": "#1E1C1A", "ghostA": "#5C5750", "ghostB": "#D9402A", "paper": true}, {"bg": "#B3301D", "fg": "#FFF6E8", "sub": "#FFD2C0", "accent": "#16130F", "accent2": "#FFF6E8", "ink": "#FFF6E8", "dim": "#A42B1A", "ghostA": "#16130F", "ghostB": "#E8A070", "paper": true}, {"bg": "#BAB4A7", "fg": "#16130F", "sub": "#3E3A33", "accent": "#A82A18", "accent2": "#16130F", "ink": "#16130F", "dim": "#AFA99C", "ghostA": "#6E685E", "ghostB": "#B8321E", "paper": true}], "fonts": {"display": ["brush", "mincho_black"], "serif": ["brush", "shippori"], "body": ["klee"], "mono": ["mono"]}, "texture": {"grain": 0.6, "paper": 0.8, "scan": 0}, "ghost": 0.35, "bias": {"layout": {"hanko": 2, "vcols": 1.8, "kanjiFocus": 1.6, "columnsBig": 1.4, "halfVertical": 1.2, "genkou": 1.1, "center": 1.2, "huge": 1.1, "mirror": 0.8, "stickerBomb": 0.2, "keycaps": 0.2, "bubbles": 0.2, "neon": 0.2, "equalizer": 0.3}, "enter": {"inkBleed": 2, "strokeDraw": 1.6, "unroll": 1.3, "blur": 1.2, "fadeStagger": 1.2, "stamp": 1, "bounceBig": 0.2, "rubber": 0.2, "neonOn": 0.2}, "exit": {"dissolve": 1.5, "drift": 1.4, "undraw": 1.3, "burn": 1.1, "blurOutStagger": 1.2, "popOut": 0.2, "spinOut": 0.2}, "treat": {"emphasisDots": 1.3, "softShadow": 1.1, "tall": 1.1, "glow": 0.2, "extrude": 0.3, "alternate": 0.3}, "bg": {"bigChar": 1.5, "spotlight": 0.9, "splitH": 0.9, "ripples": 0.8, "retroGrid": 0.1, "polka": 0.1, "eqBars": 0.1, "tvBars": 0.1}, "cam": {"handheld": 1.3, "tiltUp": 1.2, "dollyIn": 1.2, "bounce": 0.3, "beatPunch": 0.4}, "fx": {"blackFrame": 1.2, "filmBurn": 1.1, "whiteFrame": 1, "hueShift": 0.2, "posterize": 0.3}}, "decor": {"brushStroke": 1.8, "seal": 1.6, "watermarkKanji": 1.2, "verticalStrip": 1, "blobs": 0.9, "petals": 0.4, "confetti": 0.1, "heartsStars": 0.1, "glitchRects": 0.1}, "hud": false, "glow": 0.4, "extra": true, "wa": true}, "gold": {"name": "Golden Night", "desc": "Deep black, gold foil, ivory serif and glints", "moods": ["emotional", "calm", "editorial"], "schemes": [{"bg": "#0A0907", "fg": "#F3E7C4", "sub": "#B39A62", "accent": "#D4AF37", "accent2": "#F3E7C4", "ink": "#D4AF37", "dim": "#17140E", "ghostA": "#D4AF37", "ghostB": "#7A5230", "grad": ["#FFE9A8", "#B8862B"]}, {"bg": "#C9A24A", "fg": "#0A0907", "sub": "#3A2C12", "accent": "#0A0907", "accent2": "#FFF4D6", "ink": "#0A0907", "dim": "#BD9740", "ghostA": "#8A5A1A", "ghostB": "#3A2C12", "grad": ["#2A1E08", "#0A0907"]}, {"bg": "#0B1024", "fg": "#F3E7C4", "sub": "#A89A78", "accent": "#D4AF37", "accent2": "#F3E7C4", "ink": "#D4AF37", "dim": "#141A33", "ghostA": "#D4AF37", "ghostB": "#3A5AA8", "grad": ["#FFE9A8", "#B8862B"]}, {"bg": "#2B0A14", "fg": "#F6EBD0", "sub": "#C9A987", "accent": "#D4AF37", "accent2": "#F6EBD0", "ink": "#D4AF37", "dim": "#38101F", "ghostA": "#D4AF37", "ghostB": "#9A2A4E", "grad": ["#FFE9A8", "#B8862B"]}], "fonts": {"display": ["shippori", "mincho_light"], "serif": ["shippori", "mincho_light"], "body": ["mincho_light"], "mono": ["mono"]}, "texture": {"grain": 0.6, "paper": 0, "scan": 0}, "ghost": 0.5, "bias": {"layout": {"frameBox": 1.6, "center": 1.4, "kanjiFocus": 1.4, "curtain": 1.3, "credits": 1.2, "circle": 1.2, "quote": 1.2, "orbit": 1.1, "justified": 1, "stickerBomb": 0.2, "bubbles": 0.3, "keycaps": 0.2, "slotMachine": 0.3}, "enter": {"trackIn": 1.6, "blur": 1.4, "blurStagger": 1.3, "outlineFill": 1.2, "iris": 1.1, "riseMask": 1.1, "squashDrop": 0.2, "rubber": 0.2, "glitchIn": 0.3}, "exit": {"trackOutWide": 1.5, "blur": 1.4, "echoOut": 1.2, "zoomFar": 1.1, "dissolve": 1.1, "popOut": 0.3, "gravity": 0.3}, "treat": {"gradientV": 1.6, "glow": 1.5, "doubleOutline": 1, "softShadow": 1, "tall": 1, "halftone": 0.3, "stripes": 0.3}, "bg": {"spotlight": 1.5, "bokehBg": 1.3, "letterbox": 1.2, "particlesBg": 1.1, "concentric": 0.9, "polka": 0.1, "checker": 0.2, "eqBars": 0.2}, "cam": {"dollyIn": 1.4, "pullOut": 1.2, "driftDiag": 1.1, "shakeHard": 0.2, "bounce": 0.3}, "fx": {"lightSweep": 1.7, "filmBurn": 1.1, "whiteFrame": 0.8, "pixelDrift": 0.2, "posterize": 0.2}}, "decor": {"twinkle": 1.4, "bokeh": 1.2, "lightLeak": 1, "constellation": 1, "concentricSquares": 0.8, "cropMarks": 0.5, "glitchRects": 0.1, "qrBlock": 0.1}, "hud": false, "glow": 1.7, "useGrad": true, "extra": true}}, "styleOrder": ["noir", "crimson", "caution", "magenta", "paper", "hud", "mint", "specimen", "transit", "blueprint", "rouge", "mono", "hrRuin", "hrNightRec", "hrCurse", "sakura", "ocean", "sunset", "forest", "vapor", "newsprint", "synth80", "kraft", "candy", "acid", "sumi", "gold"], "baseStyles": ["noir", "crimson", "caution", "magenta", "paper", "hud", "mint", "specimen", "transit", "blueprint", "rouge", "mono"], "orders": {"layout": ["center", "mixed", "vcols", "marquee", "tile", "scatter", "ring", "wave", "huge", "labels", "condensed", "gloss", "type", "diag", "circle", "stack", "pill", "hrFlashlight", "hrDoorGap", "hrWallScrawl", "hrCctv", "hrOuija", "hrMissing", "hrWrongOne", "hrRisingDark", "hrRedacted", "hrStaticTv", "hrSpiritPhoto", "hrWrongShadow", "knSlamStack", "knQuarterTurn", "knSwapCenter", "knZoomDive", "knFlowSnap", "knSeesaw", "knTypeSlam", "knRhythmCuts", "knPathRide", "knGearWords", "knCollide", "knTumble", "knReflow", "knPadGrid", "lowerThird", "corners", "staircase", "zigzag", "arcTop", "spiral", "gridCells", "dropCap", "justified", "frameBox", "bubble", "subtitleBar", "ticker", "splitScreen", "mirror", "sideways", "edgeFrame", "perspective", "hanko", "genkou", "panels", "filmstrip", "quote", "ruler", "searchBar", "chat", "notification", "ticket", "rain", "hanging", "orbit", "tunnel", "wordCloud", "bounceLine", "elastic", "crossBands", "stickerBomb", "neon", "keycaps", "bubbles", "slotMachine", "flipBoard", "credits", "zoomRepeat", "splitHalves", "columnsBig", "circleWords", "dotMatrix", "depthStack", "typeSpecimen", "kanjiFocus", "halfVertical", "curtain", "equalizer", "tape", "magazine", "headlineDeck", "contents", "footnote", "proofread", "numbered", "poster", "swissGrid", "dictionary", "ema", "ransom", "newspaper", "vinyl", "cassette", "bookSpine", "polaroid", "stampSheet", "postcard", "letterPaper", "calendar", "chochin", "routeMap", "stationSign", "noren", "tanzaku", "omikuji", "kakejiku", "shoji", "clapper", "warningLabel", "priceTag", "nameTag", "stickyNotes", "karuta", "cube", "cylinder", "flipCards", "accordion", "flag", "ribbon", "pendulum", "pile", "blocks", "balloons", "magnets", "tiles", "bulbs", "ledScroll", "billboard", "crowdBubbles", "crossword", "wordSearch", "puzzle", "shadowPlay", "kaleido", "dominoes", "burst", "fisheye", "wall", "origami", "zipper", "sliceStack", "glitchGrid", "mosaicTiles", "maskReveal", "contour", "halftoneBig", "stencil", "tyKeySplit", "tyCropGiant", "tyCross", "tyBandHide", "tyRuby", "tyBaseline", "tyScaleSteps", "tyJustify", "tyIndexTable", "tySplitType", "tyErode", "tyVRuler", "tyFullTrack", "tyStatCount", "tyMargin", "tyRotBlock", "tySquare", "tyLineFocus"], "enter": ["cut", "assemble", "slice", "type", "pop", "drop", "stretch", "wipe", "blur", "spin", "flicker", "scramble", "zoom", "riseMask", "dropMask", "slideL", "slideR", "slideWhole", "flipX", "flipY", "domino", "fold", "unroll", "strokeDraw", "outlineFill", "splitJoin", "vSlice", "shutter", "iris", "diagWipe", "blinds", "checker", "randomOrder", "bounceBig", "squashDrop", "rubber", "glitchIn", "echoIn", "whip", "skewIn", "trackIn", "trackOut", "blurStagger", "fadeStagger", "waveIn", "spiralIn", "zoomOut", "resolve", "magnet", "inkBleed", "neonOn", "cursorSweep", "stamp", "springIn", "pendulum", "rollIn", "slingshot", "rockSettle", "bounceBall", "snapRail", "fanOpen", "cylinder", "shuffle", "stopMotion", "ripple", "zipper", "zoomAlt", "tiltUp", "stickerPeel", "crumple", "noteUnfold", "tornJoin", "splitFlap", "overexpose", "glint", "loupe", "filmFeed", "backlight", "lightLeak", "heatHaze", "crtOn", "interlace", "loadingBar", "dither", "odometer", "matrixRain", "hatchFill", "brushReveal", "inkDrop", "quarters", "invertBox", "printRegister", "echoCount", "liquidFill", "windBlown", "strokeOrder", "clockWipe", "shadowFirst", "bubbles", "tokoroten", "hrBlinkCreep", "hrJumpScare", "hrUneasy", "hrVhold", "hrMirrorSnap", "hrManifest", "hrClawReveal", "knWordSlam", "knTypeToSlam", "knReplaceIn", "knHingeDrop", "knLoopIn", "knPushIn", "knInertia", "knWordSpin", "knDiveIn", "knStretchOut", "tyKeyFirst", "tyLineWipe", "tyZoomOne", "tyUnderLift", "tyDotGrow", "tyBracketOpen", "tyRetype", "tyRubyDrop"], "hold": ["still", "jitter", "drift", "breathe", "wave", "glitchtick", "float", "sway", "pulse", "shimmer", "colorRun", "rotateSlow", "trackBreathe", "skewWobble", "beatHop", "hWave", "heartbeat", "orbitSmall", "jelly", "scanBand", "noiseDrift", "tilt", "zoomSlow", "stretchPulse", "glitchJump", "echoTrail", "glowFlicker", "windGust", "dangle", "eqBounce", "flashBox", "glintSweep", "flipSwap", "shadowSway", "magnetJiggle", "typeRattle", "focusRack", "pluckString", "hrTwitch", "hrStare", "hrLagOne", "hrFlickerLight", "knWordPulse", "knCounterRock", "knWordRide", "knTickShift", "knBeatLean", "knGapBreath", "tyKeyPulse", "tyReadCursor", "tyOutlineBlink", "tyTrackStep"], "exit": ["cut", "explode", "fall", "drift", "slice", "wipe", "shrink", "blur", "stretch", "scatter", "glitch", "sinkMask", "riseOut", "slideOutL", "slideOutR", "flipOutX", "flipOutY", "foldOut", "squash", "trackOutWide", "collapse", "zoomThrough", "zoomFar", "spinOut", "twist", "waveOut", "blurOutStagger", "undraw", "outlineOut", "irisClose", "diagWipeOut", "blindsClose", "checkerOut", "splitApart", "vSliceDrop", "melt", "dissolve", "backspace", "scrambleOut", "glitchDissolve", "echoOut", "whipOut", "gravity", "popOut", "burn", "sweepCover", "shatterLite", "peelOff", "crumpleOut", "tearOut", "scorchOut", "overexposeOut", "scanOut", "stripesOut", "halftoneOut", "eraserOut", "vacuumOut", "sandOut", "shredOut", "dominoOut", "hingeOut", "rocketOff", "bounceOff", "balloonOff", "deflateOut", "hazeOut", "glassBreak", "zipOut", "clapShut", "lampOff", "slotOut", "clockOut", "matrixOut", "tornadoOut", "rollUpOut", "snakeOut", "flutterOut", "rollOff", "fanClose", "rgbSplitOut", "shockOut", "floodOut", "slashOut", "mosaicOut", "scribbleOut", "candleOut", "hrPulledDown", "hrLookBack", "hrTurnAway", "hrShiver", "hrSwallow", "hrFlickerDie", "hrDrain", "knWordKick", "knPushOut", "knDiveGlyph", "knLaunch", "knWordBlink", "knCloseGap", "knJumpCutOut", "knStackAway", "tyStrike", "tyToDot", "tyLineFeed", "tyBracketClose", "tyToIndex", "tyKeyLast", "tyUnderSink", "tyFoldVert"], "decor": ["brackets", "rings", "dots", "arrows", "slash", "sparks", "leaders", "waveform", "barcode", "grid", "stripes", "blobs", "bars", "shapes", "counter", "crosshair", "cropMarks", "reticle", "radar", "progressRing", "timecodeBar", "rulerEdge", "dimension", "indexNum", "dateStamp", "qrBlock", "glitchRects", "concentricSquares", "triangleSpin", "lineBurst", "plusGrid", "guides", "waveLine", "spiralLine", "halftonePatch", "checkerStrip", "beatRing", "orbitDots", "constellation", "confetti", "petals", "rainStreaks", "snow", "lightLeak", "bokeh", "speedCorner", "risingParticles", "twinkle", "brushStroke", "tapePieces", "scribbleCircle", "scribbleUnder", "crossOut", "highlightMark", "heartsStars", "watermarkKanji", "verticalStrip", "romajiLine", "bracketsJP", "seal", "kamon", "seigaiha", "asanoha", "hanabi", "chochin", "shimenawa", "sensu", "tsukiKumo", "momiji", "namiGashira", "kasumi", "hexGrid", "spectrumRing", "dataColumns", "spinner", "headingTape", "glyphLock", "atomOrbit", "sonarArcs", "circuit", "swatches", "ruledLines", "registration", "punchHoles", "staple", "paperClip", "indexTabs", "vines", "cloudPuffs", "starField", "moonPhases", "sunRays", "rainRipples", "bubbles", "smoke", "dandelion", "fireflies", "memphis", "zigzagRibbon", "polkaPatch", "stripeCircle", "decoCorners", "halfCircles", "loopArrows", "starburst", "tally", "cursorClick", "windowChrome", "progressBar", "toggleSwitch", "notifBell", "likeCounter", "mediaControls", "volumeBars", "musicNotes", "hrScratches", "hrSigil", "hrWatchEye", "hrStaticPatch", "hrDustBeam", "hrDrips", "hrCracks", "knSpeedTrail", "knWordTicks", "tyColophon", "tyRunningHead", "tyGlyphBody", "tyTextRule", "tyTypeScale", "tyBigPunct"], "treat": ["none", "hrInkBleed", "hrEroded", "hrRedact", "hrDoubleExp", "knWordScale", "knWordPlate", "outline", "outlineFill", "doubleOutline", "extrude", "longShadow", "hardShadow", "softShadow", "glow", "marker", "underline", "strike", "boxed", "gradientV", "splitColor", "halftone", "stripes", "hatch", "dotted", "alternate", "italic", "wide", "tall", "echoOutline", "emphasisDots", "neonOutline", "chrome", "rainbow", "glitchSplit", "shadowStack", "stencilGap", "waterline", "karaoke", "sizeWave", "rotateAlt", "baselineShift", "fauxBold", "circled", "bracketsQuote", "reflection", "inline", "sticker", "gradientSweep", "kerningWide", "monoGrid", "outlineOffset", "toneShadow", "fadeChars", "cutShift", "focusPull", "spotChar", "ransom", "tyHollowKey", "tyHeadRules", "tyHeadBig", "tyIndexSup"], "bg": ["none", "auroraRibbons", "meshBlobs", "duotoneSweep", "horizonGlow", "seigaiha", "asanoha", "houndstooth", "herringbone", "argyle", "tartan", "chevron", "isoCubes", "hexGrid", "triTess", "moire", "squareTunnel", "spiralArms", "topoLines", "ridgePlot", "starfield", "nightMoon", "skyline", "sunsetSun", "oceanWaves", "rainWindow", "snowLayers", "fireworks", "cloudLayers", "mountains", "filmStrip", "vhsBand", "tornPaper", "godRays", "vignettePulse", "kaleidoscope", "marble", "paperCut", "hrFailingLamp", "hrCorridor", "hrMold", "hrDeadTrees", "sunburst", "concentric", "halftoneFade", "bigStripes", "splitV", "splitH", "splitDiag", "gradientSweep", "spotlight", "tvBars", "checker", "bigChar", "speedLines", "scanBars", "dotGrid", "retroGrid", "bokehBg", "particlesBg", "ripples", "polka", "eqBars", "borderFrame", "letterbox", "noiseField"], "cam": ["push", "orbitDrift", "barrelRoll", "pendulumSway", "focusIn", "rackFocus", "earthquake", "floatNoise", "vertigo", "tiltDown", "spiralIn", "snapPan", "jelly", "hrNervous", "hrDutchSnap", "knReadPan", "knTiltKick", "knCardFlip", "knShearKick", "knJumpCut", "knRushIn", "pullOut", "panL", "panR", "tiltUp", "dutch", "handheld", "beatPunch", "whipIn", "crashZoom", "bounce", "roll", "driftDiag", "shakeHard", "dollyIn", "stepZoom"], "fx": ["chroma", "shake", "slice", "block", "invert", "flash", "zoom", "mosaic", "radialChroma", "bloomFlash", "bulge", "pixelSort", "interlace", "macroBlock", "halftone", "duotone", "ditherBit", "rotateSnap", "echoFrames", "kaleido", "bandInvert", "lightRays", "anamorphic", "heartbeat", "tvStatic", "dustScratches", "filmAdvance", "perspectiveTilt", "ripple", "focusLines", "speedLines", "starGlint", "colorBars", "zoomStutter", "negativeRing", "edgeDetect", "shatter", "defocus", "snapshot", "squash", "scanBar", "loopScroll", "hrSubliminal", "hrSignalLoss", "hrPassingShadow", "panelWipe", "irisTrans", "doors", "blindsTrans", "rgbSplit", "smear", "vhsRoll", "trackingNoise", "mirrorFlash", "strobe", "posterize", "hueShift", "tileShift", "filmBurn", "whipBlur", "blackFrame", "whiteFrame", "gridRepeat", "waveWarp", "pixelDrift", "zoomPunch", "lightSweep", "crtOff", "splitSlide"], "trans": ["hrStaticCut", "hrBlink", "knCornerSwing", "knStutterCut", "knStripSlam", "wipe", "diagonalWipe", "clockWipe", "irisOpen", "pushSlide", "cover", "uncover", "zoomThrough", "doorsOpen", "blinds", "checker", "blockDissolve", "whipPan", "spinOut", "inkBlob", "shatterTiles", "sliceShift", "cubeTurn", "flashCross", "pixelate", "tyRuleWipe", "tyGridCells"]}, "meta": {"layout": {"center": {"name": "Center", "tags": ["glitch", "calm", "pop", "editorial", "emotional", "horror"], "fits": "111111111111111111111111111111111111111111111111111111111111"}, "mixed": {"name": "Mixed", "tags": ["calm", "pop", "editorial", "emotional"], "fits": "011111111111111100000000000000000000000000000000000000000000"}, "vcols": {"name": "Vertical text", "tags": ["glitch", "calm", "editorial", "emotional", "horror"], "fits": "111111111111111111000000000000000000000000000000000000000000"}, "marquee": {"name": "Scrolling banner", "tags": ["glitch", "graphic"], "fits": "111111111111000000000000000000000000000000000000000000000000"}, "tile": {"name": "Tiled text", "tags": ["glitch", "graphic"], "fits": "111111111111000000000000000000000000000000000000000000000000"}, "scatter": {"name": "Scatter", "tags": ["glitch", "pop"], "fits": "011111111111110000000000000000000000000000000000000000000000"}, "ring": {"name": "Ring", "tags": ["pop"], "fits": "011111111111111100000000000000000000000000000000000000000000"}, "wave": {"name": "Wave", "tags": ["pop"], "fits": "011111111111111100000000000000000000000000000000000000000000"}, "huge": {"name": "Oversized text", "tags": ["glitch", "pop", "graphic", "emotional", "horror"], "fits": "111111110000000000000000000000000000000000000000000000000000"}, "labels": {"name": "Labels", "tags": ["pop", "graphic"], "fits": "111111111111111100000000000000000000000000000000000000000000"}, "condensed": {"name": "Condensed", "tags": ["glitch", "graphic", "emotional"], "fits": "111111111100000000000000000000000000000000000000000000000000"}, "gloss": {"name": "Annotation", "tags": ["calm", "editorial"], "fits": "111111111111000000000000000000000000000000000000000000000000"}, "type": {"name": "Type", "tags": ["calm", "editorial", "horror"], "fits": "111111111111111111111111111100000000000000000000000000000000"}, "diag": {"name": "Diagonal band", "tags": ["pop", "graphic"], "fits": "111111111111110000000000000000000000000000000000000000000000"}, "circle": {"name": "Circle", "tags": ["calm", "graphic", "editorial", "emotional"], "fits": "111111111100000000000000000000000000000000000000000000000000"}, "stack": {"name": "Echo stack", "tags": ["glitch", "calm", "editorial", "emotional", "horror"], "fits": "111111111111000000000000000000000000000000000000000000000000"}, "pill": {"name": "Pill", "tags": ["pop", "graphic"], "fits": "111111111111110000000000000000000000000000000000000000000000"}, "hrFlashlight": {"name": "Flashlight", "w": 1, "tags": ["horror", "emotional"], "ae": "circle", "busy": true, "pack": "horror", "set": "horror", "fits": "111111111111111111000000000000000000000000000000000000000000"}, "hrDoorGap": {"name": "Door Gap", "w": 0.9, "tags": ["horror", "editorial"], "ae": "vcols", "busy": true, "pack": "horror", "set": "horror", "fits": "111111111111111100000000000000000000000000000000000000000000"}, "hrWallScrawl": {"name": "Wall Scrawl", "w": 0.9, "tags": ["horror", "glitch"], "ae": "tile", "busy": true, "pack": "horror", "set": "horror", "fits": "111111111111110000000000000000000000000000000000000000000000"}, "hrCctv": {"name": "CCTV Monitor", "w": 1, "tags": ["horror", "glitch", "editorial"], "ae": "type", "busy": true, "treat": "safe", "pack": "horror", "set": "horror", "fits": "111111111111111111000000000000000000000000000000000000000000"}, "hrOuija": {"name": "Spirit Board", "w": 0.7, "tags": ["horror"], "ae": "type", "portrait": 0.8, "pack": "horror", "set": "horror", "fits": "111111111111110000000000000000000000000000000000000000000000"}, "hrMissing": {"name": "Missing Poster", "w": 0.8, "tags": ["horror", "editorial"], "ae": "labels", "treat": "safe", "pack": "horror", "set": "horror", "fits": "111111111111111100000000000000000000000000000000000000000000"}, "hrWrongOne": {"name": "The Wrong Glyph", "w": 1, "tags": ["horror", "editorial", "calm"], "ae": "mixed", "pack": "horror", "set": "horror", "fits": "011111111111111100000000000000000000000000000000000000000000"}, "hrRisingDark": {"name": "Out of the Dark", "w": 0.8, "tags": ["horror", "emotional"], "ae": "wave", "pack": "horror", "set": "horror", "fits": "111111111111110000000000000000000000000000000000000000000000"}, "hrRedacted": {"name": "Redacted File", "w": 0.8, "tags": ["horror", "editorial"], "ae": "type", "busy": true, "pack": "horror", "set": "horror", "fits": "111111111111111111110000000000000000000000000000000000000000"}, "hrStaticTv": {"name": "Static TV", "w": 0.8, "tags": ["horror", "glitch"], "ae": "circle", "treat": "safe", "pack": "horror", "set": "horror", "fits": "111111111111110000000000000000000000000000000000000000000000"}, "hrSpiritPhoto": {"name": "Spirit Photo", "w": 0.8, "tags": ["horror", "emotional"], "ae": "gloss", "pack": "horror", "set": "horror", "fits": "111111111111111100000000000000000000000000000000000000000000"}, "hrWrongShadow": {"name": "Wrong Shadow", "w": 0.9, "tags": ["horror", "emotional", "graphic"], "ae": "stack", "pack": "horror", "set": "horror", "fits": "111111111111000000000000000000000000000000000000000000000000"}, "knSlamStack": {"name": "Slam Stack", "w": 1.1, "tags": ["pop", "graphic"], "ae": "justified", "enterBias": {"cut": 3, "blur": 0.7, "pop": 0.8, "slice": 0.3, "wipe": 0.4}, "pack": "kinetic", "set": "kinetic", "fits": "011111111111111111000000000000000000000000000000000000000000"}, "knQuarterTurn": {"name": "Quarter Turn", "w": 0.9, "tags": ["pop", "graphic", "editorial"], "ae": "sideways", "portrait": 0.9, "enterBias": {"cut": 3, "pop": 0.8, "blur": 0.8, "slice": 0.2, "wipe": 0.3}, "pack": "kinetic", "set": "kinetic", "fits": "011111111111111100000000000000000000000000000000000000000000"}, "knSwapCenter": {"name": "Word Swap", "w": 1, "tags": ["pop", "graphic", "glitch"], "ae": "slotMachine", "enterBias": {"cut": 3, "pop": 0.7, "blur": 0.8, "slice": 0.3, "wipe": 0.3}, "pack": "kinetic", "set": "kinetic", "fits": "011111111111111111000000000000000000000000000000000000000000"}, "knZoomDive": {"name": "Dive Into Letter", "w": 0.9, "tags": ["pop", "emotional", "graphic"], "ae": "zoomRepeat", "enterBias": {"cut": 3, "blur": 0.8, "pop": 0.5, "slice": 0.2, "wipe": 0.2}, "pack": "kinetic", "set": "kinetic", "fits": "011111111111111100000000000000000000000000000000000000000000"}, "knFlowSnap": {"name": "Flow and Snap", "w": 0.9, "tags": ["graphic", "pop", "editorial"], "ae": "gridCells", "enterBias": {"cut": 3, "blur": 0.6, "pop": 0.6, "slice": 0.2, "wipe": 0.2}, "pack": "kinetic", "set": "kinetic", "fits": "001111111111111100000000000000000000000000000000000000000000"}, "knSeesaw": {"name": "Seesaw", "w": 0.8, "tags": ["pop", "graphic"], "ae": "bounceLine", "portrait": 0.4, "enterBias": {"cut": 3, "pop": 0.6, "blur": 0.5, "slice": 0.2, "wipe": 0.2}, "pack": "kinetic", "set": "kinetic", "fits": "011111111111110000000000000000000000000000000000000000000000"}, "knTypeSlam": {"name": "Type then Slam", "w": 1, "tags": ["pop", "graphic", "editorial"], "ae": "type", "enterBias": {"cut": 3, "blur": 0.5, "pop": 0.5, "slice": 0.2, "wipe": 0.3}, "pack": "kinetic", "set": "kinetic", "fits": "011111111111111111111111000000000000000000000000000000000000"}, "knRhythmCuts": {"name": "Word Jump Cuts", "w": 0.9, "tags": ["pop", "graphic", "glitch"], "ae": "panels", "enterBias": {"cut": 3.5, "pop": 0.4, "blur": 0.4, "slice": 0.2, "wipe": 0.2}, "pack": "kinetic", "set": "kinetic", "fits": "011111111111111111000000000000000000000000000000000000000000"}, "knPathRide": {"name": "Loop Track", "w": 0.8, "tags": ["pop", "graphic"], "ae": "wave", "portrait": 0.4, "enterBias": {"cut": 3, "blur": 0.6, "pop": 0.3, "slice": 0.2, "wipe": 0.2}, "pack": "kinetic", "set": "kinetic", "fits": "011111111111110000000000000000000000000000000000000000000000"}, "knGearWords": {"name": "Gears", "w": 0.7, "tags": ["pop", "graphic"], "ae": "circleWords", "enterBias": {"cut": 3, "pop": 0.5, "blur": 0.4, "slice": 0.1, "wipe": 0.1}, "treat": "safe", "pack": "kinetic", "set": "kinetic", "fits": "011111111111111100000000000000000000000000000000000000000000"}, "knCollide": {"name": "Head-on Crash", "w": 0.9, "tags": ["pop", "graphic", "glitch"], "ae": "splitHalves", "enterBias": {"cut": 3.5, "pop": 0.3, "blur": 0.4, "slice": 0.2, "wipe": 0.2}, "pack": "kinetic", "set": "kinetic", "fits": "011111111111111100000000000000000000000000000000000000000000"}, "knTumble": {"name": "Tumbling Boxes", "w": 0.8, "tags": ["pop", "graphic"], "ae": "dominoes", "enterBias": {"cut": 3.5, "pop": 0.3, "blur": 0.4, "slice": 0.1, "wipe": 0.1}, "pack": "kinetic", "set": "kinetic", "fits": "011111111111111100000000000000000000000000000000000000000000"}, "knReflow": {"name": "Vertical to Line", "w": 0.9, "tags": ["editorial", "graphic", "emotional"], "ae": "halfVertical", "enterBias": {"cut": 2.5, "blur": 1, "pop": 0.4, "slice": 0.2, "wipe": 0.3}, "pack": "kinetic", "set": "kinetic", "fits": "011111111111111100000000000000000000000000000000000000000000"}, "knPadGrid": {"name": "Drum Pads", "w": 0.8, "tags": ["pop", "graphic", "glitch"], "ae": "gridCells", "enterBias": {"cut": 3, "pop": 0.8, "blur": 0.4, "slice": 0.2, "wipe": 0.2}, "pack": "kinetic", "set": "kinetic", "fits": "011111111111111111000000000000000000000000000000000000000000"}, "lowerThird": {"name": "Lower third", "w": 1.1, "tags": ["editorial", "calm", "emotional"], "portrait": 0.9, "pack": "layoutsA", "ae": "center", "fits": "111111111111111111110000000000000000000000000000000000000000"}, "corners": {"name": "Opposite corners", "w": 1, "tags": ["graphic", "editorial", "calm"], "pack": "layoutsA", "ae": "mixed", "fits": "011111111111111111000000000000000000000000000000000000000000"}, "staircase": {"name": "Staircase", "w": 1, "tags": ["graphic", "pop", "editorial"], "pack": "layoutsA", "ae": "mixed", "fits": "011111111111111111000000000000000000000000000000000000000000"}, "zigzag": {"name": "Zigzag", "w": 0.9, "tags": ["pop", "graphic"], "pack": "layoutsA", "ae": "wave", "fits": "001111111111111100000000000000000000000000000000000000000000"}, "arcTop": {"name": "Rainbow arc", "w": 0.9, "tags": ["pop", "emotional", "graphic"], "pack": "layoutsA", "ae": "ring", "fits": "001111111111111100000000000000000000000000000000000000000000"}, "spiral": {"name": "Spiral", "w": 0.8, "tags": ["emotional", "graphic", "calm"], "pack": "layoutsA", "ae": "ring", "fits": "011111111111111100000000000000000000000000000000000000000000"}, "gridCells": {"name": "Grid cells", "w": 1, "tags": ["graphic", "editorial", "pop"], "treat": "safe", "pack": "layoutsA", "ae": "labels", "fits": "011111111111111111000000000000000000000000000000000000000000"}, "dropCap": {"name": "Drop cap", "w": 1, "tags": ["editorial", "emotional", "calm"], "portrait": 0.8, "enterBias": {"blur": 1.4, "wipe": 1.3, "type": 1.2}, "pack": "layoutsA", "ae": "mixed", "fits": "001111111111111111111111000000000000000000000000000000000000"}, "justified": {"name": "Justified", "w": 0.8, "tags": ["editorial", "calm", "emotional"], "busy": true, "treat": "safe", "pack": "layoutsA", "ae": "tile", "fits": "011111111111111111111100000000000000000000000000000000000000"}, "frameBox": {"name": "Picture frame", "w": 1.1, "tags": ["editorial", "calm", "graphic"], "pack": "layoutsA", "ae": "center", "fits": "111111111111111111110000000000000000000000000000000000000000"}, "bubble": {"name": "Bubble", "w": 0.9, "tags": ["pop", "emotional"], "enterBias": {"pop": 2, "drop": 1.4, "spin": 0.5}, "treat": "safe", "pack": "layoutsA", "ae": "pill", "fits": "111111111111111111110000000000000000000000000000000000000000"}, "subtitleBar": {"name": "Subtitle bar", "w": 0.9, "tags": ["emotional", "calm", "editorial"], "portrait": 0.7, "busy": true, "treat": "safe", "pack": "layoutsA", "ae": "center", "fits": "111111111111111111111111110000000000000000000000000000000000"}, "ticker": {"name": "Ticker", "w": 0.9, "tags": ["pop", "glitch", "graphic"], "enterBias": {"wipe": 1.6, "slice": 1.4, "type": 1.2}, "pack": "layoutsA", "ae": "marquee", "fits": "111111111111111111000000000000000000000000000000000000000000"}, "splitScreen": {"name": "Split screen", "w": 0.9, "tags": ["graphic", "pop", "editorial"], "emph": 1.6, "enterBias": {"wipe": 1.5, "slice": 1.4, "stretch": 1.2}, "busy": true, "treat": false, "pack": "layoutsA", "ae": "diag", "fits": "111111111111110000000000000000000000000000000000000000000000"}, "mirror": {"name": "Mirror", "w": 1, "tags": ["calm", "emotional", "graphic"], "pack": "layoutsA", "ae": "stack", "fits": "111111111111111100000000000000000000000000000000000000000000"}, "sideways": {"name": "Sideways text", "w": 1, "tags": ["editorial", "graphic", "pop"], "portrait": 1.3, "emph": 1.3, "enterBias": {"wipe": 1.4, "slice": 1.3, "stretch": 1.3}, "pack": "layoutsA", "ae": "vcols", "fits": "001111111111111100000000000000000000000000000000000000000000"}, "edgeFrame": {"name": "Edge frame", "w": 0.9, "tags": ["graphic", "editorial", "glitch"], "busy": true, "pack": "layoutsA", "ae": "marquee", "fits": "111111111111111100000000000000000000000000000000000000000000"}, "perspective": {"name": "Perspective", "w": 0.9, "tags": ["graphic", "emotional", "glitch"], "emph": 1.3, "enterBias": {"zoom": 1.6, "stretch": 1.3}, "pack": "layoutsA", "ae": "stack", "fits": "111111111111110000000000000000000000000000000000000000000000"}, "hanko": {"name": "Signature seal", "w": 0.8, "tags": ["calm", "emotional", "editorial"], "wa": true, "portrait": 1.2, "emph": 1.3, "enterBias": {"blur": 1.6, "wipe": 1.3, "type": 1.2}, "pack": "layoutsA", "ae": "vcols", "fits": "111111111100000000000000000000000000000000000000000000000000"}, "genkou": {"name": "Manuscript paper", "w": 0.8, "tags": ["calm", "editorial", "emotional"], "wa": true, "portrait": 1.2, "enterBias": {"type": 2.2, "blur": 1.4, "assemble": 1.3}, "pack": "layoutsA", "ae": "vcols", "fits": "111111111111111111111100000000000000000000000000000000000000"}, "panels": {"name": "Comic panels", "w": 0.9, "tags": ["pop", "graphic", "emotional"], "emph": 1.4, "enterBias": {"pop": 1.4, "zoom": 1.3, "slice": 1.2}, "busy": true, "treat": "safe", "pack": "layoutsA", "ae": "diag", "fits": "011111111111111111000000000000000000000000000000000000000000"}, "filmstrip": {"name": "Filmstrip", "w": 0.8, "tags": ["emotional", "calm", "graphic"], "portrait": 0.8, "busy": true, "treat": "safe", "pack": "layoutsA", "ae": "labels", "fits": "111111111111111111000000000000000000000000000000000000000000"}, "quote": {"name": "Quote", "w": 1, "tags": ["editorial", "emotional", "calm"], "emph": 1.2, "enterBias": {"blur": 1.4, "type": 1.3, "wipe": 1.2}, "pack": "layoutsA", "ae": "center", "fits": "111111111111111111110000000000000000000000000000000000000000"}, "ruler": {"name": "Dimension lines", "w": 0.8, "tags": ["graphic", "editorial", "glitch"], "pack": "layoutsA", "ae": "gloss", "fits": "111111111111111100000000000000000000000000000000000000000000"}, "searchBar": {"name": "Search bar", "w": 0.6, "tags": ["pop", "graphic"], "portrait": 0.8, "enterBias": {"type": 3, "scramble": 1.5, "cut": 1.5}, "treat": "safe", "pack": "layoutsA", "ae": "type", "fits": "111111111111111111000000000000000000000000000000000000000000"}, "chat": {"name": "Chat", "w": 0.6, "tags": ["pop", "emotional"], "enterBias": {"pop": 2, "cut": 1.5, "type": 1.3}, "treat": "safe", "pack": "layoutsA", "ae": "labels", "fits": "111111111111111111111100000000000000000000000000000000000000"}, "notification": {"name": "Notification", "w": 0.5, "tags": ["pop", "emotional", "calm"], "enterBias": {"cut": 1.6, "type": 1.3, "blur": 1.2}, "treat": "safe", "pack": "layoutsA", "ae": "pill", "fits": "111111111111111111111111000000000000000000000000000000000000"}, "ticket": {"name": "Ticket", "w": 0.6, "tags": ["pop", "graphic", "editorial"], "portrait": 0.6, "treat": "safe", "pack": "layoutsA", "ae": "pill", "fits": "111111111111111111000000000000000000000000000000000000000000"}, "rain": {"name": "Letter rain", "w": 0.9, "tags": ["glitch", "graphic", "emotional"], "enterBias": {"cut": 2.4, "flicker": 1.4, "scramble": 1.4, "blur": 1.1, "type": 0.3, "wipe": 0.4, "slice": 0.4, "stretch": 0.5, "assemble": 0.5}, "busy": true, "pack": "layoutsB", "ae": "tile", "fits": "111111111111111100000000000000000000000000000000000000000000"}, "hanging": {"name": "Hanging", "w": 0.9, "tags": ["pop", "calm", "emotional"], "portrait": 0.8, "enterBias": {"drop": 1.8, "pop": 1.3, "cut": 1.5, "slice": 0.3, "wipe": 0.3, "stretch": 0.4}, "treat": "safe", "pack": "layoutsB", "ae": "scatter", "fits": "011111111111000000000000000000000000000000000000000000000000"}, "orbit": {"name": "Orbit", "w": 1, "tags": ["calm", "graphic", "emotional"], "pack": "layoutsB", "ae": "ring", "fits": "111111111111110000000000000000000000000000000000000000000000"}, "tunnel": {"name": "Tunnel", "w": 0.9, "tags": ["glitch", "graphic", "emotional"], "emph": 1.3, "busy": true, "pack": "layoutsB", "ae": "tile", "fits": "111111111111110000000000000000000000000000000000000000000000"}, "wordCloud": {"name": "Word cloud", "w": 0.9, "tags": ["pop", "editorial", "graphic"], "busy": true, "pack": "layoutsB", "ae": "scatter", "fits": "111111111111110000000000000000000000000000000000000000000000"}, "bounceLine": {"name": "Bouncing line", "w": 1, "tags": ["pop"], "enterBias": {"drop": 1.8, "pop": 1.5, "cut": 1.2, "slice": 0.4, "stretch": 0.5}, "pack": "layoutsB", "ae": "mixed", "fits": "011111111111111100000000000000000000000000000000000000000000"}, "elastic": {"name": "Elastic", "w": 0.9, "tags": ["pop", "graphic"], "portrait": 0.85, "enterBias": {"cut": 1.6, "stretch": 0.3, "pop": 1.2}, "pack": "layoutsB", "ae": "condensed", "fits": "011111111111000000000000000000000000000000000000000000000000"}, "crossBands": {"name": "Crossing bands", "w": 1, "tags": ["graphic", "pop", "glitch"], "busy": true, "treat": "safe", "pack": "layoutsB", "ae": "diag", "fits": "111111111111110000000000000000000000000000000000000000000000"}, "stickerBomb": {"name": "Sticker collage", "w": 0.9, "tags": ["pop", "graphic"], "enterBias": {"cut": 2, "pop": 1.2, "slice": 0.4, "wipe": 0.5, "assemble": 0.4}, "treat": "safe", "pack": "layoutsB", "ae": "labels", "fits": "111111111111000000000000000000000000000000000000000000000000"}, "neon": {"name": "Neon", "w": 1, "tags": ["calm", "emotional"], "enterBias": {"flicker": 2.6, "blur": 1.4, "cut": 1.3, "assemble": 0.3, "slice": 0.5, "scramble": 0.6}, "treat": false, "pack": "layoutsB", "ae": "center", "fits": "111111111111110000000000000000000000000000000000000000000000"}, "keycaps": {"name": "Keycaps", "w": 0.6, "tags": ["pop", "graphic"], "enterBias": {"cut": 2.5, "pop": 1.2, "blur": 0.6, "slice": 0.3, "wipe": 0.3, "stretch": 0.3, "assemble": 0.3}, "treat": "safe", "pack": "layoutsB", "ae": "labels", "fits": "111111111100000000000000000000000000000000000000000000000000"}, "bubbles": {"name": "Bubbles", "w": 0.8, "tags": ["pop", "calm"], "enterBias": {"pop": 1.6, "cut": 1.5, "blur": 1.2, "slice": 0.3, "wipe": 0.3, "stretch": 0.3}, "treat": "safe", "pack": "layoutsB", "ae": "scatter", "fits": "111111111111000000000000000000000000000000000000000000000000"}, "slotMachine": {"name": "Slot machine", "w": 0.6, "tags": ["pop", "glitch"], "enterBias": {"cut": 3, "flicker": 0.6, "blur": 0.5, "slice": 0.2, "wipe": 0.2, "assemble": 0.2, "type": 0.2}, "treat": "safe", "pack": "layoutsB", "ae": "labels", "fits": "111111111100000000000000000000000000000000000000000000000000"}, "flipBoard": {"name": "Flip board", "w": 0.7, "tags": ["graphic", "editorial"], "enterBias": {"cut": 3, "flicker": 0.5, "blur": 0.4, "slice": 0.2, "wipe": 0.3, "assemble": 0.2, "type": 0.3}, "treat": "safe", "pack": "layoutsB", "ae": "labels", "fits": "111111111111000000000000000000000000000000000000000000000000"}, "credits": {"name": "End credits", "w": 1, "tags": ["calm", "editorial", "emotional"], "pack": "layoutsB", "ae": "type", "fits": "111111111111111100000000000000000000000000000000000000000000"}, "zoomRepeat": {"name": "Zoom repeat", "w": 0.9, "tags": ["glitch", "emotional", "graphic"], "emph": 1.5, "busy": true, "pack": "layoutsB", "ae": "stack", "fits": "111111111111000000000000000000000000000000000000000000000000"}, "splitHalves": {"name": "Split halves", "w": 1, "tags": ["graphic", "glitch", "editorial"], "emph": 1.2, "enterBias": {"cut": 1.8, "slice": 0.3, "wipe": 0.6}, "pack": "layoutsB", "ae": "stack", "fits": "111111111111110000000000000000000000000000000000000000000000"}, "columnsBig": {"name": "Contrasting columns", "w": 1, "tags": ["editorial", "calm", "emotional"], "portrait": 1.4, "pack": "layoutsB", "ae": "vcols", "fits": "111111111100000000000000000000000000000000000000000000000000"}, "circleWords": {"name": "Concentric words", "w": 1, "tags": ["graphic", "calm", "editorial"], "pack": "layoutsB", "ae": "ring", "fits": "111111111111110000000000000000000000000000000000000000000000"}, "dotMatrix": {"name": "Dot matrix", "w": 0.7, "tags": ["graphic", "glitch", "pop"], "enterBias": {"cut": 2.5, "flicker": 1.6, "scramble": 0.2, "assemble": 0.2, "type": 1.2}, "treat": false, "pack": "layoutsB", "ae": "type", "fits": "111111111111000000000000000000000000000000000000000000000000"}, "depthStack": {"name": "Depth stack", "w": 1, "tags": ["graphic", "emotional", "glitch"], "emph": 1.3, "pack": "layoutsB", "ae": "stack", "fits": "111111111111000000000000000000000000000000000000000000000000"}, "typeSpecimen": {"name": "Type specimen", "w": 0.8, "tags": ["editorial", "graphic"], "pack": "layoutsB", "ae": "stack", "fits": "111111110000000000000000000000000000000000000000000000000000"}, "kanjiFocus": {"name": "Character focus", "w": 1, "tags": ["emotional", "editorial", "calm"], "emph": 1.6, "pack": "layoutsB", "ae": "huge", "fits": "011111111111111100000000000000000000000000000000000000000000"}, "halfVertical": {"name": "Mixed vertical type", "w": 1, "tags": ["editorial", "graphic"], "pack": "layoutsB", "ae": "vcols", "fits": "001111111111110000000000000000000000000000000000000000000000"}, "curtain": {"name": "Curtain", "w": 0.9, "tags": ["emotional", "pop", "graphic"], "emph": 1.4, "pack": "layoutsB", "ae": "center", "fits": "111111111111111100000000000000000000000000000000000000000000"}, "equalizer": {"name": "Equalizer", "w": 0.8, "tags": ["pop", "graphic", "glitch"], "portrait": 0.9, "enterBias": {"cut": 1.5, "pop": 1.3, "drop": 1.2, "slice": 0.4, "wipe": 0.5}, "pack": "layoutsB", "ae": "mixed", "fits": "111111111111000000000000000000000000000000000000000000000000"}, "tape": {"name": "Tape", "w": 1, "tags": ["pop", "editorial", "graphic"], "treat": "safe", "pack": "layoutsB", "ae": "diag", "fits": "111111111111111100000000000000000000000000000000000000000000"}, "magazine": {"name": "Magazine spread", "w": 0.9, "tags": ["editorial", "calm", "emotional"], "extra": true, "ae": "gloss", "enterBias": {"blur": 1.3, "wipe": 1.3, "type": 1.2, "cut": 1.2}, "treat": "safe", "pack": "layoutsC", "fits": "111111111111111111000000000000000000000000000000000000000000"}, "headlineDeck": {"name": "Headline and deck", "w": 1.1, "tags": ["editorial", "graphic", "calm"], "extra": true, "ae": "center", "enterBias": {"wipe": 1.4, "slice": 1.2, "stretch": 1.2}, "pack": "layoutsC", "fits": "111111111111111111111100000000000000000000000000000000000000"}, "contents": {"name": "Contents", "w": 0.9, "tags": ["editorial", "calm"], "extra": true, "ae": "stack", "enterBias": {"wipe": 1.5, "type": 1.3, "slice": 1.2}, "pack": "layoutsC", "fits": "011111111111111111110000000000000000000000000000000000000000"}, "footnote": {"name": "Footnote", "w": 0.9, "tags": ["editorial", "calm", "emotional"], "extra": true, "ae": "gloss", "enterBias": {"blur": 1.3, "type": 1.3, "wipe": 1.2}, "pack": "layoutsC", "fits": "011111111111111111111100000000000000000000000000000000000000"}, "proofread": {"name": "Proof sheet", "w": 0.8, "tags": ["editorial", "graphic", "calm"], "extra": true, "ae": "gloss", "enterBias": {"type": 1.4, "blur": 1.2, "cut": 1.2}, "pack": "layoutsC", "fits": "011111111111111111110000000000000000000000000000000000000000"}, "numbered": {"name": "Numbered", "w": 1, "tags": ["graphic", "editorial", "pop"], "extra": true, "ae": "mixed", "enterBias": {"slice": 1.3, "wipe": 1.3, "drop": 1.2}, "pack": "layoutsC", "fits": "011111111111111111000000000000000000000000000000000000000000"}, "poster": {"name": "Poster", "w": 1, "tags": ["graphic", "pop", "editorial"], "extra": true, "ae": "huge", "emph": 1.3, "enterBias": {"slice": 1.4, "stretch": 1.3, "wipe": 1.2}, "pack": "layoutsC", "fits": "111111111111111100000000000000000000000000000000000000000000"}, "swissGrid": {"name": "Swiss grid", "w": 1, "tags": ["graphic", "editorial", "calm"], "extra": true, "ae": "mixed", "enterBias": {"wipe": 1.4, "slice": 1.3, "cut": 1.2}, "pack": "layoutsC", "fits": "111111111111111111000000000000000000000000000000000000000000"}, "dictionary": {"name": "Dictionary", "w": 0.9, "tags": ["editorial", "calm", "emotional"], "extra": true, "ae": "gloss", "enterBias": {"blur": 1.3, "type": 1.3, "wipe": 1.2}, "pack": "layoutsC", "fits": "111111111111111100000000000000000000000000000000000000000000"}, "ema": {"name": "Ema wish plaque", "w": 0.7, "tags": ["emotional", "calm", "pop"], "extra": true, "wa": true, "ae": "labels", "enterBias": {"blur": 1.3, "cut": 1.3, "type": 1.3, "slice": 0.5, "stretch": 0.5}, "treat": "safe", "pack": "layoutsC", "fits": "111111111111111100000000000000000000000000000000000000000000"}, "ransom": {"name": "Cutout letters", "w": 0.8, "tags": ["pop", "glitch", "graphic"], "extra": true, "ae": "labels", "enterBias": {"cut": 2, "pop": 1.5, "drop": 1.3, "blur": 0.4, "wipe": 0.4, "slice": 0.5}, "treat": false, "pack": "layoutsC", "fits": "111111111111111100000000000000000000000000000000000000000000"}, "newspaper": {"name": "Newspaper", "w": 0.8, "tags": ["editorial", "graphic", "pop"], "extra": true, "ae": "tile", "emph": 1.3, "enterBias": {"cut": 1.4, "zoom": 1.3, "slice": 1.2}, "busy": true, "treat": "safe", "pack": "layoutsC", "fits": "111111111111111100000000000000000000000000000000000000000000"}, "vinyl": {"name": "Vinyl record", "w": 0.8, "tags": ["emotional", "pop", "calm"], "extra": true, "ae": "ring", "enterBias": {"blur": 1.3, "zoom": 1.2, "spin": 0.4}, "treat": "safe", "pack": "layoutsC", "fits": "111111111111111100000000000000000000000000000000000000000000"}, "cassette": {"name": "Cassette", "w": 0.8, "tags": ["emotional", "pop", "calm"], "extra": true, "ae": "pill", "portrait": 0.7, "enterBias": {"type": 1.6, "wipe": 1.3, "cut": 1.2}, "treat": "safe", "pack": "layoutsC", "fits": "111111111111111100000000000000000000000000000000000000000000"}, "bookSpine": {"name": "Book spine", "w": 0.8, "tags": ["calm", "editorial", "emotional"], "extra": true, "ae": "vcols", "portrait": 1.1, "enterBias": {"wipe": 1.3, "blur": 1.2, "cut": 1.2}, "treat": "safe", "pack": "layoutsC", "fits": "111111111111110000000000000000000000000000000000000000000000"}, "polaroid": {"name": "Polaroid", "w": 0.9, "tags": ["emotional", "calm", "pop"], "extra": true, "ae": "labels", "enterBias": {"blur": 1.6, "cut": 1.2, "drop": 1.2, "slice": 0.5}, "treat": "safe", "pack": "layoutsC", "fits": "111111111111111100000000000000000000000000000000000000000000"}, "stampSheet": {"name": "Stamp sheet", "w": 0.7, "tags": ["pop", "graphic", "calm"], "extra": true, "ae": "tile", "enterBias": {"pop": 1.4, "cut": 1.3, "blur": 1.1}, "treat": "safe", "pack": "layoutsC", "fits": "111111111111000000000000000000000000000000000000000000000000"}, "postcard": {"name": "Postcard", "w": 0.8, "tags": ["emotional", "calm", "editorial"], "extra": true, "wa": true, "ae": "vcols", "enterBias": {"blur": 1.3, "type": 1.3, "wipe": 1.2}, "treat": "safe", "pack": "layoutsC", "fits": "111111111111111111000000000000000000000000000000000000000000"}, "letterPaper": {"name": "Letter paper", "w": 0.9, "tags": ["emotional", "calm"], "extra": true, "wa": true, "ae": "vcols", "portrait": 1.1, "enterBias": {"type": 1.8, "wipe": 1.5, "blur": 1.3, "slice": 0.4, "stretch": 0.4}, "treat": "safe", "pack": "layoutsC", "fits": "111111111111111111111100000000000000000000000000000000000000"}, "calendar": {"name": "Calendar", "w": 0.7, "tags": ["pop", "editorial", "calm"], "extra": true, "ae": "center", "enterBias": {"pop": 1.3, "cut": 1.3, "zoom": 1.2}, "treat": "safe", "pack": "layoutsC", "fits": "111111111111111100000000000000000000000000000000000000000000"}, "chochin": {"name": "Paper lantern", "w": 0.7, "tags": ["emotional", "calm", "pop"], "extra": true, "wa": true, "ae": "circle", "portrait": 1.1, "enterBias": {"blur": 1.4, "flicker": 1.4, "cut": 1.2, "slice": 0.4}, "treat": "safe", "pack": "layoutsC", "fits": "111111111111000000000000000000000000000000000000000000000000"}, "routeMap": {"name": "Route map", "w": 0.8, "tags": ["graphic", "pop", "editorial"], "extra": true, "ae": "labels", "enterBias": {"wipe": 1.4, "pop": 1.3, "type": 1.2}, "pack": "layoutsC", "fits": "011111111111111111000000000000000000000000000000000000000000"}, "stationSign": {"name": "Station sign", "w": 0.8, "tags": ["graphic", "pop", "editorial"], "extra": true, "ae": "center", "portrait": 0.6, "enterBias": {"cut": 1.4, "wipe": 1.3, "slice": 1.2}, "treat": "safe", "pack": "layoutsC", "fits": "111111111111000000000000000000000000000000000000000000000000"}, "noren": {"name": "Noren curtain", "w": 0.8, "tags": ["calm", "emotional", "graphic"], "extra": true, "wa": true, "ae": "vcols", "portrait": 1.1, "enterBias": {"wipe": 1.5, "blur": 1.3, "cut": 1.2, "slice": 0.4}, "treat": "safe", "pack": "layoutsC", "fits": "111111111111000000000000000000000000000000000000000000000000"}, "tanzaku": {"name": "Wish strip", "w": 0.7, "tags": ["emotional", "calm", "pop"], "extra": true, "wa": true, "ae": "vcols", "portrait": 1.2, "enterBias": {"blur": 1.3, "drop": 1.3, "cut": 1.2, "slice": 0.4, "stretch": 0.4}, "treat": "safe", "pack": "layoutsC", "fits": "111111111111111100000000000000000000000000000000000000000000"}, "omikuji": {"name": "Fortune slip", "w": 0.7, "tags": ["emotional", "calm", "editorial"], "extra": true, "wa": true, "ae": "vcols", "portrait": 1.1, "enterBias": {"wipe": 1.5, "blur": 1.3, "type": 1.2}, "treat": "safe", "pack": "layoutsC", "fits": "111111111111111100000000000000000000000000000000000000000000"}, "kakejiku": {"name": "Hanging scroll", "w": 0.7, "tags": ["calm", "emotional", "editorial"], "extra": true, "wa": true, "ae": "vcols", "portrait": 1.3, "enterBias": {"blur": 1.4, "wipe": 1.3, "cut": 1.2, "slice": 0.4, "stretch": 0.4}, "treat": "safe", "pack": "layoutsC", "fits": "111111111111110000000000000000000000000000000000000000000000"}, "shoji": {"name": "Shoji screen", "w": 0.7, "tags": ["calm", "emotional", "graphic"], "extra": true, "wa": true, "ae": "center", "enterBias": {"blur": 1.6, "cut": 1.2, "wipe": 0.6, "slice": 0.4}, "busy": true, "treat": "safe", "pack": "layoutsC", "fits": "111111111111111100000000000000000000000000000000000000000000"}, "clapper": {"name": "Clapperboard", "w": 0.6, "tags": ["pop", "graphic", "editorial"], "extra": true, "ae": "labels", "enterBias": {"cut": 1.8, "pop": 1.2, "blur": 0.5}, "treat": "safe", "pack": "layoutsC", "fits": "111111111111111100000000000000000000000000000000000000000000"}, "warningLabel": {"name": "Warning label", "w": 0.7, "tags": ["graphic", "glitch", "pop"], "extra": true, "ae": "pill", "enterBias": {"cut": 1.5, "flicker": 1.5, "pop": 1.3, "blur": 0.5}, "treat": "safe", "pack": "layoutsC", "fits": "111111111111111100000000000000000000000000000000000000000000"}, "priceTag": {"name": "Price tag", "w": 0.6, "tags": ["pop", "graphic"], "extra": true, "ae": "pill", "enterBias": {"pop": 1.5, "cut": 1.3, "drop": 1.2, "slice": 0.5}, "treat": "safe", "pack": "layoutsC", "fits": "111111111111110000000000000000000000000000000000000000000000"}, "nameTag": {"name": "Name tag", "w": 0.6, "tags": ["pop", "emotional"], "extra": true, "ae": "pill", "enterBias": {"cut": 1.4, "type": 1.3, "pop": 1.2, "slice": 0.5}, "treat": "safe", "pack": "layoutsC", "fits": "111111111111110000000000000000000000000000000000000000000000"}, "stickyNotes": {"name": "Sticky notes", "w": 0.8, "tags": ["pop", "emotional", "calm"], "extra": true, "ae": "labels", "enterBias": {"cut": 1.5, "pop": 1.4, "type": 1.2, "slice": 0.5, "stretch": 0.5}, "treat": "safe", "pack": "layoutsC", "fits": "111111111111111111000000000000000000000000000000000000000000"}, "karuta": {"name": "Karuta card", "w": 0.6, "tags": ["pop", "emotional", "editorial"], "extra": true, "wa": true, "ae": "vcols", "enterBias": {"cut": 1.6, "pop": 1.2, "blur": 0.6, "slice": 0.5}, "treat": "safe", "pack": "layoutsC", "fits": "111111111111111100000000000000000000000000000000000000000000"}, "cube": {"name": "Cube", "w": 0.8, "tags": ["graphic", "pop"], "extra": true, "ae": "pill", "emph": 1.2, "enterBias": {"cut": 1.6, "blur": 1.2, "pop": 1.1, "slice": 0.4, "wipe": 0.5, "stretch": 0.5}, "treat": "safe", "pack": "layoutsD", "fits": "111111111111111100000000000000000000000000000000000000000000"}, "cylinder": {"name": "Cylinder", "w": 0.9, "tags": ["graphic", "calm", "emotional"], "extra": true, "ae": "ring", "enterBias": {"cut": 1.4, "blur": 1.3, "slice": 0.3, "wipe": 0.4, "stretch": 0.5}, "pack": "layoutsD", "fits": "011111111111111100000000000000000000000000000000000000000000"}, "flipCards": {"name": "Flipping cards", "w": 0.8, "tags": ["pop", "graphic"], "extra": true, "ae": "labels", "enterBias": {"cut": 3, "pop": 0.8, "blur": 0.5, "slice": 0.2, "wipe": 0.2, "stretch": 0.3, "assemble": 0.2}, "treat": "safe", "pack": "layoutsD", "fits": "111111111111110000000000000000000000000000000000000000000000"}, "accordion": {"name": "Accordion", "w": 0.8, "tags": ["pop", "graphic", "editorial"], "extra": true, "ae": "labels", "enterBias": {"cut": 2, "blur": 1, "pop": 0.8, "slice": 0.3, "wipe": 0.4}, "treat": "safe", "pack": "layoutsD", "fits": "011111111111111100000000000000000000000000000000000000000000"}, "flag": {"name": "Flag", "w": 0.8, "tags": ["emotional", "pop", "graphic"], "extra": true, "ae": "wave", "enterBias": {"cut": 1.6, "blur": 1.2, "pop": 0.6, "slice": 0.3, "wipe": 0.6}, "treat": "safe", "pack": "layoutsD", "fits": "111111111111111100000000000000000000000000000000000000000000"}, "ribbon": {"name": "Ribbon", "w": 0.9, "tags": ["pop", "emotional", "graphic"], "extra": true, "ae": "wave", "portrait": 0.7, "enterBias": {"cut": 1.8, "blur": 1.2, "pop": 0.8, "slice": 0.3, "stretch": 0.4}, "treat": "safe", "pack": "layoutsD", "fits": "011111111111111100000000000000000000000000000000000000000000"}, "pendulum": {"name": "Pendulum", "w": 0.8, "tags": ["calm", "pop", "emotional"], "extra": true, "ae": "labels", "portrait": 1.2, "enterBias": {"cut": 2, "drop": 1.4, "pop": 1.2, "blur": 0.8, "slice": 0.2, "wipe": 0.3, "stretch": 0.3}, "treat": "safe", "pack": "layoutsD", "fits": "111111111111111100000000000000000000000000000000000000000000"}, "pile": {"name": "Letter pile", "w": 0.8, "tags": ["pop", "emotional", "graphic"], "extra": true, "ae": "scatter", "enterBias": {"cut": 2.4, "drop": 1.2, "pop": 0.8, "slice": 0.2, "wipe": 0.2, "stretch": 0.3, "assemble": 0.4}, "pack": "layoutsD", "fits": "111111111111110000000000000000000000000000000000000000000000"}, "blocks": {"name": "Blocks", "w": 0.8, "tags": ["pop", "graphic"], "extra": true, "ae": "labels", "enterBias": {"cut": 2.5, "drop": 1.4, "pop": 1, "blur": 0.4, "slice": 0.2, "wipe": 0.2, "stretch": 0.3}, "treat": "safe", "pack": "layoutsD", "fits": "111111111111110000000000000000000000000000000000000000000000"}, "balloons": {"name": "Letter balloons", "w": 0.8, "tags": ["pop", "emotional", "calm"], "extra": true, "ae": "scatter", "enterBias": {"cut": 1.8, "pop": 1.5, "drop": 0.6, "blur": 0.8, "slice": 0.2, "wipe": 0.2, "assemble": 0.3}, "treat": false, "pack": "layoutsD", "fits": "111111111111110000000000000000000000000000000000000000000000"}, "magnets": {"name": "Magnets", "w": 0.7, "tags": ["pop", "graphic"], "extra": true, "ae": "scatter", "enterBias": {"cut": 2.5, "pop": 1.3, "drop": 0.8, "slice": 0.2, "wipe": 0.2, "stretch": 0.3}, "busy": true, "treat": false, "pack": "layoutsD", "fits": "111111111111110000000000000000000000000000000000000000000000"}, "tiles": {"name": "Letter tiles", "w": 0.7, "tags": ["pop", "editorial", "graphic"], "extra": true, "ae": "labels", "portrait": 0.7, "enterBias": {"cut": 3, "pop": 0.8, "blur": 0.4, "slice": 0.2, "wipe": 0.2, "stretch": 0.2}, "treat": "safe", "pack": "layoutsD", "fits": "111111111111110000000000000000000000000000000000000000000000"}, "bulbs": {"name": "Marquee bulbs", "w": 0.8, "tags": ["pop", "emotional", "graphic"], "extra": true, "ae": "center", "emph": 1.3, "enterBias": {"cut": 1.8, "flicker": 1.6, "pop": 1.2, "blur": 0.6, "slice": 0.3, "wipe": 0.4}, "treat": "safe", "pack": "layoutsD", "fits": "111111111111110000000000000000000000000000000000000000000000"}, "ledScroll": {"name": "LED ticker", "w": 0.8, "tags": ["pop", "graphic", "glitch"], "extra": true, "ae": "marquee", "portrait": 1.1, "enterBias": {"cut": 4, "flicker": 0.8, "type": 0.4, "blur": 0.2, "slice": 0.1, "wipe": 0.2, "assemble": 0.1, "scramble": 0.3}, "treat": false, "pack": "layoutsD", "fits": "111111111111111111111111000000000000000000000000000000000000"}, "billboard": {"name": "Billboard", "w": 0.8, "tags": ["pop", "emotional", "graphic"], "extra": true, "ae": "center", "enterBias": {"cut": 1.6, "flicker": 1.5, "blur": 1, "pop": 0.8, "slice": 0.4, "wipe": 0.6}, "treat": "safe", "pack": "layoutsD", "fits": "111111111111111100000000000000000000000000000000000000000000"}, "crowdBubbles": {"name": "Speech bubble crowd", "w": 0.8, "tags": ["pop", "emotional", "editorial"], "extra": true, "ae": "labels", "enterBias": {"cut": 1.8, "pop": 1.6, "blur": 0.8, "slice": 0.3, "wipe": 0.4}, "busy": true, "treat": "safe", "pack": "layoutsD", "fits": "111111111111111100000000000000000000000000000000000000000000"}, "crossword": {"name": "Crossword", "w": 0.7, "tags": ["editorial", "graphic", "pop"], "extra": true, "ae": "type", "portrait": 0.7, "enterBias": {"cut": 3, "type": 0.6, "flicker": 0.6, "blur": 0.4, "slice": 0.2, "wipe": 0.2, "stretch": 0.2}, "treat": "safe", "pack": "layoutsD", "fits": "011111111111111100000000000000000000000000000000000000000000"}, "wordSearch": {"name": "Word search", "w": 0.7, "tags": ["pop", "graphic", "editorial"], "extra": true, "ae": "tile", "enterBias": {"cut": 3, "flicker": 0.8, "blur": 0.6, "slice": 0.2, "wipe": 0.2, "stretch": 0.2, "assemble": 0.3}, "treat": "safe", "pack": "layoutsD", "fits": "011111111111110000000000000000000000000000000000000000000000"}, "puzzle": {"name": "Puzzle", "w": 0.8, "tags": ["pop", "graphic", "emotional"], "extra": true, "ae": "center", "enterBias": {"cut": 3, "blur": 0.5, "pop": 0.4, "slice": 0.2, "wipe": 0.2, "stretch": 0.2, "assemble": 0.2}, "treat": "safe", "pack": "layoutsD", "fits": "111111111111111100000000000000000000000000000000000000000000"}, "shadowPlay": {"name": "Shadow puppets", "w": 0.9, "tags": ["emotional", "calm", "graphic"], "extra": true, "ae": "stack", "enterBias": {"cut": 1.2, "blur": 1.3, "drop": 1.2, "pop": 0.6, "slice": 0.4}, "pack": "layoutsD", "fits": "111111111111110000000000000000000000000000000000000000000000"}, "kaleido": {"name": "Kaleidoscope", "w": 0.8, "tags": ["glitch", "emotional", "graphic"], "extra": true, "ae": "ring", "enterBias": {"cut": 1.4, "zoom": 1.4, "spin": 1.3, "blur": 1.2, "slice": 0.3, "wipe": 0.3}, "busy": true, "pack": "layoutsD", "fits": "111111111111110000000000000000000000000000000000000000000000"}, "dominoes": {"name": "Dominoes", "w": 0.7, "tags": ["pop", "graphic"], "extra": true, "ae": "labels", "portrait": 0.7, "enterBias": {"cut": 3, "blur": 0.5, "pop": 0.6, "slice": 0.2, "wipe": 0.2, "stretch": 0.2, "drop": 0.5}, "treat": "safe", "pack": "layoutsD", "fits": "011111111111110000000000000000000000000000000000000000000000"}, "burst": {"name": "Burst", "w": 0.8, "tags": ["pop", "graphic", "emotional"], "extra": true, "ae": "circle", "emph": 1.6, "enterBias": {"cut": 1.6, "pop": 1.6, "zoom": 1.4, "blur": 0.5, "slice": 0.3, "wipe": 0.3, "type": 0.3}, "treat": "safe", "pack": "layoutsD", "fits": "111111111111000000000000000000000000000000000000000000000000"}, "fisheye": {"name": "Fisheye lens", "w": 0.8, "tags": ["pop", "graphic", "glitch"], "extra": true, "ae": "mixed", "portrait": 0.7, "enterBias": {"cut": 1.4, "blur": 1.2, "pop": 1, "slice": 0.4, "wipe": 0.6, "type": 0.8}, "pack": "layoutsD", "fits": "001111111111111100000000000000000000000000000000000000000000"}, "wall": {"name": "Perspective wall", "w": 0.8, "tags": ["graphic", "editorial", "emotional"], "extra": true, "ae": "diag", "portrait": 0.6, "enterBias": {"cut": 1.6, "blur": 1.2, "wipe": 1, "slice": 0.5, "pop": 0.4}, "busy": true, "pack": "layoutsD", "fits": "111111111111111100000000000000000000000000000000000000000000"}, "origami": {"name": "Origami", "w": 0.8, "tags": ["calm", "pop", "emotional"], "extra": true, "wa": true, "ae": "circle", "enterBias": {"cut": 2.5, "blur": 0.8, "pop": 0.6, "slice": 0.2, "wipe": 0.3}, "treat": "safe", "pack": "layoutsD", "fits": "111111111111110000000000000000000000000000000000000000000000"}, "zipper": {"name": "Zipper", "w": 0.7, "tags": ["pop", "graphic", "emotional"], "extra": true, "ae": "center", "portrait": 1.1, "enterBias": {"cut": 2, "blur": 1, "pop": 0.8, "slice": 0.3, "wipe": 0.5}, "busy": true, "pack": "layoutsD", "fits": "111111111111110000000000000000000000000000000000000000000000"}, "sliceStack": {"name": "Sliced layers", "w": 0.9, "tags": ["glitch", "graphic", "pop"], "extra": true, "ae": "stack", "emph": 1.2, "enterBias": {"cut": 2, "blur": 0.8, "flicker": 0.8, "slice": 0.2, "wipe": 0.4}, "pack": "layoutsD", "fits": "111111111111110000000000000000000000000000000000000000000000"}, "glitchGrid": {"name": "Glitch grid", "w": 0.8, "tags": ["glitch", "graphic"], "extra": true, "ae": "tile", "enterBias": {"cut": 2.5, "flicker": 1.5, "scramble": 1.2, "blur": 0.4, "slice": 0.8, "wipe": 0.3}, "busy": true, "pack": "layoutsD", "fits": "111111111111110000000000000000000000000000000000000000000000"}, "mosaicTiles": {"name": "Mosaic tiles", "w": 0.7, "tags": ["pop", "graphic", "glitch"], "extra": true, "ae": "center", "enterBias": {"cut": 3, "flicker": 0.8, "blur": 0.3, "slice": 0.2, "wipe": 0.3, "assemble": 0.2}, "treat": false, "pack": "layoutsD", "fits": "111111111111000000000000000000000000000000000000000000000000"}, "maskReveal": {"name": "Text window", "w": 0.9, "tags": ["graphic", "pop", "emotional"], "extra": true, "ae": "huge", "emph": 1.3, "enterBias": {"cut": 1.4, "blur": 1.2, "wipe": 1.2, "slice": 0.8, "stretch": 0.8, "pop": 0.6, "assemble": 0.2}, "treat": false, "pack": "layoutsD", "fits": "111111111111000000000000000000000000000000000000000000000000"}, "contour": {"name": "Contour", "w": 0.9, "tags": ["calm", "graphic", "emotional"], "extra": true, "ae": "huge", "enterBias": {"blur": 1.3, "cut": 1.2, "wipe": 1, "slice": 0.6}, "pack": "layoutsD", "fits": "111111111111111100000000000000000000000000000000000000000000"}, "halftoneBig": {"name": "Oversized halftone text", "w": 0.9, "tags": ["pop", "graphic", "editorial"], "extra": true, "ae": "huge", "emph": 1.3, "enterBias": {"cut": 1.6, "blur": 1, "wipe": 1, "slice": 0.6, "stretch": 0.6, "assemble": 0.2}, "treat": false, "pack": "layoutsD", "fits": "111111111100000000000000000000000000000000000000000000000000"}, "stencil": {"name": "Stencil", "w": 0.8, "tags": ["graphic", "pop", "editorial"], "extra": true, "ae": "center", "enterBias": {"cut": 1.8, "wipe": 1.4, "blur": 0.8, "slice": 0.4, "pop": 0.4, "assemble": 0.2}, "treat": false, "pack": "layoutsD", "fits": "111111111111110000000000000000000000000000000000000000000000"}, "tyKeySplit": {"name": "Key Glyph Split", "w": 1.1, "tags": ["editorial", "graphic", "emotional"], "ae": "mixed", "emph": 1.4, "pack": "typo", "set": "typo", "fits": "011111111111111100000000000000000000000000000000000000000000"}, "tyCropGiant": {"name": "Cropped Giant", "w": 1, "tags": ["graphic", "editorial", "pop"], "ae": "huge", "pack": "typo", "set": "typo", "fits": "111111111111110000000000000000000000000000000000000000000000"}, "tyCross": {"name": "Cross Setting", "w": 0.9, "tags": ["graphic", "editorial"], "ae": "vcols", "pack": "typo", "set": "typo", "fits": "001111111110000000000000000000000000000000000000000000000000"}, "tyBandHide": {"name": "Band Cover", "w": 1, "tags": ["graphic", "pop", "editorial"], "ae": "diag", "pack": "typo", "set": "typo", "fits": "111111111111110000000000000000000000000000000000000000000000"}, "tyRuby": {"name": "Ruby Notes", "w": 1, "tags": ["editorial", "calm", "emotional"], "ae": "gloss", "pack": "typo", "set": "typo", "fits": "111111111111110000000000000000000000000000000000000000000000"}, "tyBaseline": {"name": "Ruled Lines", "w": 1.1, "tags": ["editorial", "calm", "graphic"], "ae": "type", "pack": "typo", "set": "typo", "fits": "011111111111111111000000000000000000000000000000000000000000"}, "tyScaleSteps": {"name": "Size Crescendo", "w": 1, "tags": ["graphic", "editorial", "pop"], "ae": "mixed", "pack": "typo", "set": "typo", "fits": "011111111111111100000000000000000000000000000000000000000000"}, "tyJustify": {"name": "Justified Stack", "w": 1.1, "tags": ["graphic", "pop", "editorial"], "ae": "stack", "pack": "typo", "set": "typo", "fits": "001111111111111100000000000000000000000000000000000000000000"}, "tyIndexTable": {"name": "Glyph Table", "w": 0.8, "tags": ["editorial", "graphic", "calm"], "ae": "gloss", "portrait": 1.3, "pack": "typo", "set": "typo", "fits": "011111111000000000000000000000000000000000000000000000000000"}, "tySplitType": {"name": "Split Type", "w": 1, "tags": ["graphic", "glitch", "pop"], "ae": "center", "pack": "typo", "set": "typo", "fits": "111111111111110000000000000000000000000000000000000000000000"}, "tyErode": {"name": "Eroding Repeat", "w": 0.9, "tags": ["emotional", "editorial", "calm"], "ae": "stack", "portrait": 0.6, "pack": "typo", "set": "typo", "fits": "001111111111000000000000000000000000000000000000000000000000"}, "tyVRuler": {"name": "Vertical Scale", "w": 0.9, "tags": ["editorial", "calm", "graphic"], "ae": "vcols", "portrait": 1.3, "pack": "typo", "set": "typo", "fits": "111111111111000000000000000000000000000000000000000000000000"}, "tyFullTrack": {"name": "Full-Width Tracking", "w": 1, "tags": ["editorial", "calm", "graphic"], "ae": "center", "pack": "typo", "set": "typo", "fits": "001111111111110000000000000000000000000000000000000000000000"}, "tyStatCount": {"name": "Glyph Count", "w": 0.8, "tags": ["editorial", "graphic"], "ae": "type", "pack": "typo", "set": "typo", "fits": "111111111111111100000000000000000000000000000000000000000000"}, "tyMargin": {"name": "White Space", "w": 0.8, "tags": ["calm", "editorial", "emotional"], "ae": "center", "emph": 0.4, "pack": "typo", "set": "typo", "fits": "111111111111111111000000000000000000000000000000000000000000"}, "tyRotBlock": {"name": "Rotated Block", "w": 1, "tags": ["graphic", "pop", "editorial"], "ae": "sideways", "pack": "typo", "set": "typo", "fits": "000111111111111100000000000000000000000000000000000000000000"}, "tySquare": {"name": "Square Setting", "w": 1, "tags": ["graphic", "editorial", "pop"], "ae": "gridCells", "pack": "typo", "set": "typo", "fits": "001111111111111100000000000000000000000000000000000000000000"}, "tyLineFocus": {"name": "Line Highlight", "w": 1, "tags": ["editorial", "emotional", "calm"], "ae": "center", "pack": "typo", "set": "typo", "fits": "111111111111111100000000000000000000000000000000000000000000"}}, "enter": {"cut": {"name": "Cut"}, "assemble": {"name": "Break apart and assemble", "tags": ["glitch", "calm", "editorial", "emotional"]}, "slice": {"name": "Slice", "tags": ["glitch", "graphic", "emotional"]}, "type": {"name": "Type", "tags": ["calm", "editorial", "horror"]}, "pop": {"name": "Pop", "tags": ["pop"]}, "drop": {"name": "Drop", "tags": ["pop"]}, "stretch": {"name": "Stretch", "tags": ["glitch", "pop", "graphic"]}, "wipe": {"name": "Wipe", "tags": ["calm", "graphic", "editorial", "emotional"]}, "blur": {"name": "Blur", "tags": ["calm", "editorial", "emotional", "horror"]}, "spin": {"name": "Spin", "tags": ["pop"]}, "flicker": {"name": "Flicker", "tags": ["glitch", "horror"]}, "scramble": {"name": "Scramble", "tags": ["glitch", "horror"]}, "zoom": {"name": "Zoom", "tags": ["glitch", "pop", "graphic", "emotional"]}, "riseMask": {"name": "Reveal from below", "w": 1.4, "tags": ["editorial", "graphic", "calm", "emotional"], "pack": "enter", "ae": "drop"}, "dropMask": {"name": "Reveal from above", "w": 1.2, "tags": ["editorial", "graphic", "pop"], "pack": "enter", "ae": "drop"}, "slideL": {"name": "Slide from left", "w": 1, "tags": ["editorial", "calm", "pop"], "pack": "enter", "ae": "wipe"}, "slideR": {"name": "Slide from right", "w": 1, "tags": ["editorial", "graphic", "pop"], "pack": "enter", "ae": "wipe"}, "slideWhole": {"name": "Slide in together", "w": 0.9, "tags": ["pop", "graphic"], "pack": "enter", "ae": "stretch"}, "flipX": {"name": "Flip on vertical axis", "w": 1, "tags": ["pop", "graphic", "editorial"], "pack": "enter", "ae": "spin"}, "flipY": {"name": "Flip on horizontal axis", "w": 0.9, "tags": ["pop", "graphic"], "pack": "enter", "ae": "spin"}, "domino": {"name": "Domino", "w": 0.7, "tags": ["pop"], "minDur": 0.5, "pack": "enter", "ae": "spin", "inDur": {"d": [0.15, 0.15, 0.168, 0.21, 0.252, 0.294, 0.336, 0.378, 0.42, 0.462, 0.504, 0.546, 0.588, 0.63, 0.672, 0.714, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75]}}, "fold": {"name": "Fold", "w": 0.9, "tags": ["graphic", "editorial"], "pack": "enter", "ae": "pop"}, "unroll": {"name": "Unroll", "w": 0.9, "tags": ["calm", "editorial", "emotional"], "pack": "enter", "ae": "wipe"}, "strokeDraw": {"name": "Draw outlines, then fill", "w": 1, "tags": ["calm", "emotional", "editorial"], "minDur": 0.8, "pack": "enter", "ae": "assemble", "inDur": {"d": [0.2, 0.2, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95]}}, "outlineFill": {"name": "Outline to fill", "w": 1, "tags": ["graphic", "pop", "editorial"], "minDur": 0.7, "pack": "enter", "ae": "blur", "inDur": {"d": [0.2, 0.2, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9]}}, "splitJoin": {"name": "Join from top and bottom", "w": 0.9, "tags": ["graphic", "editorial"], "pack": "enter", "ae": "slice"}, "vSlice": {"name": "Vertical slices", "w": 1, "tags": ["graphic", "glitch"], "pack": "enter", "ae": "slice"}, "shutter": {"name": "Shutter", "w": 1, "tags": ["graphic", "editorial"], "pack": "enter", "ae": "wipe"}, "iris": {"name": "Iris", "w": 0.8, "tags": ["pop", "emotional", "graphic"], "pack": "enter", "ae": "zoom"}, "diagWipe": {"name": "Diagonal wipe", "w": 1, "tags": ["graphic", "editorial", "pop"], "pack": "enter", "ae": "wipe"}, "blinds": {"name": "Blinds", "w": 0.8, "tags": ["graphic", "editorial"], "pack": "enter", "ae": "slice"}, "checker": {"name": "Checker", "w": 0.7, "tags": ["graphic", "glitch", "pop"], "pack": "enter", "ae": "flicker"}, "randomOrder": {"name": "Random letter order", "w": 1, "tags": ["pop", "glitch"], "pack": "enter", "ae": "flicker"}, "bounceBig": {"name": "Big bounce", "w": 0.7, "tags": ["pop"], "minDur": 0.7, "pack": "enter", "ae": "drop", "inDur": {"d": [0.2, 0.2, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95]}}, "squashDrop": {"name": "Squash landing", "w": 0.8, "tags": ["pop"], "minDur": 0.5, "pack": "enter", "ae": "drop", "inDur": {"d": [0.15, 0.15, 0.168, 0.21, 0.252, 0.294, 0.336, 0.378, 0.42, 0.462, 0.504, 0.546, 0.588, 0.63, 0.672, 0.714, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75]}}, "rubber": {"name": "Rubber", "w": 0.8, "tags": ["pop"], "minDur": 0.45, "pack": "enter", "ae": "stretch", "inDur": {"d": [0.15, 0.15, 0.168, 0.21, 0.252, 0.294, 0.336, 0.378, 0.42, 0.462, 0.504, 0.546, 0.588, 0.63, 0.672, 0.714, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75]}}, "glitchIn": {"name": "Glitch in", "w": 1.2, "tags": ["glitch"], "pack": "enter", "ae": "scramble"}, "echoIn": {"name": "Echoes converge", "w": 1, "tags": ["graphic", "emotional", "pop"], "pack": "enter", "ae": "zoom"}, "whip": {"name": "Whip", "w": 1, "tags": ["pop", "graphic"], "pack": "enter", "ae": "stretch", "inDur": {"d": [0.12, 0.12, 0.12, 0.15, 0.18, 0.21, 0.24, 0.27, 0.3, 0.33, 0.36, 0.39, 0.42, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45]}}, "skewIn": {"name": "Skew in", "w": 0.9, "tags": ["editorial", "graphic"], "pack": "enter", "ae": "stretch"}, "trackIn": {"name": "Tighten letter spacing", "w": 1.2, "tags": ["calm", "editorial", "emotional"], "pack": "enter", "ae": "blur"}, "trackOut": {"name": "Expand letter spacing", "w": 1, "tags": ["calm", "editorial", "pop"], "pack": "enter", "ae": "blur"}, "blurStagger": {"name": "Staggered blur", "w": 1.2, "tags": ["calm", "emotional"], "pack": "enter", "ae": "blur"}, "fadeStagger": {"name": "Letter by letter fade", "w": 1.2, "tags": ["calm", "emotional"], "pack": "enter", "ae": "blur", "inDur": {"d": [0.15, 0.15, 0.18, 0.225, 0.27, 0.315, 0.36, 0.405, 0.45, 0.495, 0.54, 0.585, 0.63, 0.675, 0.72, 0.765, 0.81, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85]}}, "waveIn": {"name": "Wave in", "w": 0.9, "tags": ["pop", "emotional"], "minDur": 0.5, "pack": "enter", "ae": "pop", "inDur": {"d": [0.15, 0.15, 0.18, 0.225, 0.27, 0.315, 0.36, 0.405, 0.45, 0.495, 0.54, 0.585, 0.63, 0.675, 0.72, 0.765, 0.81, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85]}}, "spiralIn": {"name": "Spiral assembly", "w": 0.7, "tags": ["pop", "emotional"], "minDur": 0.5, "pack": "enter", "ae": "spin", "inDur": {"d": [0.15, 0.15, 0.168, 0.21, 0.252, 0.294, 0.336, 0.378, 0.42, 0.462, 0.504, 0.546, 0.588, 0.63, 0.672, 0.714, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75]}}, "zoomOut": {"name": "Oversized to normal", "w": 1, "tags": ["pop", "graphic", "glitch"], "pack": "enter", "ae": "zoom"}, "resolve": {"name": "Resolve", "w": 0.9, "tags": ["glitch", "editorial"], "minDur": 0.6, "pack": "enter", "ae": "scramble", "inDur": {"n": [1, 2, 3, 4, 6, 8, 12, 16, 24], "d": [[0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3], [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3], [0.3, 0.3, 0.3, 0.3, 0.33, 0.335, 0.335, 0.335, 0.335, 0.335, 0.335, 0.335, 0.335, 0.335, 0.335, 0.335, 0.335, 0.335, 0.335, 0.335, 0.335, 0.335, 0.335, 0.335, 0.335, 0.335, 0.335, 0.335, 0.335, 0.335, 0.335, 0.335, 0.335, 0.335, 0.335, 0.335, 0.335, 0.335, 0.335], [0.3, 0.3, 0.3, 0.3, 0.33, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38], [0.3, 0.3, 0.3, 0.3, 0.33, 0.385, 0.44, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47], [0.3, 0.3, 0.3, 0.3, 0.33, 0.385, 0.44, 0.495, 0.55, 0.56, 0.56, 0.56, 0.56, 0.56, 0.56, 0.56, 0.56, 0.56, 0.56, 0.56, 0.56, 0.56, 0.56, 0.56, 0.56, 0.56, 0.56, 0.56, 0.56, 0.56, 0.56, 0.56, 0.56, 0.56, 0.56, 0.56, 0.56, 0.56, 0.56], [0.3, 0.3, 0.3, 0.3, 0.33, 0.385, 0.44, 0.495, 0.55, 0.605, 0.66, 0.715, 0.74, 0.74, 0.74, 0.74, 0.74, 0.74, 0.74, 0.74, 0.74, 0.74, 0.74, 0.74, 0.74, 0.74, 0.74, 0.74, 0.74, 0.74, 0.74, 0.74, 0.74, 0.74, 0.74, 0.74, 0.74, 0.74, 0.74], [0.3, 0.3, 0.3, 0.3, 0.33, 0.385, 0.44, 0.495, 0.55, 0.605, 0.66, 0.715, 0.77, 0.825, 0.88, 0.92, 0.92, 0.92, 0.92, 0.92, 0.92, 0.92, 0.92, 0.92, 0.92, 0.92, 0.92, 0.92, 0.92, 0.92, 0.92, 0.92, 0.92, 0.92, 0.92, 0.92, 0.92, 0.92, 0.92], [0.3, 0.3, 0.3, 0.3, 0.33, 0.385, 0.44, 0.495, 0.55, 0.605, 0.66, 0.715, 0.77, 0.825, 0.88, 0.935, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95]]}}, "magnet": {"name": "Magnet", "w": 0.8, "tags": ["pop", "graphic"], "minDur": 0.6, "pack": "enter", "ae": "assemble", "inDur": {"d": [0.2, 0.2, 0.2, 0.24, 0.288, 0.336, 0.384, 0.432, 0.48, 0.528, 0.576, 0.624, 0.672, 0.72, 0.768, 0.816, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85]}}, "inkBleed": {"name": "Ink bleed", "w": 1, "tags": ["calm", "emotional"], "pack": "enter", "ae": "blur", "inDur": {"d": [0.15, 0.15, 0.18, 0.225, 0.27, 0.315, 0.36, 0.405, 0.45, 0.495, 0.54, 0.585, 0.63, 0.675, 0.72, 0.765, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8]}}, "neonOn": {"name": "Neon on", "w": 0.8, "tags": ["glitch", "emotional", "pop"], "minDur": 0.6, "pack": "enter", "ae": "flicker", "inDur": {"d": [0.2, 0.2, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9]}}, "cursorSweep": {"name": "Cursor sweep", "w": 1, "tags": ["editorial", "graphic"], "pack": "enter", "ae": "type", "inDur": {"d": [0.14, 0.14, 0.16, 0.2, 0.24, 0.28, 0.32, 0.36, 0.4, 0.44, 0.48, 0.52, 0.56, 0.6, 0.64, 0.68, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7]}}, "stamp": {"name": "Stamp", "w": 0.9, "tags": ["pop", "graphic"], "pack": "enter", "ae": "zoom", "inDur": {"d": [0.14, 0.14, 0.16, 0.2, 0.24, 0.28, 0.32, 0.36, 0.4, 0.44, 0.48, 0.52, 0.56, 0.6, 0.64, 0.68, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7]}}, "springIn": {"name": "Spring in", "w": 0.9, "tags": ["pop"], "extra": true, "ae": "drop", "minDur": 0.5, "pack": "enterB", "inDur": {"d": [0.2, 0.2, 0.2, 0.225, 0.27, 0.315, 0.36, 0.405, 0.45, 0.495, 0.54, 0.585, 0.63, 0.675, 0.72, 0.765, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8]}}, "pendulum": {"name": "Pendulum", "w": 0.9, "tags": ["pop", "emotional", "calm"], "extra": true, "ae": "spin", "minDur": 0.5, "pack": "enterB", "inDur": {"d": [0.22, 0.22, 0.22, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9]}}, "rollIn": {"name": "Roll in", "w": 0.9, "tags": ["pop", "graphic"], "extra": true, "ae": "spin", "pack": "enterB"}, "slingshot": {"name": "Slingshot", "w": 0.7, "tags": ["pop", "graphic"], "extra": true, "ae": "stretch", "minDur": 0.55, "pack": "enterB", "inDur": {"d": [0.25, 0.25, 0.25, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9]}}, "rockSettle": {"name": "Wobble to rest", "w": 0.8, "tags": ["pop"], "extra": true, "ae": "drop", "minDur": 0.55, "pack": "enterB", "inDur": {"d": [0.22, 0.22, 0.22, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9]}}, "bounceBall": {"name": "Bounce ball", "w": 0.8, "tags": ["pop", "emotional"], "extra": true, "ae": "pop", "minDur": 0.8, "maxChars": 20, "pack": "enterB", "inDur": {"n": [1, 2, 3, 4, 6, 8, 12, 16, 24], "d": [[0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45], [0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45], [0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48], [0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.48, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54], [0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.48, 0.54, 0.6, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66], [0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.48, 0.54, 0.6, 0.66, 0.72, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78], [0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.48, 0.54, 0.6, 0.66, 0.72, 0.78, 0.84, 0.9, 0.96, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02], [0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.48, 0.54, 0.6, 0.66, 0.72, 0.78, 0.84, 0.9, 0.96, 1.02, 1.08, 1.14, 1.2, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25], [0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.48, 0.54, 0.6, 0.66, 0.72, 0.78, 0.84, 0.9, 0.96, 1.02, 1.08, 1.14, 1.2, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25]]}}, "snapRail": {"name": "Snap into alignment", "w": 0.9, "tags": ["graphic", "pop", "editorial"], "extra": true, "ae": "assemble", "minDur": 0.55, "pack": "enterB", "inDur": {"d": [0.25, 0.25, 0.25, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9]}}, "fanOpen": {"name": "Folding fan opens", "w": 0.9, "tags": ["emotional", "graphic", "calm"], "extra": true, "wa": true, "ae": "spin", "pack": "enterB", "inDur": {"d": [0.2, 0.2, 0.2, 0.225, 0.27, 0.315, 0.36, 0.405, 0.45, 0.495, 0.54, 0.585, 0.63, 0.675, 0.72, 0.765, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8]}}, "cylinder": {"name": "Cylinder", "w": 0.9, "tags": ["graphic", "pop", "editorial"], "extra": true, "ae": "spin", "pack": "enterB"}, "shuffle": {"name": "Shuffle", "w": 0.9, "tags": ["pop", "glitch", "graphic"], "extra": true, "ae": "scramble", "minDur": 0.55, "pack": "enterB", "inDur": {"d": [0.25, 0.25, 0.25, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9]}}, "stopMotion": {"name": "Stop motion", "w": 0.8, "tags": ["pop", "emotional"], "extra": true, "ae": "pop", "minDur": 0.5, "pack": "enterB", "inDur": {"d": [0.25, 0.25, 0.25, 0.25, 0.27, 0.315, 0.36, 0.405, 0.45, 0.495, 0.54, 0.585, 0.63, 0.675, 0.72, 0.765, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8]}}, "ripple": {"name": "Ripple", "w": 1, "tags": ["emotional", "calm", "graphic"], "extra": true, "ae": "pop", "pack": "enterB"}, "zipper": {"name": "Zipper", "w": 0.8, "tags": ["graphic", "pop"], "extra": true, "ae": "slice", "pack": "enterB", "inDur": {"d": [0.18, 0.18, 0.18, 0.21, 0.252, 0.294, 0.336, 0.378, 0.42, 0.462, 0.504, 0.546, 0.588, 0.63, 0.672, 0.714, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75]}}, "zoomAlt": {"name": "Zoom alt", "w": 0.9, "tags": ["pop", "graphic", "glitch"], "extra": true, "ae": "zoom", "pack": "enterB"}, "tiltUp": {"name": "Tilt up", "w": 0.9, "tags": ["graphic", "editorial", "pop"], "extra": true, "ae": "stretch", "pack": "enterB"}, "stickerPeel": {"name": "Apply sticker", "w": 0.9, "tags": ["pop", "graphic", "editorial"], "extra": true, "ae": "wipe", "minDur": 0.5, "pack": "enterB", "inDur": {"d": [0.2, 0.2, 0.2, 0.225, 0.27, 0.315, 0.36, 0.405, 0.45, 0.495, 0.54, 0.585, 0.63, 0.675, 0.72, 0.765, 0.81, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85]}}, "crumple": {"name": "Uncrumple", "w": 0.7, "tags": ["pop", "graphic", "emotional"], "extra": true, "ae": "assemble", "minDur": 0.5, "pack": "enterB", "inDur": {"d": [0.22, 0.22, 0.22, 0.225, 0.27, 0.315, 0.36, 0.405, 0.45, 0.495, 0.54, 0.585, 0.63, 0.675, 0.72, 0.765, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8]}}, "noteUnfold": {"name": "Unfold letter", "w": 0.8, "tags": ["emotional", "calm", "graphic"], "extra": true, "ae": "pop", "minDur": 0.6, "pack": "enterB", "inDur": {"d": [0.25, 0.25, 0.25, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9]}}, "tornJoin": {"name": "Join torn pieces", "w": 0.8, "tags": ["graphic", "emotional", "pop"], "extra": true, "ae": "slice", "pack": "enterB", "inDur": {"d": [0.18, 0.18, 0.18, 0.21, 0.252, 0.294, 0.336, 0.378, 0.42, 0.462, 0.504, 0.546, 0.588, 0.63, 0.672, 0.714, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75]}}, "splitFlap": {"name": "Split flap display", "w": 0.9, "tags": ["glitch", "editorial", "graphic"], "extra": true, "ae": "scramble", "minDur": 0.75, "pack": "enterB", "inDur": {"n": [1, 2, 3, 4, 6, 8, 12, 16, 24], "d": [[0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45], [0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45], [0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45], [0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47, 0.47], [0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.495, 0.53, 0.53, 0.53, 0.53, 0.53, 0.53, 0.53, 0.53, 0.53, 0.53, 0.53, 0.53, 0.53, 0.53, 0.53, 0.53, 0.53, 0.53, 0.53, 0.53, 0.53, 0.53, 0.53, 0.53, 0.53, 0.53, 0.53, 0.53, 0.53, 0.53, 0.53], [0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.495, 0.55, 0.59, 0.59, 0.59, 0.59, 0.59, 0.59, 0.59, 0.59, 0.59, 0.59, 0.59, 0.59, 0.59, 0.59, 0.59, 0.59, 0.59, 0.59, 0.59, 0.59, 0.59, 0.59, 0.59, 0.59, 0.59, 0.59, 0.59, 0.59, 0.59, 0.59], [0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.495, 0.55, 0.605, 0.66, 0.71, 0.71, 0.71, 0.71, 0.71, 0.71, 0.71, 0.71, 0.71, 0.71, 0.71, 0.71, 0.71, 0.71, 0.71, 0.71, 0.71, 0.71, 0.71, 0.71, 0.71, 0.71, 0.71, 0.71, 0.71, 0.71, 0.71, 0.71], [0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.495, 0.55, 0.605, 0.66, 0.715, 0.77, 0.825, 0.83, 0.83, 0.83, 0.83, 0.83, 0.83, 0.83, 0.83, 0.83, 0.83, 0.83, 0.83, 0.83, 0.83, 0.83, 0.83, 0.83, 0.83, 0.83, 0.83, 0.83, 0.83, 0.83, 0.83, 0.83], [0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.495, 0.55, 0.605, 0.66, 0.715, 0.77, 0.825, 0.88, 0.935, 0.99, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]}}, "overexpose": {"name": "Overexpose", "w": 1, "tags": ["emotional", "pop", "glitch"], "extra": true, "ae": "blur", "pack": "enterB", "inDur": {"d": [0.18, 0.18, 0.18, 0.21, 0.252, 0.294, 0.336, 0.378, 0.42, 0.462, 0.504, 0.546, 0.588, 0.63, 0.672, 0.714, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75]}}, "glint": {"name": "Glint", "w": 1, "tags": ["editorial", "emotional", "graphic"], "extra": true, "ae": "wipe", "pack": "enterB", "inDur": {"d": [0.2, 0.2, 0.2, 0.225, 0.27, 0.315, 0.36, 0.405, 0.45, 0.495, 0.54, 0.585, 0.63, 0.675, 0.72, 0.765, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8]}}, "loupe": {"name": "Loupe", "w": 0.8, "tags": ["editorial", "pop", "calm"], "extra": true, "ae": "zoom", "minDur": 0.6, "pack": "enterB", "inDur": {"d": [0.25, 0.25, 0.25, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9]}}, "filmFeed": {"name": "Film feed", "w": 0.8, "tags": ["emotional", "glitch", "editorial"], "extra": true, "ae": "flicker", "minDur": 0.55, "pack": "enterB", "inDur": {"d": [0.22, 0.22, 0.22, 0.24, 0.288, 0.336, 0.384, 0.432, 0.48, 0.528, 0.576, 0.624, 0.672, 0.72, 0.768, 0.816, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85]}}, "backlight": {"name": "Backlight", "w": 0.9, "tags": ["emotional", "calm"], "extra": true, "ae": "blur", "minDur": 0.5, "pack": "enterB", "inDur": {"d": [0.22, 0.22, 0.22, 0.24, 0.288, 0.336, 0.384, 0.432, 0.48, 0.528, 0.576, 0.624, 0.672, 0.72, 0.768, 0.816, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85]}}, "lightLeak": {"name": "Light leak", "w": 0.9, "tags": ["emotional", "calm", "pop"], "extra": true, "ae": "blur", "pack": "enterB", "inDur": {"d": [0.25, 0.25, 0.25, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9]}}, "heatHaze": {"name": "Heat haze", "w": 0.9, "tags": ["emotional", "calm", "glitch"], "extra": true, "ae": "slice", "pack": "enterB", "inDur": {"d": [0.2, 0.2, 0.2, 0.225, 0.27, 0.315, 0.36, 0.405, 0.45, 0.495, 0.54, 0.585, 0.63, 0.675, 0.72, 0.765, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8]}}, "crtOn": {"name": "CRT powers on", "w": 0.9, "tags": ["glitch", "pop", "graphic"], "extra": true, "ae": "stretch", "pack": "enterB"}, "interlace": {"name": "Interlace", "w": 0.9, "tags": ["glitch", "graphic", "editorial"], "extra": true, "ae": "slice", "pack": "enterB"}, "loadingBar": {"name": "Loading bar", "w": 0.8, "tags": ["graphic", "editorial", "glitch"], "extra": true, "ae": "wipe", "minDur": 0.7, "pack": "enterB", "inDur": {"d": [0.3, 0.3, 0.3, 0.3, 0.33, 0.385, 0.44, 0.495, 0.55, 0.605, 0.66, 0.715, 0.77, 0.825, 0.88, 0.935, 0.99, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}}, "dither": {"name": "Dither", "w": 0.9, "tags": ["glitch", "graphic"], "extra": true, "ae": "flicker", "pack": "enterB"}, "odometer": {"name": "Rolling number drum", "w": 0.9, "tags": ["glitch", "editorial", "pop"], "extra": true, "ae": "scramble", "minDur": 0.55, "pack": "enterB", "inDur": {"d": [0.25, 0.25, 0.25, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9]}}, "matrixRain": {"name": "Data rain", "w": 0.8, "tags": ["glitch", "graphic"], "extra": true, "ae": "scramble", "minDur": 0.55, "pack": "enterB", "inDur": {"d": [0.25, 0.25, 0.25, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9]}}, "hatchFill": {"name": "Hatch fill", "w": 0.9, "tags": ["graphic", "editorial"], "extra": true, "ae": "blur", "pack": "enterB", "inDur": {"d": [0.2, 0.2, 0.2, 0.225, 0.27, 0.315, 0.36, 0.405, 0.45, 0.495, 0.54, 0.585, 0.63, 0.675, 0.72, 0.765, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8]}}, "brushReveal": {"name": "Brush reveal", "w": 1, "tags": ["emotional", "editorial", "calm"], "extra": true, "wa": true, "ae": "wipe", "pack": "enterB", "inDur": {"d": [0.2, 0.2, 0.2, 0.225, 0.27, 0.315, 0.36, 0.405, 0.45, 0.495, 0.54, 0.585, 0.63, 0.675, 0.72, 0.765, 0.81, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85]}}, "inkDrop": {"name": "Ink drop", "w": 1, "tags": ["calm", "emotional", "graphic"], "extra": true, "ae": "blur", "pack": "enterB", "inDur": {"d": [0.2, 0.2, 0.2, 0.225, 0.27, 0.315, 0.36, 0.405, 0.45, 0.495, 0.54, 0.585, 0.63, 0.675, 0.72, 0.765, 0.81, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85]}}, "quarters": {"name": "Converge from four sides", "w": 0.9, "tags": ["graphic", "pop"], "extra": true, "ae": "assemble", "pack": "enterB"}, "invertBox": {"name": "Invert box", "w": 0.9, "tags": ["graphic", "editorial", "pop"], "extra": true, "ae": "wipe", "pack": "enterB", "inDur": {"d": [0.2, 0.2, 0.2, 0.225, 0.27, 0.315, 0.36, 0.405, 0.45, 0.495, 0.54, 0.585, 0.63, 0.675, 0.72, 0.765, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8]}}, "printRegister": {"name": "Color plates align", "w": 0.9, "tags": ["graphic", "pop", "editorial"], "extra": true, "ae": "slice", "pack": "enterB", "inDur": {"d": [0.2, 0.2, 0.2, 0.225, 0.27, 0.315, 0.36, 0.405, 0.45, 0.495, 0.54, 0.585, 0.63, 0.675, 0.72, 0.765, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8]}}, "echoCount": {"name": "Count in", "w": 0.8, "tags": ["pop", "graphic", "glitch"], "extra": true, "ae": "zoom", "minDur": 0.55, "pack": "enterB", "inDur": {"d": [0.25, 0.25, 0.25, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9]}}, "liquidFill": {"name": "Liquid rises", "w": 0.9, "tags": ["emotional", "calm", "pop"], "extra": true, "ae": "wipe", "minDur": 0.5, "pack": "enterB", "inDur": {"d": [0.25, 0.25, 0.25, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9]}}, "windBlown": {"name": "Wind blown", "w": 0.8, "tags": ["emotional", "calm"], "extra": true, "ae": "assemble", "minDur": 0.55, "pack": "enterB", "inDur": {"d": [0.25, 0.25, 0.25, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9]}}, "strokeOrder": {"name": "Stroke by stroke", "w": 0.9, "tags": ["calm", "emotional", "editorial"], "extra": true, "ae": "assemble", "minDur": 0.6, "pack": "enterB", "inDur": {"n": [1, 2, 3, 4, 6, 8, 12, 16, 24], "d": [[0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4], [0.4, 0.4, 0.4, 0.4, 0.4, 0.42, 0.42, 0.42, 0.42, 0.42, 0.42, 0.42, 0.42, 0.42, 0.42, 0.42, 0.42, 0.42, 0.42, 0.42, 0.42, 0.42, 0.42, 0.42, 0.42, 0.42, 0.42, 0.42, 0.42, 0.42, 0.42, 0.42, 0.42, 0.42, 0.42, 0.42, 0.42, 0.42, 0.42], [0.4, 0.4, 0.4, 0.4, 0.4, 0.42, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48], [0.4, 0.4, 0.4, 0.4, 0.4, 0.42, 0.48, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54, 0.54], [0.4, 0.4, 0.4, 0.4, 0.4, 0.42, 0.48, 0.54, 0.6, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66, 0.66], [0.4, 0.4, 0.4, 0.4, 0.4, 0.42, 0.48, 0.54, 0.6, 0.66, 0.72, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78, 0.78], [0.4, 0.4, 0.4, 0.4, 0.4, 0.42, 0.48, 0.54, 0.6, 0.66, 0.72, 0.78, 0.84, 0.9, 0.96, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02, 1.02], [0.4, 0.4, 0.4, 0.4, 0.4, 0.42, 0.48, 0.54, 0.6, 0.66, 0.72, 0.78, 0.84, 0.9, 0.96, 1.02, 1.08, 1.14, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2], [0.4, 0.4, 0.4, 0.4, 0.4, 0.42, 0.48, 0.54, 0.6, 0.66, 0.72, 0.78, 0.84, 0.9, 0.96, 1.02, 1.08, 1.14, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2]]}}, "clockWipe": {"name": "Clock wipe", "w": 0.9, "tags": ["graphic", "pop", "editorial"], "extra": true, "ae": "wipe", "pack": "enterB"}, "shadowFirst": {"name": "Shadow first", "w": 0.9, "tags": ["pop", "graphic", "emotional"], "extra": true, "ae": "drop", "minDur": 0.5, "pack": "enterB", "inDur": {"d": [0.22, 0.22, 0.22, 0.24, 0.288, 0.336, 0.384, 0.432, 0.48, 0.528, 0.576, 0.624, 0.672, 0.72, 0.768, 0.816, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85]}}, "bubbles": {"name": "Bubbles", "w": 0.8, "tags": ["pop", "emotional", "calm"], "extra": true, "ae": "pop", "minDur": 0.55, "pack": "enterB", "inDur": {"d": [0.25, 0.25, 0.25, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9]}}, "tokoroten": {"name": "Extruded noodles", "w": 0.7, "tags": ["pop", "graphic"], "extra": true, "ae": "stretch", "minDur": 0.5, "pack": "enterB", "inDur": {"d": [0.22, 0.22, 0.22, 0.24, 0.288, 0.336, 0.384, 0.432, 0.48, 0.528, 0.576, 0.624, 0.672, 0.72, 0.768, 0.816, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85]}}, "hrBlinkCreep": {"name": "Between Blinks", "w": 0.9, "tags": ["horror", "emotional"], "ae": "flicker", "minDur": 1, "pack": "horror", "set": "horror", "inDur": {"d": [0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 1, 1.05, 1.1, 1.15, 1.2, 1.25, 1.3, 1.3, 1.3, 1.3, 1.3, 1.3, 1.3, 1.3, 1.3, 1.3, 1.3, 1.3, 1.3, 1.3, 1.3]}}, "hrJumpScare": {"name": "Jump Scare", "w": 0.7, "tags": ["horror", "glitch", "pop"], "ae": "zoom", "minDur": 0.9, "pack": "horror", "set": "horror", "inDur": {"d": [0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.462, 0.504, 0.546, 0.588, 0.63, 0.672, 0.714, 0.756, 0.798, 0.84, 0.882, 0.924, 0.966, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}}, "hrUneasy": {"name": "Uneasy Timing", "w": 1, "tags": ["horror", "editorial"], "ae": "type", "minDur": 0.9, "pack": "horror", "set": "horror", "inDur": {"n": [1, 2, 3, 4, 6, 8, 12, 16, 24], "d": [[0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.55, 0.605, 0.66, 0.715, 0.77, 0.825, 0.88, 0.935, 0.99, 1.045, 1.1, 1.155, 1.21, 1.265, 1.32, 1.375, 1.43, 1.485, 1.54, 1.595, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6], [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.55, 0.605, 0.66, 0.715, 0.77, 0.825, 0.88, 0.935, 0.99, 1.045, 1.1, 1.155, 1.21, 1.265, 1.32, 1.375, 1.43, 1.485, 1.54, 1.595, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6], [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.55, 0.605, 0.66, 0.715, 0.77, 0.825, 0.88, 0.935, 0.99, 1.045, 1.1, 1.155, 1.21, 1.265, 1.32, 1.375, 1.43, 1.485, 1.54, 1.595, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6], [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.55, 0.605, 0.66, 0.715, 0.77, 0.825, 0.88, 0.935, 0.99, 1.045, 1.1, 1.155, 1.21, 1.265, 1.32, 1.375, 1.43, 1.485, 1.54, 1.595, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6], [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.55, 0.605, 0.66, 0.715, 0.77, 0.825, 0.88, 0.935, 0.99, 1.045, 1.1, 1.155, 1.21, 1.265, 1.32, 1.375, 1.43, 1.485, 1.54, 1.595, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6], [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.55, 0.605, 0.66, 0.715, 0.77, 0.825, 0.88, 0.935, 0.99, 1.045, 1.1, 1.155, 1.21, 1.265, 1.32, 1.375, 1.43, 1.485, 1.54, 1.595, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6], [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.55, 0.605, 0.66, 0.715, 0.77, 0.825, 0.88, 0.935, 0.99, 1.045, 1.1, 1.155, 1.21, 1.265, 1.32, 1.375, 1.43, 1.485, 1.54, 1.595, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6], [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.55, 0.605, 0.66, 0.715, 0.77, 0.825, 0.88, 0.935, 0.99, 1.045, 1.1, 1.155, 1.21, 1.265, 1.32, 1.375, 1.43, 1.485, 1.54, 1.595, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6], [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.55, 0.605, 0.66, 0.715, 0.77, 0.825, 0.88, 0.935, 0.99, 1.045, 1.1, 1.155, 1.21, 1.265, 1.32, 1.375, 1.43, 1.485, 1.54, 1.595, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6, 1.6]]}}, "hrVhold": {"name": "Vertical Hold", "w": 0.8, "tags": ["horror", "glitch"], "ae": "slice", "pack": "horror", "set": "horror", "inDur": {"d": [0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.36, 0.4, 0.44, 0.48, 0.52, 0.56, 0.6, 0.64, 0.68, 0.72, 0.76, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8]}}, "hrMirrorSnap": {"name": "Mirror Writing", "w": 0.8, "tags": ["horror", "glitch"], "ae": "flicker", "minDur": 0.8, "pack": "horror", "set": "horror", "inDur": {"d": [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.54, 0.585, 0.63, 0.675, 0.72, 0.765, 0.81, 0.855, 0.9, 0.945, 0.99, 1.035, 1.08, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1]}}, "hrManifest": {"name": "Manifest", "w": 1, "tags": ["horror", "emotional", "calm"], "ae": "blur", "minDur": 0.8, "pack": "horror", "set": "horror", "inDur": {"d": [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 1, 1.05, 1.1, 1.15, 1.2, 1.25, 1.3, 1.35, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4]}}, "hrClawReveal": {"name": "Claw Marks", "w": 0.8, "tags": ["horror", "graphic"], "ae": "wipe", "pack": "horror", "set": "horror", "inDur": {"d": [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.324, 0.36, 0.396, 0.432, 0.468, 0.504, 0.54, 0.576, 0.612, 0.648, 0.684, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7]}}, "knWordSlam": {"name": "Word Slam", "w": 1.1, "tags": ["pop", "graphic", "glitch"], "ae": "pop", "minDur": 0.5, "pack": "kinetic", "set": "kinetic", "inDur": {"d": [0.32, 0.32, 0.32, 0.32, 0.32, 0.32, 0.36, 0.405, 0.45, 0.495, 0.54, 0.585, 0.63, 0.675, 0.72, 0.765, 0.81, 0.855, 0.9, 0.945, 0.99, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}}, "knTypeToSlam": {"name": "Type and Grow", "w": 0.9, "tags": ["pop", "editorial", "graphic"], "ae": "type", "minDur": 0.55, "pack": "kinetic", "set": "kinetic", "inDur": {"d": [0.32, 0.32, 0.32, 0.32, 0.32, 0.32, 0.36, 0.405, 0.45, 0.495, 0.54, 0.585, 0.63, 0.675, 0.72, 0.765, 0.81, 0.855, 0.9, 0.945, 0.99, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}}, "knReplaceIn": {"name": "Swap In", "w": 0.9, "tags": ["pop", "graphic", "glitch"], "ae": "scramble", "minDur": 0.7, "pack": "kinetic", "set": "kinetic", "inDur": {"d": [0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 1, 1.05, 1.1, 1.15, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2]}}, "knHingeDrop": {"name": "Hinge Drop", "w": 0.9, "tags": ["pop", "graphic"], "ae": "drop", "minDur": 0.5, "pack": "kinetic", "set": "kinetic", "inDur": {"d": [0.32, 0.32, 0.32, 0.32, 0.32, 0.32, 0.36, 0.405, 0.45, 0.495, 0.54, 0.585, 0.63, 0.675, 0.72, 0.765, 0.81, 0.855, 0.9, 0.945, 0.99, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}}, "knLoopIn": {"name": "Loop In", "w": 0.8, "tags": ["pop", "graphic"], "ae": "spin", "minDur": 0.5, "pack": "kinetic", "set": "kinetic", "inDur": {"d": [0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 1, 1.05, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1]}}, "knPushIn": {"name": "Push In", "w": 1, "tags": ["pop", "editorial", "graphic"], "ae": "type", "minDur": 0.45, "pack": "kinetic", "set": "kinetic", "inDur": {"d": [0.32, 0.32, 0.32, 0.32, 0.32, 0.32, 0.36, 0.405, 0.45, 0.495, 0.54, 0.585, 0.63, 0.675, 0.72, 0.765, 0.81, 0.855, 0.9, 0.945, 0.99, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}}, "knInertia": {"name": "Hard Brake", "w": 0.9, "tags": ["pop", "graphic"], "ae": "stretch", "minDur": 0.45, "pack": "kinetic", "set": "kinetic", "inDur": {"d": [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.32, 0.36, 0.4, 0.44, 0.48, 0.52, 0.56, 0.6, 0.64, 0.68, 0.72, 0.76, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8]}}, "knWordSpin": {"name": "Word Spin", "w": 0.9, "tags": ["pop", "graphic"], "ae": "spin", "pack": "kinetic", "set": "kinetic", "inDur": {"d": [0.32, 0.32, 0.32, 0.32, 0.32, 0.32, 0.36, 0.405, 0.45, 0.495, 0.54, 0.585, 0.63, 0.675, 0.72, 0.765, 0.81, 0.855, 0.9, 0.945, 0.99, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}}, "knDiveIn": {"name": "Words From Camera", "w": 0.9, "tags": ["pop", "emotional", "graphic"], "ae": "zoom", "pack": "kinetic", "set": "kinetic", "inDur": {"d": [0.32, 0.32, 0.32, 0.32, 0.32, 0.32, 0.36, 0.405, 0.45, 0.495, 0.54, 0.585, 0.63, 0.675, 0.72, 0.765, 0.81, 0.855, 0.9, 0.945, 0.99, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}}, "knStretchOut": {"name": "Tape Stretch", "w": 0.9, "tags": ["pop", "graphic"], "ae": "stretch", "pack": "kinetic", "set": "kinetic", "inDur": {"d": [0.32, 0.32, 0.32, 0.32, 0.32, 0.32, 0.36, 0.405, 0.45, 0.495, 0.54, 0.585, 0.63, 0.675, 0.72, 0.765, 0.81, 0.855, 0.9, 0.945, 0.99, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}}, "tyKeyFirst": {"name": "Key Glyph First", "w": 1, "tags": ["pop", "graphic", "editorial", "emotional"], "ae": "pop", "pack": "typo", "set": "typo"}, "tyLineWipe": {"name": "Line-by-Line Wipe", "w": 1, "tags": ["editorial", "graphic", "calm"], "ae": "wipe", "pack": "typo", "set": "typo"}, "tyZoomOne": {"name": "One by One Zoom", "w": 0.8, "tags": ["pop", "graphic", "emotional"], "ae": "zoom", "minDur": 1, "pack": "typo", "set": "typo", "inDur": {"n": [1, 2, 3, 4, 6, 8, 12, 16, 24], "d": [[0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4], [0.11, 0.165, 0.22, 0.275, 0.33, 0.385, 0.44, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48], [0.11, 0.165, 0.22, 0.275, 0.33, 0.385, 0.44, 0.495, 0.55, 0.605, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62], [0.11, 0.165, 0.22, 0.275, 0.33, 0.385, 0.44, 0.495, 0.55, 0.605, 0.66, 0.715, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76], [0.11, 0.165, 0.22, 0.275, 0.33, 0.385, 0.44, 0.495, 0.55, 0.605, 0.66, 0.715, 0.77, 0.825, 0.88, 0.935, 0.99, 1.04, 1.04, 1.04, 1.04, 1.04, 1.04, 1.04, 1.04, 1.04, 1.04, 1.04, 1.04, 1.04, 1.04, 1.04, 1.04, 1.04, 1.04, 1.04, 1.04, 1.04, 1.04], [0.11, 0.165, 0.22, 0.275, 0.33, 0.385, 0.44, 0.495, 0.55, 0.605, 0.66, 0.715, 0.77, 0.825, 0.88, 0.935, 0.99, 1.045, 1.1, 1.155, 1.21, 1.265, 1.32, 1.32, 1.32, 1.32, 1.32, 1.32, 1.32, 1.32, 1.32, 1.32, 1.32, 1.32, 1.32, 1.32, 1.32, 1.32, 1.32], [0.11, 0.165, 0.22, 0.275, 0.33, 0.385, 0.44, 0.495, 0.55, 0.605, 0.66, 0.715, 0.77, 0.825, 0.88, 0.935, 0.99, 1.045, 1.1, 1.155, 1.21, 1.265, 1.32, 1.375, 1.43, 1.485, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5], [0.11, 0.165, 0.22, 0.275, 0.33, 0.385, 0.44, 0.495, 0.55, 0.605, 0.66, 0.715, 0.77, 0.825, 0.88, 0.935, 0.99, 1.045, 1.1, 1.155, 1.21, 1.265, 1.32, 1.375, 1.43, 1.485, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5], [0.11, 0.165, 0.22, 0.275, 0.33, 0.385, 0.44, 0.495, 0.55, 0.605, 0.66, 0.715, 0.77, 0.825, 0.88, 0.935, 0.99, 1.045, 1.1, 1.155, 1.21, 1.265, 1.32, 1.375, 1.43, 1.485, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5]]}}, "tyUnderLift": {"name": "Rise from Underline", "w": 1, "tags": ["editorial", "graphic", "pop"], "ae": "stretch", "pack": "typo", "set": "typo"}, "tyDotGrow": {"name": "Dot to Glyph", "w": 0.9, "tags": ["pop", "calm", "graphic"], "ae": "pop", "pack": "typo", "set": "typo"}, "tyBracketOpen": {"name": "Brackets Open", "w": 0.9, "tags": ["editorial", "graphic", "pop"], "ae": "wipe", "pack": "typo", "set": "typo"}, "tyRetype": {"name": "Retype", "w": 0.8, "tags": ["editorial", "glitch", "emotional"], "ae": "type", "minDur": 1, "pack": "typo", "set": "typo", "inDur": {"n": [1, 2, 3, 4, 6, 8, 12, 16, 24], "d": [[0.11, 0.165, 0.22, 0.275, 0.33, 0.385, 0.44, 0.495, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5], [0.11, 0.165, 0.22, 0.275, 0.33, 0.385, 0.44, 0.495, 0.55, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6], [0.11, 0.165, 0.22, 0.275, 0.33, 0.385, 0.44, 0.495, 0.55, 0.605, 0.66, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7], [0.11, 0.165, 0.22, 0.275, 0.33, 0.385, 0.44, 0.495, 0.55, 0.605, 0.66, 0.715, 0.77, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8], [0.11, 0.165, 0.22, 0.275, 0.33, 0.385, 0.44, 0.495, 0.55, 0.605, 0.66, 0.715, 0.77, 0.825, 0.88, 0.935, 0.99, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [0.11, 0.165, 0.22, 0.275, 0.33, 0.385, 0.44, 0.495, 0.55, 0.605, 0.66, 0.715, 0.77, 0.825, 0.88, 0.935, 0.99, 1.045, 1.1, 1.155, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2], [0.11, 0.165, 0.22, 0.275, 0.33, 0.385, 0.44, 0.495, 0.55, 0.605, 0.66, 0.715, 0.77, 0.825, 0.88, 0.935, 0.99, 1.045, 1.1, 1.155, 1.21, 1.265, 1.32, 1.375, 1.43, 1.485, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5], [0.11, 0.165, 0.22, 0.275, 0.33, 0.385, 0.44, 0.495, 0.55, 0.605, 0.66, 0.715, 0.77, 0.825, 0.88, 0.935, 0.99, 1.045, 1.1, 1.155, 1.21, 1.265, 1.32, 1.375, 1.43, 1.485, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5], [0.11, 0.165, 0.22, 0.275, 0.33, 0.385, 0.44, 0.495, 0.55, 0.605, 0.66, 0.715, 0.77, 0.825, 0.88, 0.935, 0.99, 1.045, 1.1, 1.155, 1.21, 1.265, 1.32, 1.375, 1.43, 1.485, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5]]}}, "tyRubyDrop": {"name": "From Ruby", "w": 1, "tags": ["calm", "editorial", "emotional"], "ae": "drop", "pack": "typo", "set": "typo"}}, "hold": {"still": {"name": "Still", "tags": ["calm", "editorial", "emotional", "graphic"]}, "jitter": {"name": "Jitter", "tags": ["glitch", "pop"]}, "drift": {"name": "Drift", "tags": ["calm", "emotional", "editorial"]}, "breathe": {"name": "Breathe", "tags": ["calm", "emotional"]}, "wave": {"name": "Wave", "tags": ["pop"]}, "glitchtick": {"name": "Glitch tick", "tags": ["glitch"]}, "float": {"name": "Float", "w": 0.8, "tags": ["calm", "emotional"], "pack": "exitHold", "ae": "drift"}, "sway": {"name": "Sway", "w": 0.6, "tags": ["calm", "emotional"], "pack": "exitHold", "ae": "wave"}, "pulse": {"name": "Pulse", "w": 0.6, "tags": ["pop", "graphic"], "pack": "exitHold", "ae": "breathe"}, "shimmer": {"name": "Shimmer", "w": 0.5, "tags": ["emotional", "calm"], "pack": "exitHold", "ae": "still"}, "colorRun": {"name": "Traveling color", "w": 0.5, "tags": ["pop", "graphic"], "pack": "exitHold", "ae": "still"}, "rotateSlow": {"name": "Rotate slow", "w": 0.4, "tags": ["calm", "editorial"], "pack": "exitHold", "ae": "drift"}, "trackBreathe": {"name": "Breathing letter spacing", "w": 0.5, "tags": ["calm", "editorial"], "pack": "exitHold", "ae": "breathe"}, "skewWobble": {"name": "Skew wobble", "w": 0.4, "tags": ["pop", "glitch"], "pack": "exitHold", "ae": "wave"}, "beatHop": {"name": "Hop to the beat", "w": 0.5, "tags": ["pop"], "pack": "exitHold", "ae": "wave"}, "hWave": {"name": "Horizontal wave", "w": 0.4, "tags": ["pop", "emotional"], "pack": "exitHold", "ae": "wave"}, "heartbeat": {"name": "Heartbeat", "w": 0.4, "tags": ["emotional", "pop"], "pack": "exitHold", "ae": "breathe"}, "orbitSmall": {"name": "Small orbit", "w": 0.5, "tags": ["calm", "pop"], "pack": "exitHold", "ae": "jitter"}, "jelly": {"name": "Jelly", "w": 0.4, "tags": ["pop"], "pack": "exitHold", "ae": "breathe"}, "scanBand": {"name": "Scanning band", "w": 0.4, "tags": ["glitch", "graphic"], "pack": "exitHold", "ae": "glitchtick"}, "noiseDrift": {"name": "Drifting noise", "w": 0.6, "tags": ["calm", "emotional"], "pack": "exitHold", "ae": "drift"}, "tilt": {"name": "Tilt", "w": 0.4, "tags": ["calm", "editorial"], "pack": "exitHold", "ae": "drift"}, "zoomSlow": {"name": "Slow push", "w": 0.8, "tags": ["calm", "emotional", "editorial"], "pack": "exitHold", "ae": "drift"}, "stretchPulse": {"name": "Stretch to the beat", "w": 0.4, "tags": ["pop", "graphic"], "pack": "exitHold", "ae": "breathe"}, "glitchJump": {"name": "Occasional glitch jump", "w": 0.4, "tags": ["glitch"], "pack": "exitHold", "ae": "glitchtick"}, "echoTrail": {"name": "Echo trail", "w": 0.4, "tags": ["emotional", "glitch"], "pack": "exitHold", "ae": "drift"}, "glowFlicker": {"name": "Flickering glow", "w": 0.5, "tags": ["calm", "emotional"], "extra": true, "ae": "breathe", "pack": "exitB"}, "windGust": {"name": "Wind gust", "w": 0.5, "tags": ["pop", "emotional"], "extra": true, "ae": "wave", "pack": "exitB"}, "dangle": {"name": "Dangle", "w": 0.5, "tags": ["calm", "emotional"], "extra": true, "ae": "wave", "pack": "exitB"}, "eqBounce": {"name": "Audio level bounce", "w": 0.5, "tags": ["pop", "graphic"], "extra": true, "ae": "breathe", "pack": "exitB"}, "flashBox": {"name": "Invert to the beat", "w": 0.4, "tags": ["pop", "graphic", "glitch"], "extra": true, "ae": "glitchtick", "pack": "exitB"}, "glintSweep": {"name": "Passing highlight", "w": 0.5, "tags": ["graphic", "calm"], "extra": true, "ae": "still", "pack": "exitB"}, "flipSwap": {"name": "Occasional flip", "w": 0.4, "tags": ["glitch", "pop"], "extra": true, "ae": "glitchtick", "pack": "exitB"}, "shadowSway": {"name": "Swaying shadow", "w": 0.5, "tags": ["calm", "emotional"], "extra": true, "ae": "drift", "pack": "exitB"}, "magnetJiggle": {"name": "Magnet jiggle", "w": 0.4, "tags": ["pop", "glitch"], "extra": true, "ae": "jitter", "pack": "exitB"}, "typeRattle": {"name": "Typewriter rattle", "w": 0.5, "tags": ["editorial", "glitch"], "extra": true, "ae": "jitter", "pack": "exitB"}, "focusRack": {"name": "Rack focus", "w": 0.5, "tags": ["calm", "emotional"], "extra": true, "ae": "breathe", "pack": "exitB"}, "pluckString": {"name": "Plucked string", "w": 0.4, "tags": ["pop", "emotional"], "extra": true, "ae": "wave", "pack": "exitB"}, "hrTwitch": {"name": "Twitch", "w": 1, "tags": ["horror", "glitch"], "ae": "glitchtick", "pack": "horror", "set": "horror"}, "hrStare": {"name": "Staring Glyph", "w": 0.8, "tags": ["horror", "emotional"], "ae": "drift", "pack": "horror", "set": "horror"}, "hrLagOne": {"name": "The Late One", "w": 0.8, "tags": ["horror", "calm"], "ae": "drift", "pack": "horror", "set": "horror"}, "hrFlickerLight": {"name": "Failing Light", "w": 0.9, "tags": ["horror", "glitch", "emotional"], "ae": "glitchtick", "pack": "horror", "set": "horror"}, "knWordPulse": {"name": "Word Pulse", "w": 1, "tags": ["pop", "graphic"], "ae": "breathe", "pack": "kinetic", "set": "kinetic"}, "knCounterRock": {"name": "Counter Rock", "w": 0.9, "tags": ["pop", "calm", "graphic"], "ae": "wave", "pack": "kinetic", "set": "kinetic"}, "knWordRide": {"name": "Word Surf", "w": 0.9, "tags": ["calm", "emotional", "pop"], "ae": "wave", "pack": "kinetic", "set": "kinetic"}, "knTickShift": {"name": "Tick Shift", "w": 0.8, "tags": ["graphic", "pop", "editorial"], "ae": "jitter", "pack": "kinetic", "set": "kinetic"}, "knBeatLean": {"name": "Beat Lean", "w": 0.8, "tags": ["pop", "glitch", "graphic"], "ae": "jitter", "pack": "kinetic", "set": "kinetic"}, "knGapBreath": {"name": "Breathing Gaps", "w": 0.9, "tags": ["calm", "editorial", "emotional"], "ae": "breathe", "pack": "kinetic", "set": "kinetic"}, "tyKeyPulse": {"name": "Key Glyph Pulse", "w": 1, "tags": ["emotional", "pop", "calm"], "ae": "breathe", "pack": "typo", "set": "typo"}, "tyReadCursor": {"name": "Reading Cursor", "w": 1, "tags": ["calm", "editorial", "pop"], "ae": "wave", "pack": "typo", "set": "typo"}, "tyOutlineBlink": {"name": "Outline Blink", "w": 0.9, "tags": ["glitch", "graphic", "pop"], "ae": "glitchtick", "pack": "typo", "set": "typo"}, "tyTrackStep": {"name": "Tracking Steps", "w": 0.8, "tags": ["graphic", "editorial", "pop"], "ae": "breathe", "pack": "typo", "set": "typo"}}, "exit": {"cut": {"name": "Cut"}, "explode": {"name": "Explode", "tags": ["glitch", "emotional"]}, "fall": {"name": "Crumble and fall", "tags": ["glitch", "emotional", "horror"]}, "drift": {"name": "Drift away", "tags": ["calm", "editorial", "emotional", "horror"]}, "slice": {"name": "Slice", "tags": ["glitch", "graphic"]}, "wipe": {"name": "Wipe", "tags": ["calm", "graphic", "editorial"]}, "shrink": {"name": "Shrink", "tags": ["calm", "pop"]}, "blur": {"name": "Blur", "tags": ["calm", "pop", "editorial", "emotional", "horror"]}, "stretch": {"name": "Stretch", "tags": ["pop", "graphic"]}, "scatter": {"name": "Scatter", "tags": ["pop"]}, "glitch": {"name": "Glitch", "tags": ["glitch", "horror"]}, "sinkMask": {"name": "Sink out", "w": 1, "tags": ["calm", "editorial", "graphic"], "pack": "exitHold", "ae": "fall"}, "riseOut": {"name": "Rise out", "w": 1, "tags": ["calm", "emotional", "editorial"], "pack": "exitHold", "ae": "drift"}, "slideOutL": {"name": "Slide left", "w": 0.8, "tags": ["pop", "graphic"], "pack": "exitHold", "ae": "stretch"}, "slideOutR": {"name": "Slide right", "w": 0.8, "tags": ["pop", "graphic"], "pack": "exitHold", "ae": "stretch"}, "flipOutX": {"name": "Doors close", "w": 1, "tags": ["graphic", "pop", "editorial"], "pack": "exitHold", "ae": "shrink"}, "flipOutY": {"name": "Flip down", "w": 0.9, "tags": ["pop", "graphic"], "pack": "exitHold", "ae": "fall"}, "foldOut": {"name": "Fold out", "w": 0.9, "tags": ["graphic", "editorial"], "pack": "exitHold", "ae": "shrink", "outDur": {"d": [0.28, 0.28, 0.28, 0.28, 0.28, 0.28, 0.28, 0.306, 0.34, 0.374, 0.408, 0.442, 0.476, 0.51, 0.544, 0.578, 0.612, 0.646, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65]}}, "squash": {"name": "Squash", "w": 0.9, "tags": ["glitch", "pop", "graphic"], "pack": "exitHold", "ae": "shrink"}, "trackOutWide": {"name": "Spread letter spacing", "w": 1, "tags": ["calm", "emotional", "editorial"], "pack": "exitHold", "ae": "blur"}, "collapse": {"name": "Collapse inward", "w": 0.9, "tags": ["pop", "graphic"], "pack": "exitHold", "ae": "shrink"}, "zoomThrough": {"name": "Zoom through", "w": 1, "tags": ["emotional", "pop"], "pack": "exitHold", "ae": "blur"}, "zoomFar": {"name": "Recede into distance", "w": 1, "tags": ["emotional", "calm"], "pack": "exitHold", "ae": "shrink"}, "spinOut": {"name": "Spin away", "w": 0.9, "tags": ["pop"], "pack": "exitHold", "ae": "scatter"}, "twist": {"name": "Twist", "w": 0.8, "tags": ["pop", "graphic"], "pack": "exitHold", "ae": "shrink"}, "waveOut": {"name": "Wave out", "w": 0.9, "tags": ["pop", "emotional"], "pack": "exitHold", "ae": "scatter"}, "blurOutStagger": {"name": "Blur letters in sequence", "w": 1, "tags": ["calm", "emotional"], "pack": "exitHold", "ae": "blur"}, "undraw": {"name": "Return to outlines", "w": 1, "tags": ["editorial", "calm", "graphic"], "pack": "exitHold", "ae": "blur", "outDur": {"d": [0.25, 0.25, 0.25, 0.25, 0.25, 0.252, 0.288, 0.324, 0.36, 0.396, 0.432, 0.468, 0.504, 0.54, 0.576, 0.612, 0.648, 0.684, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7]}}, "outlineOut": {"name": "Fill fades to outline", "w": 0.9, "tags": ["graphic", "emotional"], "pack": "exitHold", "ae": "blur"}, "irisClose": {"name": "Iris closes", "w": 1, "tags": ["graphic", "pop", "editorial"], "pack": "exitHold", "ae": "shrink"}, "diagWipeOut": {"name": "Diag wipe out", "w": 1, "tags": ["graphic", "editorial"], "pack": "exitHold", "ae": "wipe"}, "blindsClose": {"name": "Blinds close", "w": 0.9, "tags": ["graphic", "editorial"], "pack": "exitHold", "ae": "slice"}, "checkerOut": {"name": "Checker out", "w": 0.8, "tags": ["graphic", "glitch"], "pack": "exitHold", "ae": "glitch"}, "splitApart": {"name": "Split top and bottom", "w": 1, "tags": ["graphic", "pop"], "pack": "exitHold", "ae": "slice"}, "vSliceDrop": {"name": "Vertical slices fall", "w": 0.9, "tags": ["graphic", "glitch"], "pack": "exitHold", "ae": "fall", "outDur": {"d": [0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.272, 0.306, 0.34, 0.374, 0.408, 0.442, 0.476, 0.51, 0.544, 0.578, 0.612, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62]}}, "melt": {"name": "Melt", "w": 0.8, "tags": ["emotional", "glitch"], "minDur": 0.9, "pack": "exitHold", "ae": "fall", "outDur": {"d": [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.32, 0.36, 0.4, 0.44, 0.48, 0.52, 0.56, 0.6, 0.64, 0.68, 0.72, 0.76, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8]}}, "dissolve": {"name": "Crumble away", "w": 0.9, "tags": ["calm", "emotional"], "pack": "exitHold", "ae": "drift"}, "backspace": {"name": "Backspace", "w": 0.8, "tags": ["editorial", "glitch"], "minDur": 0.9, "pack": "exitHold", "ae": "wipe", "outDur": {"n": [1, 2, 3, 4, 6, 8, 12, 16, 24], "d": [[0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3], [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3], [0.3, 0.3, 0.3, 0.3, 0.3, 0.305, 0.305, 0.305, 0.305, 0.305, 0.305, 0.305, 0.305, 0.305, 0.305, 0.305, 0.305, 0.305, 0.305, 0.305, 0.305, 0.305, 0.305, 0.305, 0.305, 0.305, 0.305, 0.305, 0.305, 0.305, 0.305, 0.305, 0.305, 0.305, 0.305, 0.305, 0.305, 0.305, 0.305], [0.3, 0.3, 0.3, 0.3, 0.3, 0.315, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34], [0.3, 0.3, 0.3, 0.3, 0.3, 0.315, 0.36, 0.405, 0.41, 0.41, 0.41, 0.41, 0.41, 0.41, 0.41, 0.41, 0.41, 0.41, 0.41, 0.41, 0.41, 0.41, 0.41, 0.41, 0.41, 0.41, 0.41, 0.41, 0.41, 0.41, 0.41, 0.41, 0.41, 0.41, 0.41, 0.41, 0.41, 0.41, 0.41], [0.3, 0.3, 0.3, 0.3, 0.3, 0.315, 0.36, 0.405, 0.45, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48, 0.48], [0.3, 0.3, 0.3, 0.3, 0.3, 0.315, 0.36, 0.405, 0.45, 0.495, 0.54, 0.585, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62, 0.62], [0.3, 0.3, 0.3, 0.3, 0.3, 0.315, 0.36, 0.405, 0.45, 0.495, 0.54, 0.585, 0.63, 0.675, 0.72, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76, 0.76], [0.3, 0.3, 0.3, 0.3, 0.3, 0.315, 0.36, 0.405, 0.45, 0.495, 0.54, 0.585, 0.63, 0.675, 0.72, 0.765, 0.81, 0.855, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9]]}}, "scrambleOut": {"name": "Become symbols", "w": 0.9, "tags": ["glitch"], "pack": "exitHold", "ae": "glitch"}, "glitchDissolve": {"name": "Glitch into blocks", "w": 0.8, "tags": ["glitch"], "pack": "exitHold", "ae": "glitch"}, "echoOut": {"name": "Echo out", "w": 0.9, "tags": ["emotional", "calm"], "pack": "exitHold", "ae": "blur"}, "whipOut": {"name": "Whip out", "w": 1, "tags": ["pop", "graphic"], "pack": "exitHold", "ae": "stretch"}, "gravity": {"name": "Fall with gravity", "w": 1, "tags": ["pop", "emotional"], "pack": "exitHold", "ae": "fall", "outDur": {"d": [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.304, 0.342, 0.38, 0.418, 0.456, 0.494, 0.532, 0.57, 0.608, 0.646, 0.684, 0.722, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75]}}, "popOut": {"name": "Pop out", "w": 1, "tags": ["pop"], "pack": "exitHold", "ae": "scatter"}, "burn": {"name": "Burn away", "w": 0.8, "tags": ["emotional", "glitch"], "pack": "exitHold", "ae": "drift", "outDur": {"d": [0.28, 0.28, 0.28, 0.28, 0.28, 0.28, 0.288, 0.324, 0.36, 0.396, 0.432, 0.468, 0.504, 0.54, 0.576, 0.612, 0.648, 0.684, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7]}}, "sweepCover": {"name": "Covered by a bar", "w": 1, "tags": ["graphic", "editorial", "pop"], "pack": "exitHold", "ae": "wipe", "outDur": {"d": [0.26, 0.26, 0.26, 0.26, 0.26, 0.26, 0.272, 0.306, 0.34, 0.374, 0.408, 0.442, 0.476, 0.51, 0.544, 0.578, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6]}}, "shatterLite": {"name": "Shatter into quarters", "w": 0.8, "tags": ["pop", "glitch"], "pack": "exitHold", "ae": "explode"}, "peelOff": {"name": "Peel off sticker", "w": 1, "tags": ["pop", "graphic"], "extra": true, "ae": "wipe", "pack": "exitB", "outDur": {"d": [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.324, 0.36, 0.396, 0.432, 0.468, 0.504, 0.54, 0.576, 0.612, 0.648, 0.684, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7]}}, "crumpleOut": {"name": "Crumple and toss", "w": 0.9, "tags": ["pop", "emotional"], "extra": true, "ae": "shrink", "minDur": 0.8, "pack": "exitB", "outDur": {"d": [0.36, 0.36, 0.36, 0.36, 0.36, 0.36, 0.36, 0.378, 0.42, 0.462, 0.504, 0.546, 0.588, 0.63, 0.672, 0.714, 0.756, 0.798, 0.84, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85]}}, "tearOut": {"name": "Tear away", "w": 0.9, "tags": ["emotional", "graphic"], "extra": true, "ae": "slice", "pack": "exitB", "outDur": {"d": [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.304, 0.342, 0.38, 0.418, 0.456, 0.494, 0.532, 0.57, 0.608, 0.646, 0.684, 0.722, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75]}}, "scorchOut": {"name": "Scorch away", "w": 0.8, "tags": ["emotional", "glitch"], "extra": true, "ae": "wipe", "pack": "exitB", "outDur": {"d": [0.32, 0.32, 0.32, 0.32, 0.32, 0.32, 0.32, 0.36, 0.4, 0.44, 0.48, 0.52, 0.56, 0.6, 0.64, 0.68, 0.72, 0.76, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8]}}, "overexposeOut": {"name": "Blow out to white", "w": 1, "tags": ["emotional", "calm", "pop"], "extra": true, "ae": "blur", "pack": "exitB"}, "scanOut": {"name": "Scanline erase", "w": 0.9, "tags": ["glitch", "graphic"], "extra": true, "ae": "slice", "pack": "exitB"}, "stripesOut": {"name": "Stripes out", "w": 0.9, "tags": ["graphic", "pop"], "extra": true, "ae": "wipe", "pack": "exitB"}, "halftoneOut": {"name": "Dissolve to halftone", "w": 0.9, "tags": ["graphic", "calm"], "extra": true, "ae": "shrink", "pack": "exitB"}, "eraserOut": {"name": "Chalkboard erase", "w": 0.9, "tags": ["editorial", "calm"], "extra": true, "ae": "wipe", "minDur": 0.9, "pack": "exitB", "outDur": {"d": [0.36, 0.36, 0.36, 0.36, 0.36, 0.36, 0.36, 0.378, 0.42, 0.462, 0.504, 0.546, 0.588, 0.63, 0.672, 0.714, 0.756, 0.798, 0.84, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85]}}, "vacuumOut": {"name": "Vacuum to a point", "w": 0.9, "tags": ["pop", "graphic"], "extra": true, "ae": "shrink", "pack": "exitB"}, "sandOut": {"name": "Scatter as sand", "w": 1, "tags": ["emotional", "calm"], "extra": true, "ae": "drift", "minDur": 0.8, "pack": "exitB", "outDur": {"d": [0.36, 0.36, 0.36, 0.36, 0.36, 0.36, 0.36, 0.396, 0.44, 0.484, 0.528, 0.572, 0.616, 0.66, 0.704, 0.748, 0.792, 0.836, 0.88, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9]}}, "shredOut": {"name": "Shred out", "w": 0.8, "tags": ["graphic", "pop"], "extra": true, "ae": "fall", "minDur": 0.9, "pack": "exitB", "outDur": {"d": [0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.396, 0.44, 0.484, 0.528, 0.572, 0.616, 0.66, 0.704, 0.748, 0.792, 0.836, 0.88, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9]}}, "dominoOut": {"name": "Domino out", "w": 0.9, "tags": ["pop", "graphic"], "extra": true, "ae": "fall", "minDur": 0.8, "pack": "exitB", "outDur": {"n": [1, 2, 3, 4, 6, 8, 12, 16, 24], "d": [[0.36, 0.36, 0.36, 0.36, 0.36, 0.36, 0.36, 0.378, 0.42, 0.462, 0.504, 0.546, 0.588, 0.63, 0.672, 0.714, 0.756, 0.798, 0.84, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85], [0.36, 0.36, 0.36, 0.36, 0.36, 0.36, 0.36, 0.378, 0.42, 0.462, 0.504, 0.546, 0.588, 0.63, 0.672, 0.714, 0.756, 0.798, 0.84, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85], [0.36, 0.36, 0.36, 0.36, 0.36, 0.36, 0.36, 0.378, 0.42, 0.462, 0.504, 0.546, 0.588, 0.63, 0.672, 0.714, 0.756, 0.798, 0.84, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85], [0.36, 0.36, 0.36, 0.36, 0.36, 0.36, 0.36, 0.378, 0.42, 0.462, 0.504, 0.546, 0.588, 0.63, 0.672, 0.714, 0.756, 0.798, 0.84, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85], [0.36, 0.36, 0.36, 0.36, 0.36, 0.36, 0.36, 0.378, 0.42, 0.462, 0.504, 0.546, 0.588, 0.63, 0.672, 0.714, 0.756, 0.798, 0.84, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85], [0.36, 0.36, 0.36, 0.36, 0.36, 0.36, 0.36, 0.378, 0.42, 0.462, 0.504, 0.546, 0.588, 0.63, 0.672, 0.714, 0.756, 0.798, 0.84, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85], [0.36, 0.36, 0.36, 0.36, 0.36, 0.36, 0.36, 0.378, 0.42, 0.462, 0.504, 0.546, 0.588, 0.63, 0.672, 0.714, 0.756, 0.798, 0.84, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85], [0.36, 0.36, 0.36, 0.36, 0.36, 0.36, 0.36, 0.378, 0.42, 0.462, 0.504, 0.546, 0.588, 0.63, 0.672, 0.714, 0.756, 0.798, 0.84, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85], [0.36, 0.36, 0.36, 0.36, 0.36, 0.36, 0.36, 0.378, 0.42, 0.462, 0.504, 0.546, 0.588, 0.63, 0.672, 0.714, 0.756, 0.798, 0.84, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85]]}}, "hingeOut": {"name": "Loose hinge", "w": 0.9, "tags": ["pop", "editorial"], "extra": true, "ae": "fall", "minDur": 0.9, "pack": "exitB", "outDur": {"d": [0.42, 0.42, 0.42, 0.42, 0.42, 0.42, 0.42, 0.42, 0.46, 0.506, 0.552, 0.598, 0.644, 0.69, 0.736, 0.782, 0.828, 0.874, 0.92, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95]}}, "rocketOff": {"name": "Rocket away", "w": 0.9, "tags": ["pop"], "extra": true, "ae": "drift", "minDur": 0.8, "pack": "exitB", "outDur": {"d": [0.36, 0.36, 0.36, 0.36, 0.36, 0.36, 0.36, 0.396, 0.44, 0.484, 0.528, 0.572, 0.616, 0.66, 0.704, 0.748, 0.792, 0.836, 0.88, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9]}}, "bounceOff": {"name": "Bounce off", "w": 0.9, "tags": ["pop"], "extra": true, "ae": "scatter", "minDur": 0.8, "pack": "exitB", "outDur": {"d": [0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.405, 0.45, 0.495, 0.54, 0.585, 0.63, 0.675, 0.72, 0.765, 0.81, 0.855, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9]}}, "balloonOff": {"name": "Float off like a balloon", "w": 0.9, "tags": ["emotional", "calm", "pop"], "extra": true, "ae": "drift", "minDur": 0.9, "pack": "exitB", "outDur": {"d": [0.42, 0.42, 0.42, 0.42, 0.42, 0.42, 0.42, 0.432, 0.48, 0.528, 0.576, 0.624, 0.672, 0.72, 0.768, 0.816, 0.864, 0.912, 0.96, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}}, "deflateOut": {"name": "Deflate and fly", "w": 0.8, "tags": ["pop"], "extra": true, "ae": "scatter", "pack": "exitB", "outDur": {"d": [0.36, 0.36, 0.36, 0.36, 0.36, 0.36, 0.36, 0.378, 0.42, 0.462, 0.504, 0.546, 0.588, 0.63, 0.672, 0.714, 0.756, 0.798, 0.84, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85]}}, "hazeOut": {"name": "Haze out", "w": 0.9, "tags": ["emotional", "calm"], "extra": true, "ae": "blur", "pack": "exitB"}, "glassBreak": {"name": "Break like glass", "w": 0.8, "tags": ["graphic", "pop", "glitch"], "extra": true, "ae": "explode", "pack": "exitB", "outDur": {"d": [0.36, 0.36, 0.36, 0.36, 0.36, 0.36, 0.36, 0.378, 0.42, 0.462, 0.504, 0.546, 0.588, 0.63, 0.672, 0.714, 0.756, 0.798, 0.84, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85]}}, "zipOut": {"name": "Zip out", "w": 0.8, "tags": ["graphic", "pop"], "extra": true, "ae": "wipe", "pack": "exitB"}, "clapShut": {"name": "Close at center", "w": 0.9, "tags": ["graphic", "pop"], "extra": true, "ae": "wipe", "pack": "exitB"}, "lampOff": {"name": "Lights out", "w": 0.8, "tags": ["glitch", "emotional"], "extra": true, "ae": "glitch", "pack": "exitB"}, "slotOut": {"name": "Slot out", "w": 0.8, "tags": ["glitch", "pop"], "extra": true, "ae": "glitch", "pack": "exitB", "outDur": {"d": [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.304, 0.342, 0.38, 0.418, 0.456, 0.494, 0.532, 0.57, 0.608, 0.646, 0.684, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7]}}, "clockOut": {"name": "Clock out", "w": 0.9, "tags": ["graphic", "editorial"], "extra": true, "ae": "wipe", "pack": "exitB"}, "matrixOut": {"name": "Digital rain", "w": 0.8, "tags": ["glitch"], "extra": true, "ae": "glitch", "pack": "exitB", "outDur": {"d": [0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.34, 0.378, 0.42, 0.462, 0.504, 0.546, 0.588, 0.63, 0.672, 0.714, 0.756, 0.798, 0.84, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85]}}, "tornadoOut": {"name": "Tornado", "w": 0.8, "tags": ["pop"], "extra": true, "ae": "scatter", "minDur": 0.8, "pack": "exitB", "outDur": {"d": [0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.405, 0.45, 0.495, 0.54, 0.585, 0.63, 0.675, 0.72, 0.765, 0.81, 0.855, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9]}}, "rollUpOut": {"name": "Roll up out", "w": 0.8, "tags": ["graphic", "editorial"], "extra": true, "ae": "wipe", "pack": "exitB", "outDur": {"d": [0.32, 0.32, 0.32, 0.32, 0.32, 0.32, 0.32, 0.36, 0.4, 0.44, 0.48, 0.52, 0.56, 0.6, 0.64, 0.68, 0.72, 0.76, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8]}}, "snakeOut": {"name": "Leave in a line", "w": 0.9, "tags": ["calm", "pop"], "extra": true, "ae": "stretch", "minDur": 0.8, "pack": "exitB", "outDur": {"d": [0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.405, 0.45, 0.495, 0.54, 0.585, 0.63, 0.675, 0.72, 0.765, 0.81, 0.855, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9]}}, "flutterOut": {"name": "Flutter down", "w": 1, "tags": ["emotional", "calm"], "extra": true, "ae": "fall", "minDur": 0.8, "pack": "exitB", "outDur": {"d": [0.42, 0.42, 0.42, 0.42, 0.42, 0.42, 0.42, 0.432, 0.48, 0.528, 0.576, 0.624, 0.672, 0.72, 0.768, 0.816, 0.864, 0.912, 0.96, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}}, "rollOff": {"name": "Roll off", "w": 0.8, "tags": ["pop"], "extra": true, "ae": "scatter", "minDur": 0.8, "pack": "exitB", "outDur": {"d": [0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.405, 0.45, 0.495, 0.54, 0.585, 0.63, 0.675, 0.72, 0.765, 0.81, 0.855, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9]}}, "fanClose": {"name": "Folding fan closes", "w": 0.9, "tags": ["graphic", "editorial", "emotional"], "extra": true, "wa": true, "ae": "shrink", "minDur": 0.8, "pack": "exitB", "outDur": {"d": [0.36, 0.36, 0.36, 0.36, 0.36, 0.36, 0.36, 0.396, 0.44, 0.484, 0.528, 0.572, 0.616, 0.66, 0.704, 0.748, 0.792, 0.836, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85]}}, "rgbSplitOut": {"name": "Split into RGB", "w": 0.9, "tags": ["glitch", "pop"], "extra": true, "ae": "glitch", "pack": "exitB"}, "shockOut": {"name": "Shockwave", "w": 0.9, "tags": ["pop", "graphic"], "extra": true, "ae": "explode", "pack": "exitB"}, "floodOut": {"name": "Submerge", "w": 0.9, "tags": ["emotional", "calm"], "extra": true, "ae": "wipe", "pack": "exitB", "outDur": {"d": [0.32, 0.32, 0.32, 0.32, 0.32, 0.32, 0.32, 0.36, 0.4, 0.44, 0.48, 0.52, 0.56, 0.6, 0.64, 0.68, 0.72, 0.76, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8]}}, "slashOut": {"name": "Sliced in two", "w": 1, "tags": ["graphic", "emotional", "pop"], "extra": true, "ae": "slice", "pack": "exitB", "outDur": {"d": [0.32, 0.32, 0.32, 0.32, 0.32, 0.32, 0.32, 0.36, 0.4, 0.44, 0.48, 0.52, 0.56, 0.6, 0.64, 0.68, 0.72, 0.76, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8]}}, "mosaicOut": {"name": "Mosaic out", "w": 0.9, "tags": ["glitch", "graphic"], "extra": true, "ae": "glitch", "pack": "exitB"}, "scribbleOut": {"name": "Scribble away", "w": 0.8, "tags": ["editorial", "emotional", "pop"], "extra": true, "ae": "wipe", "pack": "exitB", "outDur": {"d": [0.32, 0.32, 0.32, 0.32, 0.32, 0.32, 0.32, 0.36, 0.4, 0.44, 0.48, 0.52, 0.56, 0.6, 0.64, 0.68, 0.72, 0.76, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8]}}, "candleOut": {"name": "Blow out", "w": 0.9, "tags": ["emotional", "calm"], "extra": true, "ae": "drift", "pack": "exitB", "outDur": {"d": [0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.38, 0.405, 0.45, 0.495, 0.54, 0.585, 0.63, 0.675, 0.72, 0.765, 0.81, 0.855, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9]}}, "hrPulledDown": {"name": "Pulled Under", "w": 1, "tags": ["horror", "glitch", "emotional"], "ae": "fall", "pack": "horror", "set": "horror", "outDur": {"d": [0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.44, 0.48, 0.52, 0.56, 0.6, 0.64, 0.68, 0.72, 0.76, 0.8, 0.84, 0.88, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9]}}, "hrLookBack": {"name": "One Remains", "w": 0.9, "tags": ["horror", "emotional"], "ae": "cut", "minDur": 1, "pack": "horror", "set": "horror", "outDur": {"d": [0.55, 0.55, 0.55, 0.55, 0.55, 0.55, 0.55, 0.55, 0.55, 0.55, 0.55, 0.585, 0.63, 0.675, 0.72, 0.765, 0.81, 0.855, 0.9, 0.945, 0.99, 1.035, 1.08, 1.125, 1.17, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2]}}, "hrTurnAway": {"name": "Turn Away", "w": 0.9, "tags": ["horror", "calm", "emotional"], "ae": "stretch", "pack": "horror", "set": "horror", "outDur": {"d": [0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.44, 0.48, 0.52, 0.56, 0.6, 0.64, 0.68, 0.72, 0.76, 0.8, 0.84, 0.88, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9]}}, "hrShiver": {"name": "Shiver Out", "w": 0.9, "tags": ["horror", "glitch"], "ae": "glitch", "pack": "horror", "set": "horror", "outDur": {"d": [0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.385, 0.42, 0.455, 0.49, 0.525, 0.56, 0.595, 0.63, 0.665, 0.7, 0.735, 0.77, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8]}}, "hrSwallow": {"name": "Swallowed", "w": 0.9, "tags": ["horror", "emotional", "graphic"], "ae": "wipe", "pack": "horror", "set": "horror", "outDur": {"d": [0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.44, 0.48, 0.52, 0.56, 0.6, 0.64, 0.68, 0.72, 0.76, 0.8, 0.84, 0.88, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9]}}, "hrFlickerDie": {"name": "Dying Flicker", "w": 0.9, "tags": ["horror", "glitch"], "ae": "glitch", "pack": "horror", "set": "horror", "outDur": {"d": [0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.38, 0.418, 0.456, 0.494, 0.532, 0.57, 0.608, 0.646, 0.684, 0.722, 0.76, 0.798, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8]}}, "hrDrain": {"name": "Drain", "w": 0.8, "tags": ["horror", "emotional"], "ae": "wipe", "minDur": 0.8, "pack": "horror", "set": "horror", "outDur": {"d": [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.54, 0.585, 0.63, 0.675, 0.72, 0.765, 0.81, 0.855, 0.9, 0.945, 0.99, 1.035, 1.08, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1, 1.1]}}, "knWordKick": {"name": "Word Kick", "w": 1, "tags": ["pop", "graphic"], "ae": "scatter", "pack": "kinetic", "set": "kinetic", "outDur": {"d": [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.32, 0.352, 0.384, 0.416, 0.448, 0.48, 0.512, 0.544, 0.576, 0.608, 0.64, 0.672, 0.704, 0.736, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75]}}, "knPushOut": {"name": "Push Out", "w": 0.9, "tags": ["editorial", "graphic", "pop"], "ae": "wipe", "pack": "kinetic", "set": "kinetic", "outDur": {"d": [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.32, 0.352, 0.384, 0.416, 0.448, 0.48, 0.512, 0.544, 0.576, 0.608, 0.64, 0.672, 0.704, 0.736, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75]}}, "knDiveGlyph": {"name": "Dive Into Letter", "w": 0.9, "tags": ["pop", "emotional", "graphic"], "ae": "shrink", "pack": "kinetic", "set": "kinetic", "outDur": {"d": [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.33, 0.36, 0.39, 0.42, 0.45, 0.48, 0.51, 0.54, 0.57, 0.6, 0.63, 0.66, 0.69, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7]}}, "knLaunch": {"name": "Launch", "w": 0.9, "tags": ["pop", "graphic"], "ae": "stretch", "pack": "kinetic", "set": "kinetic", "outDur": {"d": [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.33, 0.36, 0.39, 0.42, 0.45, 0.48, 0.51, 0.54, 0.57, 0.6, 0.63, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65]}}, "knWordBlink": {"name": "Words Blink Out", "w": 0.9, "tags": ["pop", "glitch", "graphic"], "ae": "cut", "pack": "kinetic", "set": "kinetic", "outDur": {"d": [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.315, 0.35, 0.385, 0.42, 0.455, 0.49, 0.525, 0.56, 0.595, 0.63, 0.665, 0.7, 0.735, 0.77, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8]}}, "knCloseGap": {"name": "Close the Gaps", "w": 0.9, "tags": ["editorial", "graphic", "pop"], "ae": "shrink", "pack": "kinetic", "set": "kinetic", "outDur": {"d": [0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.385, 0.42, 0.455, 0.49, 0.525, 0.56, 0.595, 0.63, 0.665, 0.7, 0.735, 0.77, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8]}}, "knJumpCutOut": {"name": "Jump Cut", "w": 0.8, "tags": ["pop", "glitch", "graphic"], "ae": "glitch", "pack": "kinetic", "set": "kinetic", "outDur": {"d": [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.33, 0.36, 0.39, 0.42, 0.45, 0.48, 0.51, 0.54, 0.57, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6]}}, "knStackAway": {"name": "Stack and Drop", "w": 0.8, "tags": ["pop", "graphic"], "ae": "fall", "minDur": 0.9, "pack": "kinetic", "set": "kinetic", "outDur": {"d": [0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.48, 0.52, 0.56, 0.6, 0.64, 0.68, 0.72, 0.76, 0.8, 0.84, 0.88, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9]}}, "tyStrike": {"name": "Strike Out", "w": 1, "tags": ["editorial", "graphic", "emotional"], "ae": "wipe", "pack": "typo", "set": "typo"}, "tyToDot": {"name": "Back to Dots", "w": 0.9, "tags": ["calm", "pop", "graphic"], "ae": "shrink", "pack": "typo", "set": "typo"}, "tyLineFeed": {"name": "Line Feed", "w": 1, "tags": ["editorial", "calm", "graphic"], "ae": "wipe", "pack": "typo", "set": "typo"}, "tyBracketClose": {"name": "Brackets Close", "w": 0.9, "tags": ["editorial", "graphic", "pop"], "ae": "wipe", "pack": "typo", "set": "typo"}, "tyToIndex": {"name": "Turn to Numbers", "w": 0.8, "tags": ["editorial", "glitch", "graphic"], "ae": "scatter", "pack": "typo", "set": "typo"}, "tyKeyLast": {"name": "Key Glyph Last", "w": 0.9, "tags": ["emotional", "pop", "graphic"], "ae": "shrink", "pack": "typo", "set": "typo"}, "tyUnderSink": {"name": "Sink into Underline", "w": 1, "tags": ["editorial", "calm", "graphic"], "ae": "fall", "pack": "typo", "set": "typo"}, "tyFoldVert": {"name": "Fold to Vertical", "w": 0.9, "tags": ["graphic", "editorial", "pop"], "ae": "fall", "pack": "typo", "set": "typo"}}, "decor": {"brackets": {"name": "Corner marks", "tags": ["graphic", "editorial"], "layer": "front"}, "rings": {"name": "Coordinate rings", "tags": ["graphic", "emotional"], "layer": "front"}, "dots": {"name": "Dotted ring", "tags": ["pop", "graphic"], "layer": "front"}, "arrows": {"name": "Arrows", "tags": ["pop", "graphic"], "layer": "front"}, "slash": {"name": "Slash", "tags": ["glitch", "graphic"], "layer": "front"}, "sparks": {"name": "Sparks", "tags": ["pop"], "layer": "front"}, "leaders": {"name": "Leader lines", "tags": ["editorial", "calm"], "layer": "front"}, "waveform": {"name": "Waveform", "tags": ["emotional", "calm"], "layer": "front"}, "barcode": {"name": "Barcode", "tags": ["glitch", "graphic"], "layer": "front"}, "grid": {"name": "Grid", "tags": ["graphic", "editorial"], "layer": "back"}, "stripes": {"name": "Stripes", "tags": ["graphic", "pop"], "layer": "back"}, "blobs": {"name": "Ink stains", "tags": ["pop", "emotional"], "layer": "back"}, "bars": {"name": "Rough bands", "tags": ["graphic", "glitch"], "layer": "back"}, "shapes": {"name": "Shapes", "tags": ["pop", "graphic"], "layer": "back"}, "counter": {"name": "Large numbers", "tags": ["graphic", "editorial"], "layer": "back"}, "crosshair": {"name": "Crosshair", "w": 1, "tags": ["graphic", "editorial", "glitch"], "layer": "front", "pack": "decor", "ae": "brackets"}, "cropMarks": {"name": "Crop marks", "w": 1.1, "tags": ["editorial", "graphic", "calm"], "layer": "front", "pack": "decor", "ae": "brackets"}, "reticle": {"name": "Reticle", "w": 0.9, "tags": ["glitch", "graphic"], "layer": "front", "pack": "decor", "ae": "rings"}, "radar": {"name": "Radar", "w": 0.7, "tags": ["glitch", "graphic"], "layer": "front", "pack": "decor", "ae": "rings"}, "progressRing": {"name": "Progress ring", "w": 0.9, "tags": ["graphic", "editorial", "calm"], "layer": "front", "pack": "decor", "ae": "rings"}, "timecodeBar": {"name": "Timecode bar", "w": 0.9, "tags": ["editorial", "glitch", "graphic"], "layer": "front", "pack": "decor", "ae": "barcode"}, "rulerEdge": {"name": "Ruler edge", "w": 1, "tags": ["editorial", "graphic", "calm"], "layer": "front", "pack": "decor", "ae": "grid"}, "dimension": {"name": "Dimension", "w": 1, "tags": ["editorial", "graphic"], "layer": "front", "pack": "decor", "ae": "leaders"}, "indexNum": {"name": "Sequence number", "w": 1, "tags": ["editorial", "graphic", "calm"], "layer": "front", "pack": "decor", "ae": "counter"}, "dateStamp": {"name": "Photo date stamp", "w": 0.7, "tags": ["emotional", "pop", "calm"], "layer": "front", "pack": "decor", "ae": "barcode"}, "qrBlock": {"name": "QR inspired blocks", "w": 0.7, "tags": ["graphic", "glitch", "pop"], "layer": "front", "pack": "decor", "ae": "barcode"}, "glitchRects": {"name": "Glitch rects", "w": 0.9, "tags": ["glitch"], "layer": "front", "pack": "decor", "ae": "bars"}, "concentricSquares": {"name": "Concentric squares", "w": 1, "tags": ["graphic", "calm", "editorial"], "layer": "front", "pack": "decor", "ae": "shapes"}, "triangleSpin": {"name": "Triangle spin", "w": 0.9, "tags": ["graphic", "pop", "glitch"], "layer": "front", "pack": "decor", "ae": "shapes"}, "lineBurst": {"name": "Line burst", "w": 1.1, "tags": ["pop", "graphic", "emotional"], "layer": "front", "pack": "decor", "ae": "sparks"}, "plusGrid": {"name": "Plus grid", "w": 1, "tags": ["graphic", "editorial", "calm"], "layer": "front", "pack": "decor", "ae": "grid"}, "guides": {"name": "Guide lines", "w": 1, "tags": ["editorial", "graphic", "calm"], "layer": "front", "pack": "decor", "ae": "grid"}, "waveLine": {"name": "Wave line", "w": 1, "tags": ["calm", "emotional", "graphic"], "layer": "front", "pack": "decor", "ae": "waveform"}, "spiralLine": {"name": "Spiral line", "w": 0.8, "tags": ["graphic", "pop", "calm"], "layer": "front", "pack": "decor", "ae": "rings"}, "halftonePatch": {"name": "Halftone patch", "w": 1, "tags": ["graphic", "pop", "editorial"], "subtle": true, "layer": "back", "pack": "decor", "ae": "shapes"}, "checkerStrip": {"name": "Checker strip", "w": 0.8, "tags": ["pop", "graphic"], "layer": "front", "pack": "decor", "ae": "stripes"}, "beatRing": {"name": "Beat ring", "w": 1, "tags": ["pop", "emotional", "calm"], "subtle": true, "layer": "back", "pack": "decor", "ae": "rings"}, "orbitDots": {"name": "Orbit dots", "w": 1, "tags": ["calm", "emotional", "graphic"], "layer": "front", "pack": "decor", "ae": "dots"}, "constellation": {"name": "Constellation", "w": 0.9, "tags": ["calm", "emotional", "editorial"], "layer": "front", "pack": "decor", "ae": "sparks"}, "confetti": {"name": "Confetti", "w": 0.9, "tags": ["pop", "emotional"], "layer": "front", "pack": "decor", "ae": "shapes"}, "petals": {"name": "Petals", "w": 0.9, "tags": ["emotional", "calm", "pop"], "wa": true, "layer": "front", "pack": "decor", "ae": "shapes"}, "rainStreaks": {"name": "Rain streaks", "w": 0.9, "tags": ["emotional", "calm", "glitch"], "subtle": true, "layer": "back", "pack": "decor", "ae": "slash"}, "snow": {"name": "Snow", "w": 0.8, "tags": ["calm", "emotional"], "subtle": true, "layer": "back", "pack": "decor", "ae": "dots"}, "lightLeak": {"name": "Light leak", "w": 1, "tags": ["emotional", "calm", "pop"], "subtle": true, "layer": "back", "pack": "decor", "ae": "blobs"}, "bokeh": {"name": "Bokeh", "w": 1, "tags": ["emotional", "calm", "pop"], "subtle": true, "layer": "back", "pack": "decor", "ae": "blobs"}, "speedCorner": {"name": "Speed lines", "w": 0.9, "tags": ["pop", "emotional", "glitch"], "layer": "front", "pack": "decor", "ae": "slash"}, "risingParticles": {"name": "Rising particles", "w": 0.9, "tags": ["emotional", "calm", "glitch"], "subtle": true, "layer": "back", "pack": "decor", "ae": "sparks"}, "twinkle": {"name": "Twinkle", "w": 1, "tags": ["pop", "emotional", "calm"], "layer": "front", "pack": "decor", "ae": "sparks"}, "brushStroke": {"name": "Brush stroke", "w": 1, "tags": ["emotional", "editorial", "pop"], "wa": true, "layer": "back", "pack": "decor", "ae": "bars"}, "tapePieces": {"name": "Masking tape", "w": 0.9, "tags": ["pop", "emotional", "editorial"], "layer": "front", "pack": "decor", "ae": "bars"}, "scribbleCircle": {"name": "Scribble circle", "w": 1, "tags": ["pop", "emotional", "editorial"], "layer": "front", "pack": "decor", "ae": "rings"}, "scribbleUnder": {"name": "Hand drawn underline", "w": 1.1, "tags": ["pop", "emotional", "editorial"], "layer": "front", "pack": "decor", "ae": "slash"}, "crossOut": {"name": "Editorial marks", "w": 0.4, "tags": ["editorial", "emotional"], "layer": "front", "pack": "decor", "ae": "slash"}, "highlightMark": {"name": "Highlight mark", "w": 1, "tags": ["pop", "editorial", "emotional"], "layer": "front", "pack": "decor", "ae": "bars"}, "heartsStars": {"name": "Hearts stars", "w": 0.9, "tags": ["pop", "emotional"], "layer": "front", "pack": "decor", "ae": "shapes"}, "watermarkKanji": {"name": "Large watermark character", "w": 1, "tags": ["editorial", "emotional", "calm"], "layer": "back", "pack": "decor", "ae": "counter"}, "verticalStrip": {"name": "Vertical text strip", "w": 1, "tags": ["editorial", "calm", "emotional"], "layer": "front", "pack": "decor", "ae": "leaders"}, "romajiLine": {"name": "Romanized line", "w": 1, "tags": ["editorial", "calm", "graphic"], "layer": "front", "pack": "decor", "ae": "leaders"}, "bracketsJP": {"name": "Japanese corner brackets", "w": 1, "tags": ["pop", "graphic", "editorial"], "layer": "front", "pack": "decor", "ae": "brackets"}, "seal": {"name": "Red seal", "w": 0.9, "tags": ["editorial", "emotional", "calm"], "wa": true, "layer": "front", "pack": "decor", "ae": "shapes"}, "kamon": {"name": "Family crest", "w": 0.8, "tags": ["editorial", "calm", "emotional"], "extra": true, "wa": true, "ae": "shapes", "layer": "front", "pack": "decorB"}, "seigaiha": {"name": "Seigaiha wave pattern", "w": 0.8, "tags": ["calm", "emotional", "editorial"], "extra": true, "wa": true, "ae": "grid", "subtle": true, "layer": "back", "pack": "decorB"}, "asanoha": {"name": "Asanoha hemp leaf pattern", "w": 0.8, "tags": ["calm", "editorial", "emotional"], "extra": true, "wa": true, "ae": "grid", "subtle": true, "layer": "back", "pack": "decorB"}, "hanabi": {"name": "Fireworks", "w": 0.8, "tags": ["emotional", "pop", "calm"], "extra": true, "ae": "sparks", "layer": "front", "pack": "decorB"}, "chochin": {"name": "Paper lantern", "w": 0.7, "tags": ["emotional", "pop", "calm"], "extra": true, "wa": true, "ae": "shapes", "layer": "front", "pack": "decorB"}, "shimenawa": {"name": "Sacred rope", "w": 0.6, "tags": ["editorial", "emotional", "calm"], "extra": true, "wa": true, "ae": "leaders", "layer": "front", "pack": "decorB"}, "sensu": {"name": "Folding fan", "w": 0.8, "tags": ["emotional", "editorial", "calm"], "extra": true, "wa": true, "ae": "shapes", "layer": "front", "pack": "decorB"}, "tsukiKumo": {"name": "Moon and clouds", "w": 0.8, "tags": ["emotional", "calm", "editorial"], "extra": true, "wa": true, "ae": "shapes", "layer": "front", "pack": "decorB"}, "momiji": {"name": "Maple leaves", "w": 0.8, "tags": ["emotional", "calm"], "extra": true, "wa": true, "ae": "shapes", "layer": "front", "pack": "decorB"}, "namiGashira": {"name": "Wave crest", "w": 0.8, "tags": ["emotional", "editorial", "graphic"], "extra": true, "wa": true, "ae": "waveform", "layer": "front", "pack": "decorB"}, "kasumi": {"name": "Mist", "w": 0.8, "tags": ["calm", "emotional", "editorial"], "extra": true, "wa": true, "ae": "bars", "subtle": true, "layer": "back", "pack": "decorB"}, "hexGrid": {"name": "Hex grid", "w": 0.9, "tags": ["glitch", "graphic"], "extra": true, "ae": "grid", "layer": "front", "pack": "decorB"}, "spectrumRing": {"name": "Spectrum ring", "w": 0.9, "tags": ["glitch", "pop", "graphic"], "extra": true, "ae": "waveform", "layer": "front", "pack": "decorB"}, "dataColumns": {"name": "Data columns", "w": 0.9, "tags": ["glitch", "editorial", "graphic"], "extra": true, "ae": "barcode", "layer": "front", "pack": "decorB"}, "spinner": {"name": "Spinner", "w": 0.8, "tags": ["glitch", "pop", "graphic"], "extra": true, "ae": "rings", "layer": "front", "pack": "decorB"}, "headingTape": {"name": "Heading tape", "w": 0.9, "tags": ["glitch", "graphic", "editorial"], "extra": true, "ae": "grid", "layer": "front", "pack": "decorB"}, "glyphLock": {"name": "Glyph lock", "w": 0.9, "tags": ["glitch", "graphic", "editorial"], "extra": true, "ae": "brackets", "layer": "front", "pack": "decorB"}, "atomOrbit": {"name": "Atom orbit", "w": 0.8, "tags": ["graphic", "glitch", "calm"], "extra": true, "ae": "rings", "layer": "front", "pack": "decorB"}, "sonarArcs": {"name": "Sonar arcs", "w": 0.9, "tags": ["pop", "emotional", "graphic"], "extra": true, "ae": "rings", "layer": "front", "pack": "decorB"}, "circuit": {"name": "Circuit", "w": 0.9, "tags": ["glitch", "graphic"], "extra": true, "ae": "leaders", "layer": "front", "pack": "decorB"}, "swatches": {"name": "Swatches", "w": 0.9, "tags": ["editorial", "graphic"], "extra": true, "ae": "bars", "layer": "front", "pack": "decorB"}, "ruledLines": {"name": "Ruled notebook", "w": 0.8, "tags": ["editorial", "calm", "emotional"], "extra": true, "ae": "grid", "subtle": true, "layer": "back", "pack": "decorB"}, "registration": {"name": "Registration marks", "w": 0.8, "tags": ["editorial", "graphic", "glitch"], "extra": true, "ae": "brackets", "layer": "front", "pack": "decorB"}, "punchHoles": {"name": "Punch holes", "w": 0.7, "tags": ["editorial", "calm", "graphic"], "extra": true, "ae": "dots", "layer": "front", "pack": "decorB"}, "staple": {"name": "Staple", "w": 0.7, "tags": ["editorial", "pop", "emotional"], "extra": true, "ae": "shapes", "layer": "front", "pack": "decorB"}, "paperClip": {"name": "Paper clip", "w": 0.7, "tags": ["editorial", "pop", "emotional"], "extra": true, "ae": "shapes", "layer": "front", "pack": "decorB"}, "indexTabs": {"name": "Index tabs", "w": 0.8, "tags": ["editorial", "pop", "graphic"], "extra": true, "ae": "bars", "layer": "front", "pack": "decorB"}, "vines": {"name": "Vines", "w": 0.8, "tags": ["emotional", "calm", "pop"], "extra": true, "ae": "leaders", "layer": "front", "pack": "decorB"}, "cloudPuffs": {"name": "Cloud puffs", "w": 0.8, "tags": ["calm", "pop", "emotional"], "extra": true, "ae": "blobs", "layer": "front", "pack": "decorB"}, "starField": {"name": "Star field", "w": 0.8, "tags": ["calm", "emotional"], "extra": true, "ae": "dots", "subtle": true, "layer": "back", "pack": "decorB"}, "moonPhases": {"name": "Moon phases", "w": 0.8, "tags": ["calm", "emotional", "editorial"], "extra": true, "ae": "dots", "layer": "front", "pack": "decorB"}, "sunRays": {"name": "Sun rays", "w": 0.8, "tags": ["emotional", "pop", "calm"], "extra": true, "ae": "stripes", "subtle": true, "layer": "back", "pack": "decorB"}, "rainRipples": {"name": "Rain ripples", "w": 0.8, "tags": ["calm", "emotional"], "extra": true, "ae": "rings", "subtle": true, "layer": "back", "pack": "decorB"}, "bubbles": {"name": "Bubbles", "w": 0.8, "tags": ["pop", "emotional", "calm"], "extra": true, "ae": "dots", "layer": "front", "pack": "decorB"}, "smoke": {"name": "Smoke", "w": 0.8, "tags": ["calm", "emotional", "editorial"], "extra": true, "ae": "blobs", "subtle": true, "layer": "back", "pack": "decorB"}, "dandelion": {"name": "Dandelion seeds", "w": 0.8, "tags": ["calm", "emotional"], "extra": true, "ae": "sparks", "layer": "front", "pack": "decorB"}, "fireflies": {"name": "Fireflies", "w": 0.8, "tags": ["emotional", "calm"], "extra": true, "ae": "dots", "layer": "front", "pack": "decorB"}, "memphis": {"name": "Memphis", "w": 0.9, "tags": ["pop", "graphic"], "extra": true, "ae": "shapes", "layer": "front", "pack": "decorB"}, "zigzagRibbon": {"name": "Zigzag ribbon", "w": 0.8, "tags": ["pop", "graphic"], "extra": true, "ae": "bars", "layer": "front", "pack": "decorB"}, "polkaPatch": {"name": "Polka patch", "w": 0.9, "tags": ["pop", "graphic", "calm"], "extra": true, "ae": "dots", "layer": "front", "pack": "decorB"}, "stripeCircle": {"name": "Stripe circle", "w": 0.9, "tags": ["graphic", "pop"], "extra": true, "ae": "stripes", "layer": "front", "pack": "decorB"}, "decoCorners": {"name": "Deco corners", "w": 0.9, "tags": ["editorial", "graphic", "calm"], "extra": true, "ae": "brackets", "layer": "front", "pack": "decorB"}, "halfCircles": {"name": "Half circles", "w": 0.9, "tags": ["graphic", "pop", "calm"], "extra": true, "ae": "shapes", "layer": "front", "pack": "decorB"}, "loopArrows": {"name": "Loop arrows", "w": 0.8, "tags": ["graphic", "pop", "editorial"], "extra": true, "ae": "arrows", "layer": "front", "pack": "decorB"}, "starburst": {"name": "Starburst", "w": 0.8, "tags": ["pop", "graphic"], "extra": true, "ae": "shapes", "layer": "front", "pack": "decorB"}, "tally": {"name": "Tally marks", "w": 0.8, "tags": ["editorial", "pop", "emotional"], "extra": true, "ae": "counter", "layer": "front", "pack": "decorB"}, "cursorClick": {"name": "Cursor click", "w": 0.8, "tags": ["pop", "graphic", "glitch"], "extra": true, "ae": "arrows", "layer": "front", "pack": "decorB"}, "windowChrome": {"name": "Window frame", "w": 0.8, "tags": ["pop", "graphic", "glitch"], "extra": true, "ae": "brackets", "layer": "front", "pack": "decorB"}, "progressBar": {"name": "Progress bar", "w": 0.8, "tags": ["pop", "graphic", "glitch"], "extra": true, "ae": "bars", "layer": "front", "pack": "decorB"}, "toggleSwitch": {"name": "Toggle switch", "w": 0.7, "tags": ["pop", "graphic"], "extra": true, "ae": "shapes", "layer": "front", "pack": "decorB"}, "notifBell": {"name": "Notification bell", "w": 0.7, "tags": ["pop", "emotional"], "extra": true, "ae": "counter", "layer": "front", "pack": "decorB"}, "likeCounter": {"name": "Like counter", "w": 0.8, "tags": ["pop", "emotional"], "extra": true, "ae": "counter", "layer": "front", "pack": "decorB"}, "mediaControls": {"name": "Playback controls", "w": 0.8, "tags": ["pop", "graphic", "emotional"], "extra": true, "ae": "shapes", "layer": "front", "pack": "decorB"}, "volumeBars": {"name": "Volume bars", "w": 0.8, "tags": ["pop", "glitch", "graphic"], "extra": true, "ae": "waveform", "layer": "front", "pack": "decorB"}, "musicNotes": {"name": "Music notes", "w": 0.8, "tags": ["pop", "emotional", "calm"], "extra": true, "ae": "sparks", "layer": "front", "pack": "decorB"}, "hrScratches": {"name": "Claw Scratches", "w": 0.9, "tags": ["horror", "glitch"], "ae": "slash", "layer": "front", "pack": "horror", "set": "horror"}, "hrSigil": {"name": "Sigil", "w": 0.7, "tags": ["horror", "graphic"], "ae": "rings", "subtle": true, "layer": "back", "pack": "horror", "set": "horror"}, "hrWatchEye": {"name": "Watching Eye", "w": 0.8, "tags": ["horror", "graphic"], "ae": "reticle", "layer": "front", "pack": "horror", "set": "horror"}, "hrStaticPatch": {"name": "Static Patches", "w": 0.8, "tags": ["horror", "glitch"], "ae": "glitchRects", "layer": "front", "pack": "horror", "set": "horror"}, "hrDustBeam": {"name": "Dusty Light Beam", "w": 0.8, "tags": ["horror", "calm", "emotional"], "ae": "sparks", "subtle": true, "layer": "back", "pack": "horror", "set": "horror"}, "hrDrips": {"name": "Ink Drips", "w": 0.8, "tags": ["horror", "emotional"], "ae": "blobs", "layer": "front", "pack": "horror", "set": "horror"}, "hrCracks": {"name": "Cracks", "w": 0.7, "tags": ["horror", "graphic", "glitch"], "ae": "lineBurst", "layer": "front", "pack": "horror", "set": "horror"}, "knSpeedTrail": {"name": "Trailing Speed Lines", "w": 0.8, "tags": ["pop", "graphic", "glitch"], "ae": "slash", "layer": "front", "pack": "kinetic", "set": "kinetic"}, "knWordTicks": {"name": "Word Counter", "w": 0.8, "tags": ["graphic", "editorial", "pop"], "ae": "counter", "subtle": true, "layer": "front", "pack": "kinetic", "set": "kinetic"}, "tyColophon": {"name": "Colophon", "w": 1, "tags": ["editorial", "calm", "graphic"], "ae": "leaders", "layer": "front", "pack": "typo", "set": "typo"}, "tyRunningHead": {"name": "Running Head & Folio", "w": 1, "tags": ["editorial", "calm"], "ae": "timecodeBar", "layer": "front", "pack": "typo", "set": "typo"}, "tyGlyphBody": {"name": "Glyph Body Lines", "w": 0.9, "tags": ["editorial", "graphic", "calm"], "ae": "guides", "layer": "front", "pack": "typo", "set": "typo"}, "tyTextRule": {"name": "Text Rule", "w": 1, "tags": ["editorial", "graphic", "calm"], "ae": "verticalStrip", "subtle": true, "layer": "front", "pack": "typo", "set": "typo"}, "tyTypeScale": {"name": "Type Scale", "w": 0.8, "tags": ["editorial", "graphic"], "ae": "indexNum", "layer": "front", "pack": "typo", "set": "typo"}, "tyBigPunct": {"name": "Big Quote Marks", "w": 0.9, "tags": ["editorial", "emotional", "graphic"], "ae": "bracketsJP", "layer": "front", "pack": "typo", "set": "typo"}}, "treat": {"none": {"name": "None"}, "hrInkBleed": {"name": "Bleeding Ink", "w": 0.8, "tags": ["horror", "emotional"], "ae": "softShadow", "pack": "horror", "set": "horror"}, "hrEroded": {"name": "Eroded", "w": 0.8, "tags": ["horror", "editorial", "graphic"], "ae": "halftone", "pack": "horror", "set": "horror"}, "hrRedact": {"name": "Redacted", "w": 0.7, "tags": ["horror", "editorial", "graphic"], "ae": "boxed", "pack": "horror", "set": "horror"}, "hrDoubleExp": {"name": "Double Exposure", "w": 0.8, "tags": ["horror", "emotional", "glitch"], "ae": "echoOutline", "pack": "horror", "set": "horror"}, "knWordScale": {"name": "Big and Small Words", "w": 0.9, "tags": ["pop", "graphic", "editorial"], "ae": "sizeWave", "safe": true, "pack": "kinetic", "set": "kinetic"}, "knWordPlate": {"name": "Word Knockout", "w": 0.8, "tags": ["pop", "graphic", "glitch"], "ae": "boxed", "pack": "kinetic", "set": "kinetic"}, "outline": {"name": "Hollow letters", "w": 1.2, "tags": ["graphic", "pop", "glitch", "emotional"], "pack": "looks"}, "outlineFill": {"name": "Outlined text", "w": 1, "tags": ["pop", "graphic"], "pack": "looks"}, "doubleOutline": {"name": "Double outline", "w": 0.7, "tags": ["pop", "graphic"], "pack": "looks"}, "extrude": {"name": "Extruded text", "w": 0.9, "tags": ["pop", "graphic"], "pack": "looks"}, "longShadow": {"name": "Long shadow", "w": 0.6, "tags": ["pop", "graphic"], "pack": "looks"}, "hardShadow": {"name": "Hard shadow", "w": 1, "tags": ["pop", "graphic", "glitch"], "pack": "looks"}, "softShadow": {"name": "Soft shadow", "w": 0.8, "tags": ["calm", "emotional", "editorial"], "safe": true, "pack": "looks"}, "glow": {"name": "Glow", "w": 0.9, "tags": ["emotional", "calm", "glitch"], "safe": true, "pack": "looks"}, "marker": {"name": "Marker highlight", "w": 1, "tags": ["pop", "graphic", "editorial"], "pack": "looks"}, "underline": {"name": "Underline", "w": 0.8, "tags": ["editorial", "calm", "graphic"], "pack": "looks"}, "strike": {"name": "Strikethrough", "w": 0.5, "tags": ["glitch", "editorial", "emotional"], "pack": "looks"}, "boxed": {"name": "Boxed text", "w": 0.7, "tags": ["graphic", "editorial", "pop"], "pack": "looks"}, "gradientV": {"name": "Vertical gradient", "w": 0.9, "tags": ["emotional", "pop"], "pack": "looks"}, "splitColor": {"name": "Two tone split", "w": 0.8, "tags": ["pop", "graphic"], "pack": "looks"}, "halftone": {"name": "Halftone", "w": 0.7, "tags": ["pop", "graphic"], "pack": "looks"}, "stripes": {"name": "Stripes", "w": 0.6, "tags": ["pop", "graphic"], "pack": "looks"}, "hatch": {"name": "Hatch", "w": 0.5, "tags": ["graphic", "editorial", "glitch"], "pack": "looks"}, "dotted": {"name": "Dotted outline", "w": 0.5, "tags": ["calm", "editorial", "graphic"], "pack": "looks"}, "alternate": {"name": "Alternating colors", "w": 0.8, "tags": ["pop", "graphic"], "pack": "looks"}, "italic": {"name": "Italic", "w": 0.8, "tags": ["editorial", "pop", "emotional"], "safe": true, "pack": "looks"}, "wide": {"name": "Wide letters", "w": 0.7, "tags": ["graphic", "pop"], "safe": true, "pack": "looks"}, "tall": {"name": "Tall letters", "w": 0.7, "tags": ["editorial", "calm", "graphic", "emotional"], "safe": true, "pack": "looks"}, "echoOutline": {"name": "Echoed outline", "w": 0.7, "tags": ["glitch", "emotional", "graphic"], "pack": "looks"}, "emphasisDots": {"name": "Emphasis dots", "w": 0.8, "tags": ["editorial", "emotional", "calm"], "pack": "looks"}, "neonOutline": {"name": "Neon tubing", "w": 0.8, "tags": ["glitch", "emotional", "pop"], "extra": true, "pack": "treattrans"}, "chrome": {"name": "Chrome", "w": 0.7, "tags": ["pop", "graphic"], "extra": true, "pack": "treattrans"}, "rainbow": {"name": "Rainbow", "w": 0.7, "tags": ["pop", "emotional"], "extra": true, "pack": "treattrans"}, "glitchSplit": {"name": "Misregistered colors", "w": 0.8, "tags": ["glitch", "pop"], "extra": true, "pack": "treattrans"}, "shadowStack": {"name": "Shadow stack", "w": 0.8, "tags": ["pop", "graphic"], "extra": true, "pack": "treattrans"}, "stencilGap": {"name": "Stencil gaps", "w": 0.6, "tags": ["graphic", "editorial", "glitch"], "extra": true, "pack": "treattrans"}, "waterline": {"name": "Waterline", "w": 0.6, "tags": ["emotional", "pop", "calm"], "extra": true, "pack": "treattrans"}, "karaoke": {"name": "Karaoke", "w": 0.9, "tags": ["emotional", "pop", "editorial"], "extra": true, "pack": "treattrans"}, "sizeWave": {"name": "Rhythmic letter sizes", "w": 0.7, "tags": ["pop", "graphic", "editorial"], "extra": true, "pack": "treattrans"}, "rotateAlt": {"name": "Alternating angles", "w": 0.7, "tags": ["pop", "emotional"], "extra": true, "safe": true, "pack": "treattrans"}, "baselineShift": {"name": "Staggered baseline", "w": 0.7, "tags": ["pop", "graphic"], "extra": true, "safe": true, "pack": "treattrans"}, "fauxBold": {"name": "Extra bold", "w": 0.5, "tags": ["graphic", "pop", "editorial"], "extra": true, "safe": true, "pack": "treattrans"}, "circled": {"name": "Circled letters", "w": 0.6, "tags": ["pop", "graphic", "editorial"], "extra": true, "pack": "treattrans"}, "bracketsQuote": {"name": "Quotation marks", "w": 0.6, "tags": ["editorial", "emotional", "calm"], "extra": true, "pack": "treattrans"}, "reflection": {"name": "Reflection", "w": 0.6, "tags": ["emotional", "calm", "editorial"], "extra": true, "pack": "treattrans"}, "inline": {"name": "Inline", "w": 0.6, "tags": ["editorial", "pop", "graphic"], "extra": true, "pack": "treattrans"}, "sticker": {"name": "Sticker outline", "w": 0.8, "tags": ["pop", "graphic"], "extra": true, "pack": "treattrans"}, "gradientSweep": {"name": "Gradient sweep", "w": 0.7, "tags": ["pop", "emotional"], "extra": true, "pack": "treattrans"}, "kerningWide": {"name": "Wide letter spacing", "w": 0.6, "tags": ["editorial", "calm", "emotional"], "extra": true, "safe": true, "pack": "treattrans"}, "monoGrid": {"name": "Manuscript grid", "w": 0.5, "tags": ["editorial", "calm", "emotional"], "extra": true, "wa": true, "pack": "treattrans"}, "outlineOffset": {"name": "Offset outline", "w": 0.8, "tags": ["pop", "graphic", "editorial"], "extra": true, "pack": "treattrans"}, "toneShadow": {"name": "Halftone shadow", "w": 0.7, "tags": ["pop", "graphic", "editorial"], "extra": true, "pack": "treattrans"}, "fadeChars": {"name": "Fading letters", "w": 0.5, "tags": ["emotional", "calm"], "extra": true, "safe": true, "pack": "treattrans"}, "cutShift": {"name": "Offset cut", "w": 0.7, "tags": ["graphic", "glitch", "pop"], "extra": true, "pack": "treattrans"}, "focusPull": {"name": "Focus shift", "w": 0.6, "tags": ["emotional", "calm", "editorial"], "extra": true, "pack": "treattrans"}, "spotChar": {"name": "Highlighted character", "w": 0.7, "tags": ["pop", "graphic", "editorial", "emotional"], "extra": true, "pack": "treattrans"}, "ransom": {"name": "Cutout letters", "w": 0.6, "tags": ["pop", "glitch", "graphic"], "extra": true, "pack": "treattrans"}, "tyHollowKey": {"name": "Hollow Key Glyph", "w": 0.9, "tags": ["graphic", "editorial", "pop"], "ae": "spotChar", "safe": true, "pack": "typo", "set": "typo"}, "tyHeadRules": {"name": "Head & Foot Rules", "w": 0.8, "tags": ["editorial", "graphic", "calm"], "ae": "underline", "pack": "typo", "set": "typo"}, "tyHeadBig": {"name": "Large Initial", "w": 0.9, "tags": ["editorial", "pop", "emotional"], "ae": "sizeWave", "pack": "typo", "set": "typo"}, "tyIndexSup": {"name": "Glyph Numbers", "w": 0.7, "tags": ["editorial", "graphic"], "ae": "emphasisDots", "pack": "typo", "set": "typo"}}, "bg": {"none": {"name": "Solid color"}, "auroraRibbons": {"name": "Aurora ribbons", "w": 0.9, "tags": ["emotional", "calm"], "extra": true, "pack": "bgcamB"}, "meshBlobs": {"name": "Mesh blobs", "w": 1, "tags": ["calm", "emotional", "pop"], "extra": true, "subtle": true, "pack": "bgcamB"}, "duotoneSweep": {"name": "Duotone sweep", "w": 0.9, "tags": ["calm", "pop", "graphic", "emotional"], "extra": true, "subtle": true, "pack": "bgcamB"}, "horizonGlow": {"name": "Horizon glow", "w": 0.8, "tags": ["emotional", "calm", "editorial"], "extra": true, "pack": "bgcamB"}, "seigaiha": {"name": "Seigaiha waves", "w": 0.8, "tags": ["calm", "editorial", "graphic"], "extra": true, "wa": true, "pack": "bgcamB"}, "asanoha": {"name": "Asanoha pattern", "w": 0.8, "tags": ["calm", "editorial", "graphic", "emotional"], "extra": true, "wa": true, "pack": "bgcamB"}, "houndstooth": {"name": "Houndstooth", "w": 0.6, "tags": ["graphic", "editorial", "pop"], "extra": true, "pack": "bgcamB"}, "herringbone": {"name": "Herringbone", "w": 0.6, "tags": ["editorial", "calm", "graphic"], "extra": true, "pack": "bgcamB"}, "argyle": {"name": "Argyle", "w": 0.6, "tags": ["pop", "graphic", "editorial"], "extra": true, "pack": "bgcamB"}, "tartan": {"name": "Tartan", "w": 0.6, "tags": ["pop", "calm", "editorial"], "extra": true, "pack": "bgcamB"}, "chevron": {"name": "Chevron", "w": 0.6, "tags": ["pop", "graphic"], "extra": true, "pack": "bgcamB"}, "isoCubes": {"name": "Iso cubes", "w": 0.6, "tags": ["graphic", "pop", "calm"], "extra": true, "pack": "bgcamB"}, "hexGrid": {"name": "Hex grid", "w": 0.8, "tags": ["graphic", "glitch", "calm"], "extra": true, "pack": "bgcamB"}, "triTess": {"name": "Tri tess", "w": 0.7, "tags": ["graphic", "calm", "emotional"], "extra": true, "pack": "bgcamB"}, "moire": {"name": "Moire", "w": 0.6, "tags": ["glitch", "graphic", "calm"], "extra": true, "pack": "bgcamB"}, "squareTunnel": {"name": "Square tunnel", "w": 0.6, "tags": ["glitch", "graphic", "pop"], "extra": true, "pack": "bgcamB"}, "spiralArms": {"name": "Spiral arms", "w": 0.6, "tags": ["glitch", "pop", "graphic"], "extra": true, "pack": "bgcamB"}, "topoLines": {"name": "Contour lines", "w": 0.8, "tags": ["calm", "editorial", "graphic"], "extra": true, "pack": "bgcamB"}, "ridgePlot": {"name": "Mountain ridges", "w": 0.7, "tags": ["editorial", "emotional", "calm"], "extra": true, "pack": "bgcamB"}, "starfield": {"name": "Starfield", "w": 0.9, "tags": ["emotional", "calm"], "extra": true, "pack": "bgcamB"}, "nightMoon": {"name": "Moonlit night", "w": 0.8, "tags": ["emotional", "calm", "editorial"], "extra": true, "pack": "bgcamB"}, "skyline": {"name": "Skyline", "w": 0.8, "tags": ["emotional", "editorial", "pop"], "extra": true, "pack": "bgcamB"}, "sunsetSun": {"name": "Sunset sun", "w": 0.8, "tags": ["emotional", "calm", "pop"], "extra": true, "pack": "bgcamB"}, "oceanWaves": {"name": "Ocean waves", "w": 0.8, "tags": ["calm", "emotional", "editorial"], "extra": true, "pack": "bgcamB"}, "rainWindow": {"name": "Rain window", "w": 0.8, "tags": ["emotional", "calm", "editorial"], "extra": true, "pack": "bgcamB"}, "snowLayers": {"name": "Snow layers", "w": 0.9, "tags": ["calm", "emotional"], "extra": true, "pack": "bgcamB"}, "fireworks": {"name": "Fireworks", "w": 0.7, "tags": ["pop", "emotional"], "extra": true, "pack": "bgcamB"}, "cloudLayers": {"name": "Cloud layers", "w": 0.8, "tags": ["calm", "emotional", "pop"], "extra": true, "pack": "bgcamB"}, "mountains": {"name": "Mountains", "w": 0.8, "tags": ["calm", "emotional", "editorial"], "extra": true, "pack": "bgcamB"}, "filmStrip": {"name": "Filmstrip", "w": 0.7, "tags": ["editorial", "emotional", "glitch"], "extra": true, "pack": "bgcamB"}, "vhsBand": {"name": "VHS noise band", "w": 0.6, "tags": ["glitch", "emotional"], "extra": true, "pack": "bgcamB"}, "tornPaper": {"name": "Torn paper", "w": 0.7, "tags": ["editorial", "emotional", "pop"], "extra": true, "pack": "bgcamB"}, "godRays": {"name": "Light rays", "w": 0.8, "tags": ["emotional", "calm", "editorial"], "extra": true, "pack": "bgcamB"}, "vignettePulse": {"name": "Pulsing vignette", "w": 0.9, "tags": ["emotional", "calm", "pop"], "extra": true, "subtle": true, "pack": "bgcamB"}, "kaleidoscope": {"name": "Kaleidoscope", "w": 0.6, "tags": ["pop", "glitch", "emotional"], "extra": true, "pack": "bgcamB"}, "marble": {"name": "Marble", "w": 0.7, "tags": ["calm", "editorial", "emotional"], "extra": true, "pack": "bgcamB"}, "paperCut": {"name": "Paper cutout", "w": 0.7, "tags": ["pop", "emotional", "calm"], "extra": true, "pack": "bgcamB"}, "hrFailingLamp": {"name": "Failing Lamp", "w": 0.9, "tags": ["horror", "emotional"], "ae": "vignettePulse", "subtle": true, "pack": "horror", "set": "horror"}, "hrCorridor": {"name": "Dark Corridor", "w": 0.8, "tags": ["horror", "graphic"], "ae": "squareTunnel", "pack": "horror", "set": "horror"}, "hrMold": {"name": "Spreading Stains", "w": 0.7, "tags": ["horror", "emotional"], "ae": "meshBlobs", "subtle": true, "pack": "horror", "set": "horror"}, "hrDeadTrees": {"name": "Dead Forest", "w": 0.7, "tags": ["horror", "calm", "emotional"], "ae": "mountains", "pack": "horror", "set": "horror"}, "sunburst": {"name": "Sunburst", "w": 0.8, "tags": ["pop", "graphic"], "pack": "looks"}, "concentric": {"name": "Concentric", "w": 0.9, "tags": ["calm", "graphic", "emotional"], "pack": "looks"}, "halftoneFade": {"name": "Halftone fade", "w": 1, "tags": ["pop", "graphic", "editorial"], "pack": "looks"}, "bigStripes": {"name": "Big stripes", "w": 0.9, "tags": ["graphic", "pop"], "pack": "looks"}, "splitV": {"name": "Vertical color split", "w": 1, "tags": ["graphic", "editorial", "pop"], "pack": "looks"}, "splitH": {"name": "Horizontal color split", "w": 0.9, "tags": ["graphic", "editorial", "calm"], "pack": "looks"}, "splitDiag": {"name": "Diagonal color split", "w": 1, "tags": ["graphic", "pop"], "pack": "looks"}, "gradientSweep": {"name": "Gradient sweep", "w": 1, "tags": ["calm", "emotional", "pop"], "subtle": true, "pack": "looks"}, "spotlight": {"name": "Spotlight", "w": 0.9, "tags": ["emotional", "calm", "editorial"], "subtle": true, "pack": "looks"}, "tvBars": {"name": "TV color bars", "w": 0.6, "tags": ["glitch", "graphic", "emotional"], "pack": "looks"}, "checker": {"name": "Checker", "w": 0.7, "tags": ["graphic", "pop"], "pack": "looks"}, "bigChar": {"name": "Oversized letters", "w": 1, "tags": ["editorial", "emotional", "graphic", "calm"], "subtle": true, "pack": "looks"}, "speedLines": {"name": "Speed lines", "w": 0.7, "tags": ["pop", "emotional", "graphic"], "pack": "looks"}, "scanBars": {"name": "Scan bars", "w": 0.9, "tags": ["calm", "glitch", "editorial"], "subtle": true, "pack": "looks"}, "dotGrid": {"name": "Dot grid", "w": 1, "tags": ["calm", "editorial", "graphic"], "subtle": true, "pack": "looks"}, "retroGrid": {"name": "Retro grid", "w": 0.6, "tags": ["pop", "glitch", "graphic"], "pack": "looks"}, "bokehBg": {"name": "Bokeh", "w": 1, "tags": ["emotional", "calm", "pop"], "pack": "looks"}, "particlesBg": {"name": "Floating particles", "w": 1, "tags": ["emotional", "calm", "pop"], "pack": "looks"}, "ripples": {"name": "Ripples", "w": 0.9, "tags": ["calm", "emotional", "graphic"], "pack": "looks"}, "polka": {"name": "Polka", "w": 0.7, "tags": ["pop", "graphic"], "pack": "looks"}, "eqBars": {"name": "Equalizer bars", "w": 0.6, "tags": ["pop", "glitch", "graphic"], "pack": "looks"}, "borderFrame": {"name": "Border frame", "w": 0.7, "tags": ["graphic", "pop", "editorial"], "pack": "looks"}, "letterbox": {"name": "Cinema letterbox", "w": 0.6, "tags": ["emotional", "editorial", "calm"], "pack": "looks"}, "noiseField": {"name": "Moving noise", "w": 0.6, "tags": ["glitch", "emotional"], "pack": "looks"}}, "cam": {"push": {"name": "Slow push in", "w": 5, "tags": ["calm", "editorial", "emotional", "graphic", "pop", "glitch"]}, "orbitDrift": {"name": "Orbit drift", "w": 0.8, "tags": ["calm", "emotional", "graphic"], "extra": true, "pack": "bgcamB"}, "barrelRoll": {"name": "Barrel roll", "w": 0.5, "tags": ["pop", "glitch", "graphic"], "extra": true, "strong": true, "pack": "bgcamB"}, "pendulumSway": {"name": "Pendulum sway", "w": 0.7, "tags": ["emotional", "pop", "calm"], "extra": true, "pack": "bgcamB"}, "focusIn": {"name": "Focus in", "w": 0.9, "tags": ["emotional", "calm", "editorial"], "extra": true, "pack": "bgcamB"}, "rackFocus": {"name": "Rack focus", "w": 0.6, "tags": ["emotional", "calm", "editorial"], "extra": true, "pack": "bgcamB"}, "earthquake": {"name": "Earthquake", "w": 0.5, "tags": ["glitch", "pop", "emotional"], "extra": true, "strong": true, "pack": "bgcamB"}, "floatNoise": {"name": "Floating camera", "w": 0.9, "tags": ["calm", "emotional"], "extra": true, "pack": "bgcamB"}, "vertigo": {"name": "Dolly zoom", "w": 0.6, "tags": ["emotional", "glitch"], "extra": true, "pack": "bgcamB"}, "tiltDown": {"name": "Tilt down", "w": 0.8, "tags": ["calm", "editorial", "emotional"], "extra": true, "pack": "bgcamB"}, "spiralIn": {"name": "Spiral zoom", "w": 0.6, "tags": ["pop", "graphic", "emotional"], "extra": true, "pack": "bgcamB"}, "snapPan": {"name": "Snap pan", "w": 0.7, "tags": ["pop", "glitch", "graphic"], "extra": true, "pack": "bgcamB"}, "jelly": {"name": "Elastic wobble", "w": 0.7, "tags": ["pop", "graphic"], "extra": true, "pack": "bgcamB"}, "hrNervous": {"name": "Frightened Handheld", "w": 0.9, "tags": ["horror", "glitch", "emotional"], "ae": "handheld", "pack": "horror", "set": "horror"}, "hrDutchSnap": {"name": "Sudden Tilt", "w": 0.7, "tags": ["horror", "emotional", "graphic"], "ae": "dutch", "strong": true, "pack": "horror", "set": "horror"}, "knReadPan": {"name": "Reading Pan", "w": 0.8, "tags": ["pop", "graphic", "editorial"], "ae": "snapPan", "pack": "kinetic", "set": "kinetic"}, "knTiltKick": {"name": "Tilt Kick", "w": 0.8, "tags": ["pop", "graphic", "emotional"], "ae": "dutch", "pack": "kinetic", "set": "kinetic"}, "knCardFlip": {"name": "Card Flip", "w": 0.7, "tags": ["pop", "graphic"], "ae": "barrelRoll", "strong": true, "pack": "kinetic", "set": "kinetic"}, "knShearKick": {"name": "Shear Kick", "w": 0.8, "tags": ["pop", "glitch", "graphic"], "ae": "jelly", "pack": "kinetic", "set": "kinetic"}, "knJumpCut": {"name": "Jump Cut", "w": 0.7, "tags": ["pop", "glitch", "editorial"], "ae": "stepZoom", "pack": "kinetic", "set": "kinetic"}, "knRushIn": {"name": "Rush In", "w": 0.7, "tags": ["pop", "graphic", "emotional"], "ae": "crashZoom", "strong": true, "pack": "kinetic", "set": "kinetic"}, "pullOut": {"name": "Pull out", "w": 1, "tags": ["calm", "emotional", "editorial"], "pack": "looks"}, "panL": {"name": "Pan left", "w": 0.9, "tags": ["calm", "editorial", "emotional", "graphic"], "pack": "looks"}, "panR": {"name": "Pan right", "w": 0.9, "tags": ["calm", "editorial", "emotional", "graphic"], "pack": "looks"}, "tiltUp": {"name": "Tilt up", "w": 0.8, "tags": ["calm", "emotional", "editorial"], "pack": "looks"}, "dutch": {"name": "Dutch angle", "w": 0.8, "tags": ["emotional", "glitch", "graphic"], "pack": "looks"}, "handheld": {"name": "Handheld", "w": 1, "tags": ["emotional", "calm", "editorial"], "pack": "looks"}, "beatPunch": {"name": "Zoom on beat", "w": 0.9, "tags": ["pop", "glitch", "graphic"], "pack": "looks"}, "whipIn": {"name": "Whip in", "w": 0.7, "tags": ["pop", "glitch", "graphic"], "strong": true, "pack": "looks"}, "crashZoom": {"name": "Crash zoom", "w": 0.6, "tags": ["pop", "glitch", "emotional"], "strong": true, "pack": "looks"}, "bounce": {"name": "Bounce", "w": 0.9, "tags": ["pop", "graphic"], "pack": "looks"}, "roll": {"name": "Roll", "w": 0.8, "tags": ["emotional", "calm", "glitch"], "pack": "looks"}, "driftDiag": {"name": "Diagonal drift", "w": 1, "tags": ["calm", "emotional", "editorial", "graphic"], "pack": "looks"}, "shakeHard": {"name": "Strong shake", "w": 0.6, "tags": ["glitch", "pop", "emotional"], "strong": true, "pack": "looks"}, "dollyIn": {"name": "Dolly in", "w": 1, "tags": ["emotional", "calm", "editorial"], "pack": "looks"}, "stepZoom": {"name": "Stepped zoom", "w": 0.8, "tags": ["pop", "graphic", "glitch"], "pack": "looks"}}, "fx": {"chroma": {"name": "Chromatic jump", "tags": ["glitch", "emotional", "pop", "graphic"], "builtin": true}, "shake": {"name": "Shake", "tags": ["pop", "glitch", "emotional"], "builtin": true}, "slice": {"name": "Slice glitch", "tags": ["glitch"], "builtin": true}, "block": {"name": "Block glitch", "tags": ["glitch"], "builtin": true}, "invert": {"name": "Invert", "tags": ["glitch", "graphic"], "builtin": true}, "flash": {"name": "Flash", "tags": ["pop", "emotional", "glitch"], "builtin": true}, "zoom": {"name": "Zoom blur", "tags": ["pop", "emotional"], "builtin": true}, "mosaic": {"name": "Mosaic", "tags": ["glitch"], "builtin": true}, "radialChroma": {"name": "Radial color fringing", "w": 0.9, "tags": ["glitch", "emotional", "pop"], "extra": true, "ae": "chroma", "mid": true, "dur": 4, "amp": 1, "scratch": true, "pack": "fxB"}, "bloomFlash": {"name": "Bloom flash", "w": 1, "tags": ["pop", "emotional", "calm"], "extra": true, "ae": "flash", "mid": true, "dur": 6, "amp": 1, "scratch": true, "pack": "fxB"}, "bulge": {"name": "Fisheye distortion", "w": 0.8, "tags": ["pop", "graphic", "glitch"], "extra": true, "ae": "zoom", "mid": true, "dur": 5, "amp": 1, "scratch": true, "pack": "fxB"}, "pixelSort": {"name": "Pixel sort", "w": 0.8, "tags": ["glitch"], "extra": true, "ae": "slice", "mid": true, "glitchy": true, "dur": 3, "amp": 1, "scratch": true, "pack": "fxB"}, "interlace": {"name": "Interlace", "w": 0.7, "tags": ["glitch", "emotional"], "extra": true, "ae": "slice", "mid": true, "glitchy": true, "dur": 3, "amp": 1, "scratch": true, "pack": "fxB"}, "macroBlock": {"name": "Compression blocks", "w": 0.8, "tags": ["glitch"], "extra": true, "ae": "block", "mid": true, "glitchy": true, "dur": 3, "amp": 1, "scratch": true, "pack": "fxB"}, "halftone": {"name": "Halftone", "w": 0.9, "tags": ["pop", "graphic", "editorial"], "extra": true, "ae": "mosaic", "mid": true, "dur": 5, "amp": 1, "pack": "fxB"}, "duotone": {"name": "Duotone", "w": 0.8, "tags": ["pop", "emotional", "graphic"], "extra": true, "ae": "chroma", "mid": true, "dur": 4, "amp": 1, "pack": "fxB"}, "ditherBit": {"name": "1 bit dither", "w": 0.7, "tags": ["glitch", "graphic", "pop"], "extra": true, "ae": "mosaic", "mid": true, "dur": 3, "amp": 1, "scratch": true, "pack": "fxB"}, "rotateSnap": {"name": "Angle snap", "w": 0.9, "tags": ["pop", "graphic", "glitch"], "extra": true, "ae": "shake", "mid": true, "dur": 5, "amp": 1, "scratch": true, "pack": "fxB"}, "echoFrames": {"name": "Frame echoes", "w": 0.9, "tags": ["emotional", "pop", "glitch"], "extra": true, "ae": "zoom", "mid": true, "dur": 6, "amp": 1, "scratch": true, "pack": "fxB"}, "kaleido": {"name": "Kaleidoscope", "w": 0.6, "tags": ["pop", "graphic", "emotional"], "extra": true, "ae": "block", "mid": true, "dur": 4, "amp": 1, "scratch": true, "pack": "fxB"}, "bandInvert": {"name": "Inverted band", "w": 0.7, "tags": ["glitch", "graphic"], "extra": true, "ae": "invert", "mid": true, "glitchy": true, "dur": 3, "amp": 1, "pack": "fxB"}, "lightRays": {"name": "Light rays", "w": 0.9, "tags": ["emotional", "pop", "calm"], "extra": true, "ae": "flash", "mid": true, "dur": 8, "amp": 1, "pack": "fxB"}, "anamorphic": {"name": "Anamorphic flare", "w": 0.8, "tags": ["emotional", "pop", "calm"], "extra": true, "ae": "flash", "mid": true, "dur": 7, "amp": 1, "pack": "fxB"}, "heartbeat": {"name": "Heartbeat", "w": 0.8, "tags": ["emotional", "calm"], "extra": true, "ae": "zoom", "mid": true, "dur": 10, "amp": 1, "scratch": true, "pack": "fxB"}, "tvStatic": {"name": "TV static", "w": 0.6, "tags": ["glitch", "emotional"], "extra": true, "ae": "block", "glitchy": true, "dur": 5, "amp": 1, "pre": 2, "pack": "fxB"}, "dustScratches": {"name": "Film scratches", "w": 0.8, "tags": ["emotional", "calm", "editorial"], "extra": true, "mid": true, "dur": 8, "amp": 1, "pack": "fxB"}, "filmAdvance": {"name": "Film advance", "w": 0.6, "tags": ["emotional", "editorial", "calm"], "extra": true, "ae": "slice", "dur": 9, "amp": 1, "pre": 4, "scratch": true, "pack": "fxB"}, "perspectiveTilt": {"name": "Perspective tilt", "w": 0.8, "tags": ["pop", "graphic", "emotional"], "extra": true, "ae": "shake", "mid": true, "dur": 8, "amp": 1, "scratch": true, "pack": "fxB"}, "ripple": {"name": "Ripple", "w": 0.8, "tags": ["emotional", "calm", "pop"], "extra": true, "ae": "zoom", "mid": true, "dur": 10, "amp": 1, "scratch": true, "pack": "fxB"}, "focusLines": {"name": "Focus lines", "w": 0.9, "tags": ["pop", "graphic", "emotional"], "extra": true, "ae": "zoom", "mid": true, "dur": 6, "amp": 1, "pack": "fxB"}, "speedLines": {"name": "Speed lines", "w": 0.8, "tags": ["pop", "graphic"], "extra": true, "mid": true, "dur": 6, "amp": 1, "pack": "fxB"}, "starGlint": {"name": "Star glint", "w": 0.8, "tags": ["pop", "emotional"], "extra": true, "ae": "flash", "mid": true, "dur": 9, "amp": 1, "pack": "fxB"}, "colorBars": {"name": "Color bars", "w": 0.8, "tags": ["pop", "graphic", "glitch"], "extra": true, "ae": "slice", "mid": true, "dur": 6, "amp": 1, "pack": "fxB"}, "zoomStutter": {"name": "Zoom stutter", "w": 0.8, "tags": ["pop", "graphic", "glitch"], "extra": true, "ae": "zoom", "mid": true, "dur": 6, "amp": 1, "scratch": true, "pack": "fxB"}, "negativeRing": {"name": "Inverted ring", "w": 0.8, "tags": ["graphic", "pop", "glitch"], "extra": true, "ae": "invert", "mid": true, "dur": 6, "amp": 1, "pack": "fxB"}, "edgeDetect": {"name": "Edge detect", "w": 0.7, "tags": ["graphic", "glitch", "editorial"], "extra": true, "ae": "invert", "mid": true, "dur": 4, "amp": 1, "scratch": true, "pack": "fxB"}, "shatter": {"name": "Glass shatter", "w": 0.5, "tags": ["glitch", "pop", "emotional"], "extra": true, "ae": "block", "dur": 8, "amp": 1, "pre": 1, "scratch": true, "pack": "fxB"}, "defocus": {"name": "Out of focus", "w": 0.9, "tags": ["emotional", "calm", "editorial"], "extra": true, "ae": "zoom", "mid": true, "dur": 8, "amp": 1, "pre": 4, "scratch": true, "pack": "fxB"}, "snapshot": {"name": "Snapshot", "w": 0.6, "tags": ["pop", "emotional", "editorial"], "extra": true, "ae": "flash", "mid": true, "dur": 10, "amp": 1, "scratch": true, "pack": "fxB"}, "squash": {"name": "Squash", "w": 0.8, "tags": ["pop", "graphic"], "extra": true, "ae": "zoom", "mid": true, "dur": 6, "amp": 1, "scratch": true, "pack": "fxB"}, "scanBar": {"name": "Scan bar", "w": 0.7, "tags": ["graphic", "editorial", "glitch"], "extra": true, "ae": "flash", "mid": true, "dur": 8, "amp": 1, "scratch": true, "pack": "fxB"}, "loopScroll": {"name": "Loop scroll", "w": 0.7, "tags": ["pop", "graphic", "glitch"], "extra": true, "ae": "slice", "dur": 6, "amp": 1, "pre": 3, "scratch": true, "pack": "fxB"}, "hrSubliminal": {"name": "Subliminal", "w": 0.6, "tags": ["horror", "glitch"], "ae": "invert", "mid": true, "glitchy": true, "dur": 2, "amp": 1, "scratch": true, "pack": "horror", "set": "horror"}, "hrSignalLoss": {"name": "Signal Loss", "w": 0.7, "tags": ["horror", "glitch", "editorial"], "ae": "blackFrame", "glitchy": true, "dur": 9, "amp": 1, "pre": 3, "scratch": true, "pack": "horror", "set": "horror"}, "hrPassingShadow": {"name": "Passing Shadow", "w": 0.5, "tags": ["horror", "emotional"], "ae": "flash", "mid": true, "dur": 5, "amp": 1, "pack": "horror", "set": "horror"}, "panelWipe": {"name": "Panel wipe", "w": 1, "tags": ["pop", "graphic"], "dur": 5, "amp": 1, "pre": 2, "pack": "looks", "ae": "flash"}, "irisTrans": {"name": "Iris trans", "w": 0.7, "tags": ["pop", "editorial"], "dur": 8, "amp": 1, "pre": 4, "pack": "looks", "ae": "zoom"}, "doors": {"name": "Doors", "w": 0.7, "tags": ["graphic", "pop"], "dur": 8, "amp": 1, "pre": 4, "pack": "looks", "ae": "slice"}, "blindsTrans": {"name": "Blinds trans", "w": 0.7, "tags": ["graphic", "editorial"], "dur": 6, "amp": 1, "pre": 3, "pack": "looks", "ae": "slice"}, "rgbSplit": {"name": "RGB separation", "w": 1, "tags": ["glitch", "emotional"], "mid": true, "glitchy": true, "dur": 4, "amp": 1, "scratch": true, "pack": "looks", "ae": "chroma"}, "smear": {"name": "Smear", "w": 0.8, "tags": ["glitch"], "mid": true, "glitchy": true, "dur": 3, "amp": 1, "scratch": true, "pack": "looks", "ae": "slice"}, "vhsRoll": {"name": "VHS roll", "w": 0.8, "tags": ["glitch", "emotional"], "mid": true, "glitchy": true, "dur": 6, "amp": 1, "scratch": true, "pack": "looks", "ae": "slice"}, "trackingNoise": {"name": "Tracking noise", "w": 0.8, "tags": ["glitch", "emotional"], "mid": true, "glitchy": true, "dur": 4, "amp": 1, "scratch": true, "pack": "looks", "ae": "slice"}, "mirrorFlash": {"name": "Mirror flash", "w": 0.6, "tags": ["glitch", "graphic"], "mid": true, "glitchy": true, "dur": 2, "amp": 1, "scratch": true, "pack": "looks", "ae": "block"}, "strobe": {"name": "Strobe", "w": 0.5, "tags": ["glitch", "pop"], "mid": true, "dur": 4, "amp": 1, "pack": "looks", "ae": "invert"}, "posterize": {"name": "Posterization", "w": 0.7, "tags": ["glitch", "pop", "graphic"], "mid": true, "dur": 3, "amp": 1, "scratch": true, "pack": "looks", "ae": "mosaic"}, "hueShift": {"name": "Hue shift", "w": 0.7, "tags": ["glitch", "pop", "emotional"], "mid": true, "dur": 3, "amp": 1, "scratch": true, "pack": "looks", "ae": "chroma"}, "tileShift": {"name": "Tile shift", "w": 0.8, "tags": ["glitch", "graphic"], "mid": true, "glitchy": true, "dur": 3, "amp": 1, "scratch": true, "pack": "looks", "ae": "block"}, "filmBurn": {"name": "Film burn", "w": 0.7, "tags": ["emotional", "calm", "editorial"], "mid": true, "dur": 7, "amp": 1, "pre": 2, "pack": "looks", "ae": "flash"}, "whipBlur": {"name": "Whip blur", "w": 1, "tags": ["pop", "graphic", "emotional"], "dur": 5, "amp": 1, "pre": 2, "scratch": true, "pack": "looks", "ae": "zoom"}, "blackFrame": {"name": "Black frame", "w": 0.6, "tags": ["editorial", "emotional", "glitch"], "mid": false, "dur": 2, "amp": 1, "pre": 1, "pack": "looks", "ae": "invert"}, "whiteFrame": {"name": "White frame", "w": 0.5, "tags": ["emotional", "pop"], "mid": false, "dur": 2, "amp": 1, "pre": 1, "pack": "looks", "ae": "flash"}, "gridRepeat": {"name": "Grid repeat", "w": 0.7, "tags": ["pop", "graphic", "glitch"], "mid": true, "dur": 3, "amp": 1, "scratch": true, "pack": "looks", "ae": "block"}, "waveWarp": {"name": "Wave warp", "w": 0.7, "tags": ["emotional", "glitch"], "mid": true, "dur": 5, "amp": 1, "scratch": true, "pack": "looks", "ae": "slice"}, "pixelDrift": {"name": "Pixel drift", "w": 0.8, "tags": ["glitch"], "mid": true, "glitchy": true, "dur": 3, "amp": 1, "scratch": true, "pack": "looks", "ae": "slice"}, "zoomPunch": {"name": "Zoom punch", "w": 1, "tags": ["pop", "graphic", "glitch"], "mid": true, "dur": 4, "amp": 1, "scratch": true, "pack": "looks", "ae": "zoom"}, "lightSweep": {"name": "Light sweep", "w": 0.9, "tags": ["pop", "emotional", "calm"], "mid": true, "dur": 7, "amp": 1, "pack": "looks", "ae": "flash"}, "crtOff": {"name": "CRT powers off", "w": 0.3, "tags": ["glitch", "emotional"], "dur": 8, "amp": 1, "pre": 4, "scratch": true, "pack": "looks", "ae": "flash"}, "splitSlide": {"name": "Split slide", "w": 0.9, "tags": ["graphic", "pop", "glitch"], "mid": true, "dur": 6, "amp": 1, "pre": 2, "scratch": true, "pack": "looks", "ae": "slice"}}, "trans": {"hrStaticCut": {"name": "Static Cut", "w": 0.9, "tags": ["horror", "glitch"], "ae": "pixelate", "dur": 0.4, "pack": "horror", "set": "horror"}, "hrBlink": {"name": "Blink", "w": 0.8, "tags": ["horror", "emotional"], "ae": "irisOpen", "dur": 0.45, "pack": "horror", "set": "horror"}, "knCornerSwing": {"name": "Corner Swing", "w": 0.8, "tags": ["pop", "graphic"], "ae": "spinOut", "dur": 0.42, "pack": "kinetic", "set": "kinetic"}, "knStutterCut": {"name": "Stutter Cut", "w": 0.7, "tags": ["pop", "glitch", "graphic"], "ae": "flashCross", "dur": 0.36, "pack": "kinetic", "set": "kinetic"}, "knStripSlam": {"name": "Strip Slam", "w": 0.8, "tags": ["pop", "graphic"], "ae": "sliceShift", "dur": 0.45, "pack": "kinetic", "set": "kinetic"}, "wipe": {"name": "Edge wipe", "w": 1.2, "tags": ["graphic", "editorial", "pop"], "extra": true, "dur": 0.35, "pack": "treattrans"}, "diagonalWipe": {"name": "Diagonal band wipe", "w": 1, "tags": ["pop", "graphic"], "extra": true, "dur": 0.35, "pack": "treattrans"}, "clockWipe": {"name": "Clock wipe", "w": 0.7, "tags": ["graphic", "pop", "editorial"], "extra": true, "dur": 0.45, "pack": "treattrans"}, "irisOpen": {"name": "Iris opens", "w": 0.8, "tags": ["emotional", "pop", "editorial"], "extra": true, "dur": 0.4, "pack": "treattrans"}, "pushSlide": {"name": "Push", "w": 1, "tags": ["graphic", "pop", "editorial"], "extra": true, "dur": 0.35, "pack": "treattrans"}, "cover": {"name": "Cover", "w": 0.9, "tags": ["editorial", "graphic", "calm"], "extra": true, "dur": 0.35, "pack": "treattrans"}, "uncover": {"name": "Uncover", "w": 0.8, "tags": ["editorial", "calm", "emotional"], "extra": true, "dur": 0.35, "pack": "treattrans"}, "zoomThrough": {"name": "Zoom through", "w": 1, "tags": ["pop", "emotional", "glitch"], "extra": true, "dur": 0.35, "pack": "treattrans"}, "doorsOpen": {"name": "Double doors", "w": 0.7, "tags": ["graphic", "pop", "emotional"], "extra": true, "dur": 0.4, "pack": "treattrans"}, "blinds": {"name": "Blinds", "w": 0.7, "tags": ["graphic", "editorial", "calm"], "extra": true, "dur": 0.4, "pack": "treattrans"}, "checker": {"name": "Checkerboard", "w": 0.6, "tags": ["pop", "graphic"], "extra": true, "dur": 0.45, "pack": "treattrans"}, "blockDissolve": {"name": "Block dissolve", "w": 0.8, "tags": ["glitch", "graphic"], "extra": true, "dur": 0.4, "pack": "treattrans"}, "whipPan": {"name": "Whip pan", "w": 1, "tags": ["pop", "emotional", "glitch"], "extra": true, "dur": 0.3, "pack": "treattrans"}, "spinOut": {"name": "Spin out", "w": 0.6, "tags": ["pop", "glitch"], "extra": true, "dur": 0.45, "pack": "treattrans"}, "inkBlob": {"name": "Ink blot", "w": 0.7, "tags": ["emotional", "calm", "pop"], "extra": true, "dur": 0.5, "pack": "treattrans"}, "shatterTiles": {"name": "Shattering tiles", "w": 0.6, "tags": ["glitch", "pop", "emotional"], "extra": true, "dur": 0.5, "pack": "treattrans"}, "sliceShift": {"name": "Sliding strips", "w": 0.8, "tags": ["glitch", "graphic", "pop"], "extra": true, "dur": 0.35, "pack": "treattrans"}, "cubeTurn": {"name": "Cube turn", "w": 0.6, "tags": ["graphic", "pop"], "extra": true, "dur": 0.45, "pack": "treattrans"}, "flashCross": {"name": "Flash cut", "w": 0.9, "tags": ["emotional", "pop", "calm"], "extra": true, "dur": 0.3, "pack": "treattrans"}, "pixelate": {"name": "Pixel transition", "w": 0.6, "tags": ["glitch", "pop"], "extra": true, "dur": 0.4, "pack": "treattrans"}, "tyRuleWipe": {"name": "Ruled Line Wipe", "w": 0.9, "tags": ["editorial", "graphic", "calm"], "ae": "blinds", "dur": 0.45, "pack": "typo", "set": "typo"}, "tyGridCells": {"name": "Grid Cell Fill", "w": 0.8, "tags": ["editorial", "graphic"], "ae": "checker", "dur": 0.5, "pack": "typo", "set": "typo"}}}, "names": {"layout": {"center": "Center", "mixed": "Mixed", "vcols": "Vertical text", "marquee": "Scrolling banner", "tile": "Tiled text", "scatter": "Scatter", "ring": "Ring", "wave": "Wave", "huge": "Oversized text", "labels": "Labels", "condensed": "Condensed", "gloss": "Annotation", "type": "Type", "diag": "Diagonal band", "circle": "Circle", "stack": "Echo stack", "pill": "Pill", "hrFlashlight": "Flashlight", "hrDoorGap": "Door Gap", "hrWallScrawl": "Wall Scrawl", "hrCctv": "CCTV Monitor", "hrOuija": "Spirit Board", "hrMissing": "Missing Poster", "hrWrongOne": "The Wrong Glyph", "hrRisingDark": "Out of the Dark", "hrRedacted": "Redacted File", "hrStaticTv": "Static TV", "hrSpiritPhoto": "Spirit Photo", "hrWrongShadow": "Wrong Shadow", "knSlamStack": "Slam Stack", "knQuarterTurn": "Quarter Turn", "knSwapCenter": "Word Swap", "knZoomDive": "Dive Into Letter", "knFlowSnap": "Flow and Snap", "knSeesaw": "Seesaw", "knTypeSlam": "Type then Slam", "knRhythmCuts": "Word Jump Cuts", "knPathRide": "Loop Track", "knGearWords": "Gears", "knCollide": "Head-on Crash", "knTumble": "Tumbling Boxes", "knReflow": "Vertical to Line", "knPadGrid": "Drum Pads", "lowerThird": "Lower third", "corners": "Opposite corners", "staircase": "Staircase", "zigzag": "Zigzag", "arcTop": "Rainbow arc", "spiral": "Spiral", "gridCells": "Grid cells", "dropCap": "Drop cap", "justified": "Justified", "frameBox": "Picture frame", "bubble": "Bubble", "subtitleBar": "Subtitle bar", "ticker": "Ticker", "splitScreen": "Split screen", "mirror": "Mirror", "sideways": "Sideways text", "edgeFrame": "Edge frame", "perspective": "Perspective", "hanko": "Signature seal", "genkou": "Manuscript paper", "panels": "Comic panels", "filmstrip": "Filmstrip", "quote": "Quote", "ruler": "Dimension lines", "searchBar": "Search bar", "chat": "Chat", "notification": "Notification", "ticket": "Ticket", "rain": "Letter rain", "hanging": "Hanging", "orbit": "Orbit", "tunnel": "Tunnel", "wordCloud": "Word cloud", "bounceLine": "Bouncing line", "elastic": "Elastic", "crossBands": "Crossing bands", "stickerBomb": "Sticker collage", "neon": "Neon", "keycaps": "Keycaps", "bubbles": "Bubbles", "slotMachine": "Slot machine", "flipBoard": "Flip board", "credits": "End credits", "zoomRepeat": "Zoom repeat", "splitHalves": "Split halves", "columnsBig": "Contrasting columns", "circleWords": "Concentric words", "dotMatrix": "Dot matrix", "depthStack": "Depth stack", "typeSpecimen": "Type specimen", "kanjiFocus": "Character focus", "halfVertical": "Mixed vertical type", "curtain": "Curtain", "equalizer": "Equalizer", "tape": "Tape", "magazine": "Magazine spread", "headlineDeck": "Headline and deck", "contents": "Contents", "footnote": "Footnote", "proofread": "Proof sheet", "numbered": "Numbered", "poster": "Poster", "swissGrid": "Swiss grid", "dictionary": "Dictionary", "ema": "Ema wish plaque", "ransom": "Cutout letters", "newspaper": "Newspaper", "vinyl": "Vinyl record", "cassette": "Cassette", "bookSpine": "Book spine", "polaroid": "Polaroid", "stampSheet": "Stamp sheet", "postcard": "Postcard", "letterPaper": "Letter paper", "calendar": "Calendar", "chochin": "Paper lantern", "routeMap": "Route map", "stationSign": "Station sign", "noren": "Noren curtain", "tanzaku": "Wish strip", "omikuji": "Fortune slip", "kakejiku": "Hanging scroll", "shoji": "Shoji screen", "clapper": "Clapperboard", "warningLabel": "Warning label", "priceTag": "Price tag", "nameTag": "Name tag", "stickyNotes": "Sticky notes", "karuta": "Karuta card", "cube": "Cube", "cylinder": "Cylinder", "flipCards": "Flipping cards", "accordion": "Accordion", "flag": "Flag", "ribbon": "Ribbon", "pendulum": "Pendulum", "pile": "Letter pile", "blocks": "Blocks", "balloons": "Letter balloons", "magnets": "Magnets", "tiles": "Letter tiles", "bulbs": "Marquee bulbs", "ledScroll": "LED ticker", "billboard": "Billboard", "crowdBubbles": "Speech bubble crowd", "crossword": "Crossword", "wordSearch": "Word search", "puzzle": "Puzzle", "shadowPlay": "Shadow puppets", "kaleido": "Kaleidoscope", "dominoes": "Dominoes", "burst": "Burst", "fisheye": "Fisheye lens", "wall": "Perspective wall", "origami": "Origami", "zipper": "Zipper", "sliceStack": "Sliced layers", "glitchGrid": "Glitch grid", "mosaicTiles": "Mosaic tiles", "maskReveal": "Text window", "contour": "Contour", "halftoneBig": "Oversized halftone text", "stencil": "Stencil", "tyKeySplit": "Key Glyph Split", "tyCropGiant": "Cropped Giant", "tyCross": "Cross Setting", "tyBandHide": "Band Cover", "tyRuby": "Ruby Notes", "tyBaseline": "Ruled Lines", "tyScaleSteps": "Size Crescendo", "tyJustify": "Justified Stack", "tyIndexTable": "Glyph Table", "tySplitType": "Split Type", "tyErode": "Eroding Repeat", "tyVRuler": "Vertical Scale", "tyFullTrack": "Full-Width Tracking", "tyStatCount": "Glyph Count", "tyMargin": "White Space", "tyRotBlock": "Rotated Block", "tySquare": "Square Setting", "tyLineFocus": "Line Highlight"}, "enter": {"cut": "Cut", "assemble": "Break apart and assemble", "slice": "Slice", "type": "Type", "pop": "Pop", "drop": "Drop", "stretch": "Stretch", "wipe": "Wipe", "blur": "Blur", "spin": "Spin", "flicker": "Flicker", "scramble": "Scramble", "zoom": "Zoom", "riseMask": "Reveal from below", "dropMask": "Reveal from above", "slideL": "Slide from left", "slideR": "Slide from right", "slideWhole": "Slide in together", "flipX": "Flip on vertical axis", "flipY": "Flip on horizontal axis", "domino": "Domino", "fold": "Fold", "unroll": "Unroll", "strokeDraw": "Draw outlines, then fill", "outlineFill": "Outline to fill", "splitJoin": "Join from top and bottom", "vSlice": "Vertical slices", "shutter": "Shutter", "iris": "Iris", "diagWipe": "Diagonal wipe", "blinds": "Blinds", "checker": "Checker", "randomOrder": "Random letter order", "bounceBig": "Big bounce", "squashDrop": "Squash landing", "rubber": "Rubber", "glitchIn": "Glitch in", "echoIn": "Echoes converge", "whip": "Whip", "skewIn": "Skew in", "trackIn": "Tighten letter spacing", "trackOut": "Expand letter spacing", "blurStagger": "Staggered blur", "fadeStagger": "Letter by letter fade", "waveIn": "Wave in", "spiralIn": "Spiral assembly", "zoomOut": "Oversized to normal", "resolve": "Resolve", "magnet": "Magnet", "inkBleed": "Ink bleed", "neonOn": "Neon on", "cursorSweep": "Cursor sweep", "stamp": "Stamp", "springIn": "Spring in", "pendulum": "Pendulum", "rollIn": "Roll in", "slingshot": "Slingshot", "rockSettle": "Wobble to rest", "bounceBall": "Bounce ball", "snapRail": "Snap into alignment", "fanOpen": "Folding fan opens", "cylinder": "Cylinder", "shuffle": "Shuffle", "stopMotion": "Stop motion", "ripple": "Ripple", "zipper": "Zipper", "zoomAlt": "Zoom alt", "tiltUp": "Tilt up", "stickerPeel": "Apply sticker", "crumple": "Uncrumple", "noteUnfold": "Unfold letter", "tornJoin": "Join torn pieces", "splitFlap": "Split flap display", "overexpose": "Overexpose", "glint": "Glint", "loupe": "Loupe", "filmFeed": "Film feed", "backlight": "Backlight", "lightLeak": "Light leak", "heatHaze": "Heat haze", "crtOn": "CRT powers on", "interlace": "Interlace", "loadingBar": "Loading bar", "dither": "Dither", "odometer": "Rolling number drum", "matrixRain": "Data rain", "hatchFill": "Hatch fill", "brushReveal": "Brush reveal", "inkDrop": "Ink drop", "quarters": "Converge from four sides", "invertBox": "Invert box", "printRegister": "Color plates align", "echoCount": "Count in", "liquidFill": "Liquid rises", "windBlown": "Wind blown", "strokeOrder": "Stroke by stroke", "clockWipe": "Clock wipe", "shadowFirst": "Shadow first", "bubbles": "Bubbles", "tokoroten": "Extruded noodles", "hrBlinkCreep": "Between Blinks", "hrJumpScare": "Jump Scare", "hrUneasy": "Uneasy Timing", "hrVhold": "Vertical Hold", "hrMirrorSnap": "Mirror Writing", "hrManifest": "Manifest", "hrClawReveal": "Claw Marks", "knWordSlam": "Word Slam", "knTypeToSlam": "Type and Grow", "knReplaceIn": "Swap In", "knHingeDrop": "Hinge Drop", "knLoopIn": "Loop In", "knPushIn": "Push In", "knInertia": "Hard Brake", "knWordSpin": "Word Spin", "knDiveIn": "Words From Camera", "knStretchOut": "Tape Stretch", "tyKeyFirst": "Key Glyph First", "tyLineWipe": "Line-by-Line Wipe", "tyZoomOne": "One by One Zoom", "tyUnderLift": "Rise from Underline", "tyDotGrow": "Dot to Glyph", "tyBracketOpen": "Brackets Open", "tyRetype": "Retype", "tyRubyDrop": "From Ruby"}, "hold": {"still": "Still", "jitter": "Jitter", "drift": "Drift", "breathe": "Breathe", "wave": "Wave", "glitchtick": "Glitch tick", "float": "Float", "sway": "Sway", "pulse": "Pulse", "shimmer": "Shimmer", "colorRun": "Traveling color", "rotateSlow": "Rotate slow", "trackBreathe": "Breathing letter spacing", "skewWobble": "Skew wobble", "beatHop": "Hop to the beat", "hWave": "Horizontal wave", "heartbeat": "Heartbeat", "orbitSmall": "Small orbit", "jelly": "Jelly", "scanBand": "Scanning band", "noiseDrift": "Drifting noise", "tilt": "Tilt", "zoomSlow": "Slow push", "stretchPulse": "Stretch to the beat", "glitchJump": "Occasional glitch jump", "echoTrail": "Echo trail", "glowFlicker": "Flickering glow", "windGust": "Wind gust", "dangle": "Dangle", "eqBounce": "Audio level bounce", "flashBox": "Invert to the beat", "glintSweep": "Passing highlight", "flipSwap": "Occasional flip", "shadowSway": "Swaying shadow", "magnetJiggle": "Magnet jiggle", "typeRattle": "Typewriter rattle", "focusRack": "Rack focus", "pluckString": "Plucked string", "hrTwitch": "Twitch", "hrStare": "Staring Glyph", "hrLagOne": "The Late One", "hrFlickerLight": "Failing Light", "knWordPulse": "Word Pulse", "knCounterRock": "Counter Rock", "knWordRide": "Word Surf", "knTickShift": "Tick Shift", "knBeatLean": "Beat Lean", "knGapBreath": "Breathing Gaps", "tyKeyPulse": "Key Glyph Pulse", "tyReadCursor": "Reading Cursor", "tyOutlineBlink": "Outline Blink", "tyTrackStep": "Tracking Steps"}, "exit": {"cut": "Cut", "explode": "Explode", "fall": "Crumble and fall", "drift": "Drift away", "slice": "Slice", "wipe": "Wipe", "shrink": "Shrink", "blur": "Blur", "stretch": "Stretch", "scatter": "Scatter", "glitch": "Glitch", "sinkMask": "Sink out", "riseOut": "Rise out", "slideOutL": "Slide left", "slideOutR": "Slide right", "flipOutX": "Doors close", "flipOutY": "Flip down", "foldOut": "Fold out", "squash": "Squash", "trackOutWide": "Spread letter spacing", "collapse": "Collapse inward", "zoomThrough": "Zoom through", "zoomFar": "Recede into distance", "spinOut": "Spin away", "twist": "Twist", "waveOut": "Wave out", "blurOutStagger": "Blur letters in sequence", "undraw": "Return to outlines", "outlineOut": "Fill fades to outline", "irisClose": "Iris closes", "diagWipeOut": "Diag wipe out", "blindsClose": "Blinds close", "checkerOut": "Checker out", "splitApart": "Split top and bottom", "vSliceDrop": "Vertical slices fall", "melt": "Melt", "dissolve": "Crumble away", "backspace": "Backspace", "scrambleOut": "Become symbols", "glitchDissolve": "Glitch into blocks", "echoOut": "Echo out", "whipOut": "Whip out", "gravity": "Fall with gravity", "popOut": "Pop out", "burn": "Burn away", "sweepCover": "Covered by a bar", "shatterLite": "Shatter into quarters", "peelOff": "Peel off sticker", "crumpleOut": "Crumple and toss", "tearOut": "Tear away", "scorchOut": "Scorch away", "overexposeOut": "Blow out to white", "scanOut": "Scanline erase", "stripesOut": "Stripes out", "halftoneOut": "Dissolve to halftone", "eraserOut": "Chalkboard erase", "vacuumOut": "Vacuum to a point", "sandOut": "Scatter as sand", "shredOut": "Shred out", "dominoOut": "Domino out", "hingeOut": "Loose hinge", "rocketOff": "Rocket away", "bounceOff": "Bounce off", "balloonOff": "Float off like a balloon", "deflateOut": "Deflate and fly", "hazeOut": "Haze out", "glassBreak": "Break like glass", "zipOut": "Zip out", "clapShut": "Close at center", "lampOff": "Lights out", "slotOut": "Slot out", "clockOut": "Clock out", "matrixOut": "Digital rain", "tornadoOut": "Tornado", "rollUpOut": "Roll up out", "snakeOut": "Leave in a line", "flutterOut": "Flutter down", "rollOff": "Roll off", "fanClose": "Folding fan closes", "rgbSplitOut": "Split into RGB", "shockOut": "Shockwave", "floodOut": "Submerge", "slashOut": "Sliced in two", "mosaicOut": "Mosaic out", "scribbleOut": "Scribble away", "candleOut": "Blow out", "hrPulledDown": "Pulled Under", "hrLookBack": "One Remains", "hrTurnAway": "Turn Away", "hrShiver": "Shiver Out", "hrSwallow": "Swallowed", "hrFlickerDie": "Dying Flicker", "hrDrain": "Drain", "knWordKick": "Word Kick", "knPushOut": "Push Out", "knDiveGlyph": "Dive Into Letter", "knLaunch": "Launch", "knWordBlink": "Words Blink Out", "knCloseGap": "Close the Gaps", "knJumpCutOut": "Jump Cut", "knStackAway": "Stack and Drop", "tyStrike": "Strike Out", "tyToDot": "Back to Dots", "tyLineFeed": "Line Feed", "tyBracketClose": "Brackets Close", "tyToIndex": "Turn to Numbers", "tyKeyLast": "Key Glyph Last", "tyUnderSink": "Sink into Underline", "tyFoldVert": "Fold to Vertical"}, "decor": {"brackets": "Corner marks", "rings": "Coordinate rings", "dots": "Dotted ring", "arrows": "Arrows", "slash": "Slash", "sparks": "Sparks", "leaders": "Leader lines", "waveform": "Waveform", "barcode": "Barcode", "grid": "Grid", "stripes": "Stripes", "blobs": "Ink stains", "bars": "Rough bands", "shapes": "Shapes", "counter": "Large numbers", "crosshair": "Crosshair", "cropMarks": "Crop marks", "reticle": "Reticle", "radar": "Radar", "progressRing": "Progress ring", "timecodeBar": "Timecode bar", "rulerEdge": "Ruler edge", "dimension": "Dimension", "indexNum": "Sequence number", "dateStamp": "Photo date stamp", "qrBlock": "QR inspired blocks", "glitchRects": "Glitch rects", "concentricSquares": "Concentric squares", "triangleSpin": "Triangle spin", "lineBurst": "Line burst", "plusGrid": "Plus grid", "guides": "Guide lines", "waveLine": "Wave line", "spiralLine": "Spiral line", "halftonePatch": "Halftone patch", "checkerStrip": "Checker strip", "beatRing": "Beat ring", "orbitDots": "Orbit dots", "constellation": "Constellation", "confetti": "Confetti", "petals": "Petals", "rainStreaks": "Rain streaks", "snow": "Snow", "lightLeak": "Light leak", "bokeh": "Bokeh", "speedCorner": "Speed lines", "risingParticles": "Rising particles", "twinkle": "Twinkle", "brushStroke": "Brush stroke", "tapePieces": "Masking tape", "scribbleCircle": "Scribble circle", "scribbleUnder": "Hand drawn underline", "crossOut": "Editorial marks", "highlightMark": "Highlight mark", "heartsStars": "Hearts stars", "watermarkKanji": "Large watermark character", "verticalStrip": "Vertical text strip", "romajiLine": "Romanized line", "bracketsJP": "Japanese corner brackets", "seal": "Red seal", "kamon": "Family crest", "seigaiha": "Seigaiha wave pattern", "asanoha": "Asanoha hemp leaf pattern", "hanabi": "Fireworks", "chochin": "Paper lantern", "shimenawa": "Sacred rope", "sensu": "Folding fan", "tsukiKumo": "Moon and clouds", "momiji": "Maple leaves", "namiGashira": "Wave crest", "kasumi": "Mist", "hexGrid": "Hex grid", "spectrumRing": "Spectrum ring", "dataColumns": "Data columns", "spinner": "Spinner", "headingTape": "Heading tape", "glyphLock": "Glyph lock", "atomOrbit": "Atom orbit", "sonarArcs": "Sonar arcs", "circuit": "Circuit", "swatches": "Swatches", "ruledLines": "Ruled notebook", "registration": "Registration marks", "punchHoles": "Punch holes", "staple": "Staple", "paperClip": "Paper clip", "indexTabs": "Index tabs", "vines": "Vines", "cloudPuffs": "Cloud puffs", "starField": "Star field", "moonPhases": "Moon phases", "sunRays": "Sun rays", "rainRipples": "Rain ripples", "bubbles": "Bubbles", "smoke": "Smoke", "dandelion": "Dandelion seeds", "fireflies": "Fireflies", "memphis": "Memphis", "zigzagRibbon": "Zigzag ribbon", "polkaPatch": "Polka patch", "stripeCircle": "Stripe circle", "decoCorners": "Deco corners", "halfCircles": "Half circles", "loopArrows": "Loop arrows", "starburst": "Starburst", "tally": "Tally marks", "cursorClick": "Cursor click", "windowChrome": "Window frame", "progressBar": "Progress bar", "toggleSwitch": "Toggle switch", "notifBell": "Notification bell", "likeCounter": "Like counter", "mediaControls": "Playback controls", "volumeBars": "Volume bars", "musicNotes": "Music notes", "hrScratches": "Claw Scratches", "hrSigil": "Sigil", "hrWatchEye": "Watching Eye", "hrStaticPatch": "Static Patches", "hrDustBeam": "Dusty Light Beam", "hrDrips": "Ink Drips", "hrCracks": "Cracks", "knSpeedTrail": "Trailing Speed Lines", "knWordTicks": "Word Counter", "tyColophon": "Colophon", "tyRunningHead": "Running Head & Folio", "tyGlyphBody": "Glyph Body Lines", "tyTextRule": "Text Rule", "tyTypeScale": "Type Scale", "tyBigPunct": "Big Quote Marks"}, "treat": {"none": "None", "hrInkBleed": "Bleeding Ink", "hrEroded": "Eroded", "hrRedact": "Redacted", "hrDoubleExp": "Double Exposure", "knWordScale": "Big and Small Words", "knWordPlate": "Word Knockout", "outline": "Hollow letters", "outlineFill": "Outlined text", "doubleOutline": "Double outline", "extrude": "Extruded text", "longShadow": "Long shadow", "hardShadow": "Hard shadow", "softShadow": "Soft shadow", "glow": "Glow", "marker": "Marker highlight", "underline": "Underline", "strike": "Strikethrough", "boxed": "Boxed text", "gradientV": "Vertical gradient", "splitColor": "Two tone split", "halftone": "Halftone", "stripes": "Stripes", "hatch": "Hatch", "dotted": "Dotted outline", "alternate": "Alternating colors", "italic": "Italic", "wide": "Wide letters", "tall": "Tall letters", "echoOutline": "Echoed outline", "emphasisDots": "Emphasis dots", "neonOutline": "Neon tubing", "chrome": "Chrome", "rainbow": "Rainbow", "glitchSplit": "Misregistered colors", "shadowStack": "Shadow stack", "stencilGap": "Stencil gaps", "waterline": "Waterline", "karaoke": "Karaoke", "sizeWave": "Rhythmic letter sizes", "rotateAlt": "Alternating angles", "baselineShift": "Staggered baseline", "fauxBold": "Extra bold", "circled": "Circled letters", "bracketsQuote": "Quotation marks", "reflection": "Reflection", "inline": "Inline", "sticker": "Sticker outline", "gradientSweep": "Gradient sweep", "kerningWide": "Wide letter spacing", "monoGrid": "Manuscript grid", "outlineOffset": "Offset outline", "toneShadow": "Halftone shadow", "fadeChars": "Fading letters", "cutShift": "Offset cut", "focusPull": "Focus shift", "spotChar": "Highlighted character", "ransom": "Cutout letters", "tyHollowKey": "Hollow Key Glyph", "tyHeadRules": "Head & Foot Rules", "tyHeadBig": "Large Initial", "tyIndexSup": "Glyph Numbers"}, "bg": {"none": "Solid color", "auroraRibbons": "Aurora ribbons", "meshBlobs": "Mesh blobs", "duotoneSweep": "Duotone sweep", "horizonGlow": "Horizon glow", "seigaiha": "Seigaiha waves", "asanoha": "Asanoha pattern", "houndstooth": "Houndstooth", "herringbone": "Herringbone", "argyle": "Argyle", "tartan": "Tartan", "chevron": "Chevron", "isoCubes": "Iso cubes", "hexGrid": "Hex grid", "triTess": "Tri tess", "moire": "Moire", "squareTunnel": "Square tunnel", "spiralArms": "Spiral arms", "topoLines": "Contour lines", "ridgePlot": "Mountain ridges", "starfield": "Starfield", "nightMoon": "Moonlit night", "skyline": "Skyline", "sunsetSun": "Sunset sun", "oceanWaves": "Ocean waves", "rainWindow": "Rain window", "snowLayers": "Snow layers", "fireworks": "Fireworks", "cloudLayers": "Cloud layers", "mountains": "Mountains", "filmStrip": "Filmstrip", "vhsBand": "VHS noise band", "tornPaper": "Torn paper", "godRays": "Light rays", "vignettePulse": "Pulsing vignette", "kaleidoscope": "Kaleidoscope", "marble": "Marble", "paperCut": "Paper cutout", "hrFailingLamp": "Failing Lamp", "hrCorridor": "Dark Corridor", "hrMold": "Spreading Stains", "hrDeadTrees": "Dead Forest", "sunburst": "Sunburst", "concentric": "Concentric", "halftoneFade": "Halftone fade", "bigStripes": "Big stripes", "splitV": "Vertical color split", "splitH": "Horizontal color split", "splitDiag": "Diagonal color split", "gradientSweep": "Gradient sweep", "spotlight": "Spotlight", "tvBars": "TV color bars", "checker": "Checker", "bigChar": "Oversized letters", "speedLines": "Speed lines", "scanBars": "Scan bars", "dotGrid": "Dot grid", "retroGrid": "Retro grid", "bokehBg": "Bokeh", "particlesBg": "Floating particles", "ripples": "Ripples", "polka": "Polka", "eqBars": "Equalizer bars", "borderFrame": "Border frame", "letterbox": "Cinema letterbox", "noiseField": "Moving noise"}, "cam": {"push": "Slow push in", "orbitDrift": "Orbit drift", "barrelRoll": "Barrel roll", "pendulumSway": "Pendulum sway", "focusIn": "Focus in", "rackFocus": "Rack focus", "earthquake": "Earthquake", "floatNoise": "Floating camera", "vertigo": "Dolly zoom", "tiltDown": "Tilt down", "spiralIn": "Spiral zoom", "snapPan": "Snap pan", "jelly": "Elastic wobble", "hrNervous": "Frightened Handheld", "hrDutchSnap": "Sudden Tilt", "knReadPan": "Reading Pan", "knTiltKick": "Tilt Kick", "knCardFlip": "Card Flip", "knShearKick": "Shear Kick", "knJumpCut": "Jump Cut", "knRushIn": "Rush In", "pullOut": "Pull out", "panL": "Pan left", "panR": "Pan right", "tiltUp": "Tilt up", "dutch": "Dutch angle", "handheld": "Handheld", "beatPunch": "Zoom on beat", "whipIn": "Whip in", "crashZoom": "Crash zoom", "bounce": "Bounce", "roll": "Roll", "driftDiag": "Diagonal drift", "shakeHard": "Strong shake", "dollyIn": "Dolly in", "stepZoom": "Stepped zoom"}, "fx": {"chroma": "Chromatic jump", "shake": "Shake", "slice": "Slice glitch", "block": "Block glitch", "invert": "Invert", "flash": "Flash", "zoom": "Zoom blur", "mosaic": "Mosaic", "radialChroma": "Radial color fringing", "bloomFlash": "Bloom flash", "bulge": "Fisheye distortion", "pixelSort": "Pixel sort", "interlace": "Interlace", "macroBlock": "Compression blocks", "halftone": "Halftone", "duotone": "Duotone", "ditherBit": "1 bit dither", "rotateSnap": "Angle snap", "echoFrames": "Frame echoes", "kaleido": "Kaleidoscope", "bandInvert": "Inverted band", "lightRays": "Light rays", "anamorphic": "Anamorphic flare", "heartbeat": "Heartbeat", "tvStatic": "TV static", "dustScratches": "Film scratches", "filmAdvance": "Film advance", "perspectiveTilt": "Perspective tilt", "ripple": "Ripple", "focusLines": "Focus lines", "speedLines": "Speed lines", "starGlint": "Star glint", "colorBars": "Color bars", "zoomStutter": "Zoom stutter", "negativeRing": "Inverted ring", "edgeDetect": "Edge detect", "shatter": "Glass shatter", "defocus": "Out of focus", "snapshot": "Snapshot", "squash": "Squash", "scanBar": "Scan bar", "loopScroll": "Loop scroll", "hrSubliminal": "Subliminal", "hrSignalLoss": "Signal Loss", "hrPassingShadow": "Passing Shadow", "panelWipe": "Panel wipe", "irisTrans": "Iris trans", "doors": "Doors", "blindsTrans": "Blinds trans", "rgbSplit": "RGB separation", "smear": "Smear", "vhsRoll": "VHS roll", "trackingNoise": "Tracking noise", "mirrorFlash": "Mirror flash", "strobe": "Strobe", "posterize": "Posterization", "hueShift": "Hue shift", "tileShift": "Tile shift", "filmBurn": "Film burn", "whipBlur": "Whip blur", "blackFrame": "Black frame", "whiteFrame": "White frame", "gridRepeat": "Grid repeat", "waveWarp": "Wave warp", "pixelDrift": "Pixel drift", "zoomPunch": "Zoom punch", "lightSweep": "Light sweep", "crtOff": "CRT powers off", "splitSlide": "Split slide"}, "trans": {"hrStaticCut": "Static Cut", "hrBlink": "Blink", "knCornerSwing": "Corner Swing", "knStutterCut": "Stutter Cut", "knStripSlam": "Strip Slam", "wipe": "Edge wipe", "diagonalWipe": "Diagonal band wipe", "clockWipe": "Clock wipe", "irisOpen": "Iris opens", "pushSlide": "Push", "cover": "Cover", "uncover": "Uncover", "zoomThrough": "Zoom through", "doorsOpen": "Double doors", "blinds": "Blinds", "checker": "Checkerboard", "blockDissolve": "Block dissolve", "whipPan": "Whip pan", "spinOut": "Spin out", "inkBlob": "Ink blot", "shatterTiles": "Shattering tiles", "sliceShift": "Sliding strips", "cubeTurn": "Cube turn", "flashCross": "Flash cut", "pixelate": "Pixel transition", "tyRuleWipe": "Ruled Line Wipe", "tyGridCells": "Grid Cell Fill"}}, "layoutOrder": ["center", "mixed", "vcols", "marquee", "tile", "scatter", "ring", "wave", "huge", "labels", "condensed", "gloss", "type", "diag", "circle", "stack", "pill", "hrFlashlight", "hrDoorGap", "hrWallScrawl", "hrCctv", "hrOuija", "hrMissing", "hrWrongOne", "hrRisingDark", "hrRedacted", "hrStaticTv", "hrSpiritPhoto", "hrWrongShadow", "knSlamStack", "knQuarterTurn", "knSwapCenter", "knZoomDive", "knFlowSnap", "knSeesaw", "knTypeSlam", "knRhythmCuts", "knPathRide", "knGearWords", "knCollide", "knTumble", "knReflow", "knPadGrid", "lowerThird", "corners", "staircase", "zigzag", "arcTop", "spiral", "gridCells", "dropCap", "justified", "frameBox", "bubble", "subtitleBar", "ticker", "splitScreen", "mirror", "sideways", "edgeFrame", "perspective", "hanko", "genkou", "panels", "filmstrip", "quote", "ruler", "searchBar", "chat", "notification", "ticket", "rain", "hanging", "orbit", "tunnel", "wordCloud", "bounceLine", "elastic", "crossBands", "stickerBomb", "neon", "keycaps", "bubbles", "slotMachine", "flipBoard", "credits", "zoomRepeat", "splitHalves", "columnsBig", "circleWords", "dotMatrix", "depthStack", "typeSpecimen", "kanjiFocus", "halfVertical", "curtain", "equalizer", "tape", "magazine", "headlineDeck", "contents", "footnote", "proofread", "numbered", "poster", "swissGrid", "dictionary", "ema", "ransom", "newspaper", "vinyl", "cassette", "bookSpine", "polaroid", "stampSheet", "postcard", "letterPaper", "calendar", "chochin", "routeMap", "stationSign", "noren", "tanzaku", "omikuji", "kakejiku", "shoji", "clapper", "warningLabel", "priceTag", "nameTag", "stickyNotes", "karuta", "cube", "cylinder", "flipCards", "accordion", "flag", "ribbon", "pendulum", "pile", "blocks", "balloons", "magnets", "tiles", "bulbs", "ledScroll", "billboard", "crowdBubbles", "crossword", "wordSearch", "puzzle", "shadowPlay", "kaleido", "dominoes", "burst", "fisheye", "wall", "origami", "zipper", "sliceStack", "glitchGrid", "mosaicTiles", "maskReveal", "contour", "halftoneBig", "stencil", "tyKeySplit", "tyCropGiant", "tyCross", "tyBandHide", "tyRuby", "tyBaseline", "tyScaleSteps", "tyJustify", "tyIndexTable", "tySplitType", "tyErode", "tyVRuler", "tyFullTrack", "tyStatCount", "tyMargin", "tyRotBlock", "tySquare", "tyLineFocus"], "enterOrder": ["cut", "assemble", "slice", "type", "pop", "drop", "stretch", "wipe", "blur", "spin", "flicker", "scramble", "zoom", "riseMask", "dropMask", "slideL", "slideR", "slideWhole", "flipX", "flipY", "domino", "fold", "unroll", "strokeDraw", "outlineFill", "splitJoin", "vSlice", "shutter", "iris", "diagWipe", "blinds", "checker", "randomOrder", "bounceBig", "squashDrop", "rubber", "glitchIn", "echoIn", "whip", "skewIn", "trackIn", "trackOut", "blurStagger", "fadeStagger", "waveIn", "spiralIn", "zoomOut", "resolve", "magnet", "inkBleed", "neonOn", "cursorSweep", "stamp", "springIn", "pendulum", "rollIn", "slingshot", "rockSettle", "bounceBall", "snapRail", "fanOpen", "cylinder", "shuffle", "stopMotion", "ripple", "zipper", "zoomAlt", "tiltUp", "stickerPeel", "crumple", "noteUnfold", "tornJoin", "splitFlap", "overexpose", "glint", "loupe", "filmFeed", "backlight", "lightLeak", "heatHaze", "crtOn", "interlace", "loadingBar", "dither", "odometer", "matrixRain", "hatchFill", "brushReveal", "inkDrop", "quarters", "invertBox", "printRegister", "echoCount", "liquidFill", "windBlown", "strokeOrder", "clockWipe", "shadowFirst", "bubbles", "tokoroten", "hrBlinkCreep", "hrJumpScare", "hrUneasy", "hrVhold", "hrMirrorSnap", "hrManifest", "hrClawReveal", "knWordSlam", "knTypeToSlam", "knReplaceIn", "knHingeDrop", "knLoopIn", "knPushIn", "knInertia", "knWordSpin", "knDiveIn", "knStretchOut", "tyKeyFirst", "tyLineWipe", "tyZoomOne", "tyUnderLift", "tyDotGrow", "tyBracketOpen", "tyRetype", "tyRubyDrop"], "holdOrder": ["still", "jitter", "drift", "breathe", "wave", "glitchtick", "float", "sway", "pulse", "shimmer", "colorRun", "rotateSlow", "trackBreathe", "skewWobble", "beatHop", "hWave", "heartbeat", "orbitSmall", "jelly", "scanBand", "noiseDrift", "tilt", "zoomSlow", "stretchPulse", "glitchJump", "echoTrail", "glowFlicker", "windGust", "dangle", "eqBounce", "flashBox", "glintSweep", "flipSwap", "shadowSway", "magnetJiggle", "typeRattle", "focusRack", "pluckString", "hrTwitch", "hrStare", "hrLagOne", "hrFlickerLight", "knWordPulse", "knCounterRock", "knWordRide", "knTickShift", "knBeatLean", "knGapBreath", "tyKeyPulse", "tyReadCursor", "tyOutlineBlink", "tyTrackStep"], "exitOrder": ["cut", "explode", "fall", "drift", "slice", "wipe", "shrink", "blur", "stretch", "scatter", "glitch", "sinkMask", "riseOut", "slideOutL", "slideOutR", "flipOutX", "flipOutY", "foldOut", "squash", "trackOutWide", "collapse", "zoomThrough", "zoomFar", "spinOut", "twist", "waveOut", "blurOutStagger", "undraw", "outlineOut", "irisClose", "diagWipeOut", "blindsClose", "checkerOut", "splitApart", "vSliceDrop", "melt", "dissolve", "backspace", "scrambleOut", "glitchDissolve", "echoOut", "whipOut", "gravity", "popOut", "burn", "sweepCover", "shatterLite", "peelOff", "crumpleOut", "tearOut", "scorchOut", "overexposeOut", "scanOut", "stripesOut", "halftoneOut", "eraserOut", "vacuumOut", "sandOut", "shredOut", "dominoOut", "hingeOut", "rocketOff", "bounceOff", "balloonOff", "deflateOut", "hazeOut", "glassBreak", "zipOut", "clapShut", "lampOff", "slotOut", "clockOut", "matrixOut", "tornadoOut", "rollUpOut", "snakeOut", "flutterOut", "rollOff", "fanClose", "rgbSplitOut", "shockOut", "floodOut", "slashOut", "mosaicOut", "scribbleOut", "candleOut", "hrPulledDown", "hrLookBack", "hrTurnAway", "hrShiver", "hrSwallow", "hrFlickerDie", "hrDrain", "knWordKick", "knPushOut", "knDiveGlyph", "knLaunch", "knWordBlink", "knCloseGap", "knJumpCutOut", "knStackAway", "tyStrike", "tyToDot", "tyLineFeed", "tyBracketClose", "tyToIndex", "tyKeyLast", "tyUnderSink", "tyFoldVert"], "decorOrder": ["brackets", "rings", "dots", "arrows", "slash", "sparks", "leaders", "waveform", "barcode", "grid", "stripes", "blobs", "bars", "shapes", "counter", "crosshair", "cropMarks", "reticle", "radar", "progressRing", "timecodeBar", "rulerEdge", "dimension", "indexNum", "dateStamp", "qrBlock", "glitchRects", "concentricSquares", "triangleSpin", "lineBurst", "plusGrid", "guides", "waveLine", "spiralLine", "halftonePatch", "checkerStrip", "beatRing", "orbitDots", "constellation", "confetti", "petals", "rainStreaks", "snow", "lightLeak", "bokeh", "speedCorner", "risingParticles", "twinkle", "brushStroke", "tapePieces", "scribbleCircle", "scribbleUnder", "crossOut", "highlightMark", "heartsStars", "watermarkKanji", "verticalStrip", "romajiLine", "bracketsJP", "seal", "kamon", "seigaiha", "asanoha", "hanabi", "chochin", "shimenawa", "sensu", "tsukiKumo", "momiji", "namiGashira", "kasumi", "hexGrid", "spectrumRing", "dataColumns", "spinner", "headingTape", "glyphLock", "atomOrbit", "sonarArcs", "circuit", "swatches", "ruledLines", "registration", "punchHoles", "staple", "paperClip", "indexTabs", "vines", "cloudPuffs", "starField", "moonPhases", "sunRays", "rainRipples", "bubbles", "smoke", "dandelion", "fireflies", "memphis", "zigzagRibbon", "polkaPatch", "stripeCircle", "decoCorners", "halfCircles", "loopArrows", "starburst", "tally", "cursorClick", "windowChrome", "progressBar", "toggleSwitch", "notifBell", "likeCounter", "mediaControls", "volumeBars", "musicNotes", "hrScratches", "hrSigil", "hrWatchEye", "hrStaticPatch", "hrDustBeam", "hrDrips", "hrCracks", "knSpeedTrail", "knWordTicks", "tyColophon", "tyRunningHead", "tyGlyphBody", "tyTextRule", "tyTypeScale", "tyBigPunct"], "moods": {"glitch": {"name": "Glitch", "fx": {"motion": [0.6, 0.9], "glitch": [0.75, 1], "chroma": [0.75, 1], "decor": [0.3, 0.6], "density": [0.6, 0.9], "texture": [0.5, 0.9], "bgSwitch": [0.3, 0.6]}, "layout": ["center", "condensed", "huge", "tile", "marquee", "vcols", "scatter", "stack"], "enter": ["slice", "scramble", "assemble", "flicker", "zoom", "stretch"], "exit": ["glitch", "slice", "explode", "fall"], "styles": ["noir", "crimson", "mint", "mono", "hud"]}, "calm": {"name": "Gentle", "fx": {"motion": [0.3, 0.55], "glitch": [0.05, 0.25], "chroma": [0.2, 0.5], "decor": [0.2, 0.5], "density": [0.25, 0.45], "texture": [0.5, 0.85], "bgSwitch": [0.1, 0.3]}, "layout": ["center", "vcols", "gloss", "stack", "circle", "type", "mixed"], "enter": ["blur", "type", "wipe", "assemble"], "exit": ["blur", "drift", "wipe", "shrink"], "styles": ["specimen", "paper", "hud", "noir"], "noHold": ["glitchtick", "jitter"]}, "pop": {"name": "Pop", "fx": {"motion": [0.7, 1], "glitch": [0.1, 0.35], "chroma": [0.3, 0.6], "decor": [0.6, 1], "density": [0.5, 0.8], "texture": [0.2, 0.5], "bgSwitch": [0.4, 0.8]}, "layout": ["mixed", "scatter", "wave", "labels", "pill", "ring", "huge", "diag", "center"], "enter": ["pop", "drop", "spin", "stretch", "zoom"], "exit": ["scatter", "shrink", "stretch", "blur"], "styles": ["magenta", "caution", "transit", "blueprint", "rouge"]}, "graphic": {"name": "Graphic", "fx": {"motion": [0.5, 0.8], "glitch": [0.2, 0.5], "chroma": [0.4, 0.7], "decor": [0.7, 1], "density": [0.5, 0.8], "texture": [0.4, 0.7], "bgSwitch": [0.3, 0.7]}, "layout": ["diag", "labels", "marquee", "tile", "condensed", "huge", "circle", "pill"], "enter": ["wipe", "slice", "stretch", "zoom"], "exit": ["wipe", "slice", "stretch"], "styles": ["blueprint", "caution", "rouge", "mint", "transit"]}, "editorial": {"name": "Editorial", "fx": {"motion": [0.4, 0.65], "glitch": [0.1, 0.3], "chroma": [0.2, 0.45], "decor": [0.4, 0.7], "density": [0.35, 0.6], "texture": [0.6, 0.9], "bgSwitch": [0.2, 0.4]}, "layout": ["gloss", "vcols", "mixed", "stack", "type", "center", "circle"], "enter": ["type", "blur", "wipe", "assemble"], "exit": ["blur", "drift", "wipe"], "styles": ["specimen", "paper", "noir", "mono", "hud"]}, "emotional": {"name": "Emotional", "fx": {"motion": [0.55, 0.85], "glitch": [0.3, 0.6], "chroma": [0.5, 0.85], "decor": [0.3, 0.6], "density": [0.4, 0.7], "texture": [0.6, 1], "bgSwitch": [0.2, 0.5]}, "layout": ["huge", "center", "vcols", "stack", "condensed", "mixed", "circle"], "enter": ["assemble", "blur", "zoom", "wipe", "slice"], "exit": ["drift", "explode", "fall", "blur"], "styles": ["noir", "paper", "hud", "mono", "crimson"]}, "horror": {"name": "Horror", "set": "horror", "fx": {"motion": [0.35, 0.65], "glitch": [0.3, 0.7], "chroma": [0.2, 0.5], "decor": [0.3, 0.6], "density": [0.3, 0.55], "texture": [0.7, 1], "bgSwitch": [0.1, 0.3]}, "layout": ["center", "vcols", "stack", "huge", "type"], "enter": ["flicker", "blur", "type", "scramble"], "exit": ["blur", "glitch", "fall", "drift"], "styles": ["noir", "mono", "crimson"], "noHold": ["wave"], "sprinkle": 0.1}, "chaos": {"name": "Anything goes", "fx": {"motion": [0.5, 1], "glitch": [0.3, 1], "chroma": [0.4, 1], "decor": [0.4, 1], "density": [0.45, 0.9], "texture": [0.3, 1], "bgSwitch": [0.3, 0.9]}, "layout": null, "enter": null, "exit": null, "styles": null}}, "moodOrder": ["glitch", "calm", "pop", "graphic", "editorial", "emotional", "horror", "chaos"], "ghostPairs": [["#16F4D4", "#F5A50C"], ["#FF2A2A", "#2AA8FF"], ["#FF2BD6", "#2BFF88"], ["#FFE600", "#7B2BFF"], ["#FF6A00", "#00C2B8"], ["#FF6FAE", "#B6FF3B"], ["#00E0FF", "#FF3D6E"], ["#C8FF00", "#FF00A8"], ["#4D6BFF", "#FFB000"], ["#FF4B2B", "#2BD9FF"]], "fonts": {"gothic_black": {"label": "Noto Sans JP Black", "family": "Noto Sans JP", "weight": 900, "kind": "gothic", "extra": false}, "gothic_bold": {"label": "Noto Sans JP Bold", "family": "Noto Sans JP", "weight": 700, "kind": "gothic", "extra": false}, "gothic_med": {"label": "Noto Sans JP Medium", "family": "Noto Sans JP", "weight": 500, "kind": "gothic", "extra": false}, "gothic_light": {"label": "Noto Sans JP Light", "family": "Noto Sans JP", "weight": 300, "kind": "gothic", "extra": false}, "dela": {"label": "Dela Gothic One", "family": "Dela Gothic One", "weight": 400, "kind": "display", "extra": false}, "zenkaku": {"label": "Zen Kaku Gothic New Black", "family": "Zen Kaku Gothic New", "weight": 900, "kind": "gothic", "extra": false}, "mincho_black": {"label": "Zen Old Mincho Black", "family": "Zen Old Mincho", "weight": 900, "kind": "mincho", "extra": false}, "mincho_bold": {"label": "Noto Serif JP Bold", "family": "Noto Serif JP", "weight": 700, "kind": "mincho", "extra": false}, "mincho": {"label": "Noto Serif JP Medium", "family": "Noto Serif JP", "weight": 500, "kind": "mincho", "extra": false}, "mincho_light": {"label": "Noto Serif JP Light", "family": "Noto Serif JP", "weight": 300, "kind": "mincho", "extra": false}, "tokumin": {"label": "Kaisei Tokumin", "family": "Kaisei Tokumin", "weight": 800, "kind": "mincho", "extra": false}, "round": {"label": "M PLUS Rounded 1c", "family": "M PLUS Rounded 1c", "weight": 800, "kind": "round", "extra": false}, "pop": {"label": "Mochiy Pop One", "family": "Mochiy Pop One", "weight": 400, "kind": "display", "extra": false}, "dot": {"label": "DotGothic16", "family": "DotGothic16", "weight": 400, "kind": "pixel", "extra": false}, "brush": {"label": "Yuji Syuku", "family": "Yuji Syuku", "weight": 400, "kind": "brush", "extra": false}, "mono": {"label": "IBM Plex Mono", "family": "IBM Plex Mono", "weight": 500, "kind": "mono", "extra": false}, "reggae": {"label": "Reggae One", "family": "Reggae One", "weight": 400, "kind": "display", "extra": true}, "rampart": {"label": "Rampart One", "family": "Rampart One", "weight": 400, "kind": "display", "extra": true}, "potta": {"label": "Potta One", "family": "Potta One", "weight": 400, "kind": "brush", "extra": true}, "kiwi": {"label": "Kiwi Maru", "family": "Kiwi Maru", "weight": 500, "kind": "round", "extra": true}, "klee": {"label": "Klee One", "family": "Klee One", "weight": 600, "kind": "hand", "extra": true}, "shippori": {"label": "Shippori Mincho B1", "family": "Shippori Mincho B1", "weight": 800, "kind": "mincho", "extra": true}, "sansui": {"label": "IBM Plex Sans JP", "family": "IBM Plex Sans JP", "weight": 500, "kind": "gothic", "extra": false}}};
/*  JIZURA for After Effects \u2014 lyric motion panel
    ScriptUI panel: builds editable compositions (text animators, expressions,
    shape layers, effects) from lyrics or from a JIZURA plan JSON.
    Install: copy this file to
      After Effects/Support Files/Scripts/ScriptUI Panels/
    then open it from the Window menu.                                         */

// ---------------------------------------------------------------- utilities
function jzClamp(x, a, b) { return x < a ? a : (x > b ? b : x); }
function jzLerp(a, b, t) { return a + (b - a) * t; }
function jzTrim(s) { return String(s).replace(/^[\s\u3000]+|[\s\u3000]+$/g, ''); }
function jzIndexOf(arr, v) { for (var i = 0; i < arr.length; i++) if (arr[i] === v) return i; return -1; }
function jzKeys(o) { var r = []; for (var k in o) if (o.hasOwnProperty(k)) r.push(k); return r; }
function jzPad(n, w) { var s = String(n); while (s.length < w) s = '0' + s; return s; }
function jzHex(h) {
    h = String(h || '#000000').replace('#', '');
    if (h.length === 3) h = h.charAt(0) + h.charAt(0) + h.charAt(1) + h.charAt(1) + h.charAt(2) + h.charAt(2);
    var n = parseInt(h.substr(0, 6), 16);
    return [((n >> 16) & 255) / 255, ((n >> 8) & 255) / 255, (n & 255) / 255];
}
function jzLum(h) { var c = jzHex(h); return 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2]; }
function jzParseJSON(s) {
    s = String(s).replace(/^\uFEFF/, '');
    if (typeof JSON !== 'undefined' && JSON.parse) return JSON.parse(s);
    return eval('(' + s + ')');
}
function jzChars(s) {
    var out = [], i = 0; s = String(s);
    while (i < s.length) {
        var c = s.charCodeAt(i);
        if (c >= 0xD800 && c <= 0xDBFF && i + 1 < s.length) { out.push(s.substr(i, 2)); i += 2; }
        else { out.push(s.charAt(i)); i++; }
    }
    return out;
}
function jzCode(ch) { return ch.charCodeAt(0); }
function jzIsKanji(ch) { var c = jzCode(ch); return (c >= 0x3400 && c <= 0x9FFF) || (c >= 0xF900 && c <= 0xFAFF) || ch === '\u3005' || ch === '\u3006'; }
function jzIsHira(ch) { var c = jzCode(ch); return c >= 0x3041 && c <= 0x309F; }
function jzIsKata(ch) { var c = jzCode(ch); return (c >= 0x30A0 && c <= 0x30FF) || (c >= 0x31F0 && c <= 0x31FF); }
function jzIsLatin(ch) { return /[A-Za-z0-9]/.test(ch); }
function jzIsPunct(ch) { return /[\u3001\u3002\uFF0C\uFF0E,.!?\uFF01\uFF1F\u2026\u2025\u30FB\u300C\u300D\u300E\u300F\uFF08\uFF09()\u3010\u3011~\u301C\uFF5E:\uFF1A;\uFF1B\-\u2014\u2015]/.test(ch); }
function jzCount(s) { return jzChars(String(s).replace(/[\s\u3000]+/g, '')).length; }

// deterministic hashing + seeded stream
function jzImul(a, b) {
    var ah = (a >>> 16) & 0xffff, al = a & 0xffff, bh = (b >>> 16) & 0xffff, bl = b & 0xffff;
    return ((al * bl) + (((ah * bl + al * bh) << 16) >>> 0)) | 0;
}
function jzHash(a, b, c, d) {
    var s = String(a) + '|' + String(b) + '|' + String(c) + '|' + String(d);
    var h = -2128831035;
    for (var i = 0; i < s.length; i++) { h ^= s.charCodeAt(i); h = jzImul(h, 16777619); }
    h ^= h >>> 15; h = jzImul(h, 0x2c1b3c6d); h ^= h >>> 12; h = jzImul(h, 0x297a2d39); h ^= h >>> 15;
    return h >>> 0;
}
function jzR(a, b, c, d) { return jzHash(a, b, c, d) / 4294967296; }
function JzRng(seed) {
    this.s = (seed % 2147483647 + 2147483647) % 2147483647 || 1;
}
JzRng.prototype.next = function () { this.s = (this.s * 48271) % 2147483647; return (this.s - 1) / 2147483646; };
JzRng.prototype.range = function (a, b) { return a + (b - a) * this.next(); };
JzRng.prototype.int = function (a, b) { return Math.floor(a + (b - a + 1) * this.next()); };
JzRng.prototype.pick = function (arr) { return arr[Math.floor(this.next() * arr.length) % arr.length]; };
JzRng.prototype.chance = function (p) { return this.next() < p; };
JzRng.prototype.wpick = function (list) { // [[value, weight], ...]
    var tot = 0, i; for (i = 0; i < list.length; i++) tot += list[i][1];
    var x = this.next() * tot;
    for (i = 0; i < list.length; i++) { x -= list[i][1]; if (x <= 0) return list[i][0]; }
    return list[list.length - 1][0];
};

// ---------------------------------------------------------------- lyric parsing
function jzParseLyrics(raw) {
    var lines = [], meta = {}, gap = false;
    var rows = String(raw || '').replace(/\r\n?/g, '\n').split('\n');
    for (var r = 0; r < rows.length; r++) {
        var s0 = jzTrim(rows[r]);
        if (!s0) { if (lines.length) gap = true; continue; }
        if (s0.charAt(0) === '#') continue;
        var mm = s0.match(/^\[(ti|ar|al|by|offset):(.*)\]$/i);
        if (mm) { meta[mm[1].toLowerCase()] = jzTrim(mm[2]); continue; }
        var s = s0, times = [], m;
        while ((m = s.match(/^\[(\d+):(\d+(?:[.:]\d+)?)\]/))) { times.push(parseInt(m[1], 10) * 60 + parseFloat(m[2].replace(':', '.'))); s = s.substr(m[0].length); }
        s = jzTrim(s);
        // \u9593\u594F: [\u9593\u594F] / [\u9593\u594F 8] \u2014 also [interlude] [inst] [\u95F4\u594F] [\uAC04\uC8FC]
        var im = s.match(/^\[\s*(\u9593\u594F|\u95F4\u594F|interlude|instrumental|inst|\uAC04\uC8FC)(?:\s*[:\uFF1A]?\s*(\d+(?:\.\d+)?)\s*(?:s|sec|\u79D2|\uCD08)?)?\s*\]$/i);
        if (im) {
            var ib = { text: '', interlude: true, secs: im[2] ? parseFloat(im[2]) : null, note: null, impact: false, emph: [], manual: null, gapBefore: gap, lrc: null };
            gap = false;
            if (times.length) { for (var it = 0; it < times.length; it++) { var ic = jzCopy(ib); ic.lrc = times[it]; lines.push(ic); } }
            else lines.push(ib);
            continue;
        }
        var note = null, bar = s.indexOf('|');
        if (bar >= 0) { note = jzTrim(s.substr(bar + 1)) || null; s = jzTrim(s.substr(0, bar)); }
        var impact = false;
        if (s.length > 1 && s.charAt(s.length - 1) === '!') { impact = true; s = jzTrim(s.substr(0, s.length - 1)); }
        var emph = [];
        s = s.replace(/\*([^*]+)\*/g, function (all, w) { emph.push(w); return w; });
        var manual = null;
        if (s.indexOf('/') >= 0) {
            var parts = s.split('/'), mp = [];
            for (var p = 0; p < parts.length; p++) { var tp = jzTrim(parts[p]); if (tp) mp.push(tp); }
            manual = mp; s = mp.join(/[A-Za-z]/.test(s) ? ' ' : '');
        }
        if (!s) continue;
        var base = { text: s, note: note, impact: impact, emph: emph, manual: manual, gapBefore: gap, lrc: null };
        gap = false;
        if (times.length) { for (var t = 0; t < times.length; t++) { var c = jzCopy(base); c.lrc = times[t]; lines.push(c); } }
        else lines.push(base);
    }
    return { lines: lines, meta: meta };
}
function jzCopy(o) { var r = {}; for (var k in o) if (o.hasOwnProperty(k)) r[k] = o[k]; return r; }

// bunsetsu-ish chunking without a dictionary: script runs + trailing kana
function jzChunk(text) {
    var cs = jzChars(text), chunks = [], cur = '', curType = '', hasH = false, i;
    function typeOf(ch) {
        if (/[\s\u3000]/.test(ch)) return 'S';
        if (jzIsPunct(ch)) return 'P';
        if (jzIsKanji(ch)) return 'K';
        if (jzIsHira(ch) || ch === '\u30FC') return 'H';
        if (jzIsKata(ch)) return 'T';
        if (jzIsLatin(ch)) return 'L';
        return 'O';
    }
    function close() { if (jzTrim(cur)) chunks.push(jzTrim(cur)); cur = ''; curType = ''; hasH = false; }
    var hRun = 0;
    for (i = 0; i < cs.length; i++) {
        var ch = cs[i], t = typeOf(ch);
        if (t === 'S') { close(); continue; }
        if (t === 'P') { if (cur) cur += ch; else if (chunks.length) chunks[chunks.length - 1] += ch; continue; }
        if (!cur) { cur = ch; curType = t; hasH = t === 'H'; hRun = t === 'H' ? 1 : 0; continue; }
        if (t === 'H') {
            // kana after kanji/katakana (okurigana, particles) stays; long pure-kana runs split at ~6
            if (curType !== 'H' || jzChars(cur).length < 6) { cur += ch; hasH = true; hRun++; continue; }
            close(); cur = ch; curType = 'H'; hasH = true; hRun = 1; continue;
        }
        if (t === 'K' && curType === 'K' && !hasH) { cur += ch; continue; }
        if (t === 'T' && curType === 'T' && !hasH) { cur += ch; continue; }
        if (t === 'L' && curType === 'L') { cur += ch; continue; }
        // kanji right after a short kana particle run starts a new chunk
        close(); cur = ch; curType = t; hasH = t === 'H'; hRun = 0;
    }
    close();
    var out = [];
    for (i = 0; i < chunks.length; i++) {
        var n = jzChars(chunks[i]).length;
        if (n > 10) { var half = Math.ceil(n / 2), cc = jzChars(chunks[i]); out.push(cc.slice(0, half).join('')); out.push(cc.slice(half).join('')); }
        else out.push(chunks[i]);
    }
    for (i = out.length - 1; i > 0; i--) if (jzChars(out[i]).length === 1 && !jzIsKanji(out[i])) { out[i - 1] += out[i]; out.splice(i, 1); }
    return out.length ? out : [text];
}

// ================================================================ expression registry (AE)
// Every expression group has a table of AE implementations. Packs (ae/p_*.jsx) add entries with jzReg().
// WHAT gets picked (weights, mood tags, fits, \u8FFD\u52A0\u5206/\u548C\u98A8 flags \u2026) comes from the web engine via
// JZ_DATA.meta so the browser and AE planners stay in step; this file only knows HOW to build.
//
//  layout : { build(ctx) -> bbox, plan(rng, cut, st) -> params }
//  enter / hold / exit : { apply(m) }            m = motion context (see 20_motion.jsx)
//  decor  : { build(ctx, bb, d) }
//  treat  : { apply(ctx, L, P, o), plan(rng, st) }
//  bg     : { build(bctx, P), plan(rng, st) }
//  cam    : { apply(cam, P), plan(rng, st) }    cam = { ctx, nul, content[], P }
//  fx     : { build(fctx, ev) }                 one plan event
//  trans  : { build(tctx), plan(rng, st) }      tctx = { comp, A, B, t0, dur, P, ... }
var JZ_GROUPS = ['layout', 'enter', 'hold', 'exit', 'decor', 'treat', 'bg', 'cam', 'fx', 'trans'];
var JZ_REG = { layout: {}, enter: {}, hold: {}, exit: {}, decor: {}, treat: {}, bg: {}, cam: {}, fx: {}, trans: {} };
function jzReg(g, k, def) {
    if (!JZ_REG[g]) throw new Error('JIZURA: unknown group ' + g);
    def.key = k; def.group = g;
    JZ_REG[g][k] = def;
    return def;
}
function jzMeta(g, k) { var m = JZ_DATA.meta && JZ_DATA.meta[g] ? JZ_DATA.meta[g][k] : null; return m || {}; }
function jzName(g, k) { var m = jzMeta(g, k); return m.name || k; }
function jzHas(g, k) { return !!(k && JZ_REG[g] && JZ_REG[g][k]); }
// implemented keys in the web order (anything registered but unknown to the web goes last)
function jzOrder(g) {
    var o = (JZ_DATA.orders && JZ_DATA.orders[g]) || [], out = [], seen = {}, i, k;
    for (i = 0; i < o.length; i++) if (JZ_REG[g][o[i]] && !seen[o[i]]) { out.push(o[i]); seen[o[i]] = 1; }
    for (k in JZ_REG[g]) if (JZ_REG[g].hasOwnProperty(k) && !seen[k]) out.push(k);
    return out;
}
// key the panel can actually build: itself, else the web's declared counterpart, else a default
var JZ_FALLBACKS = 0;
var JZ_FALLBACK_KEYS = [];
function jzFallback(g, k, dflt) {
    if (jzHas(g, k)) return k;
    if (k && k !== 'none') { JZ_FALLBACKS++; if (jzIndexOf(JZ_FALLBACK_KEYS, g + '.' + k) < 0 && JZ_FALLBACK_KEYS.length < 60) JZ_FALLBACK_KEYS.push(g + '.' + k); }
    var m = jzMeta(g, k);
    if (m.ae && jzHas(g, m.ae)) return m.ae;
    return dflt;
}
// is a part set switched on? (same defaults as the browser)
function jzSetOn(o, s) { if (o && (o[s] === true || o[s] === false)) return o[s]; return s !== 'horror'; }
// may random picks use this entry?  (\u8FFD\u52A0\u5206 first, then \u548C\u98A8 \u2014 same rule as the browser)
function jzRandomOk(o, g, k) {
    var m = g === 'style' ? JZ_DATA.styles[k] : (g === 'font' ? JZ_DATA.fonts[k] : jzMeta(g, k));
    if (!m) return false;
    if (m.extra && !(o && o.extra === true)) return false;
    if (m.wa && o && o.wa === false) return false;
    if (m.set && !jzSetOn(o, m.set)) return false;   // part sets: \u30DB\u30E9\u30FC (off unless switched on), \u6587\u5B57PV\u7CFB / \u30AD\u30CD\u30C6\u30A3\u30C3\u30AF (on)
    return true;
}
// inDur / outDur tables exported from the web (duration grid 0.2..4.0 s, optional glyph-count rows)
function jzTab(tb, dur, n) {
    if (!tb) return null;
    var G = JZ_DATA.durGrid, row = tb.d, i;
    if (tb.n) { var best = 0; for (i = 0; i < tb.n.length; i++) if (Math.abs(tb.n[i] - n) < Math.abs(tb.n[best] - n)) best = i; row = tb.d[best]; }
    if (dur <= G[0]) return row[0];
    if (dur >= G[G.length - 1]) return row[row.length - 1];
    var f = (dur - G[0]) / (G[1] - G[0]), j = Math.floor(f), u = f - j;
    return row[j] + (row[j + 1] - row[j]) * u;
}
function jzFitsN(k, n) { var m = jzMeta('layout', k); if (!m.fits) return true; n = jzClamp(Math.round(n), 1, 60); return m.fits.charAt(n - 1) === '1'; }

// ================================================================ builder helpers
var JZLOG = [];
function jzWarn(m) { if (JZLOG.length < 400) JZLOG.push(m); }
// number for expression source; negatives are parenthesised so 'a-' + jzN(-2) never becomes the syntax error 'a--2'
function jzN(x) { var v = Math.round(x * 10000) / 10000; return v < 0 ? '(' + String(v) + ')' : String(v); }

// ---- fonts: map JIZURA font keys to PostScript names, verify when the API exists
var JZ_FONT_CANDIDATES = {
    gothic_black: ['NotoSansJP-Black', 'NotoSansCJKjp-Black', 'SourceHanSansJP-Heavy', 'KozGoPr6N-Heavy', 'HiraginoSans-W8', 'YuGothic-Bold', 'Meiryo-Bold'],
    gothic_bold: ['NotoSansJP-Bold', 'NotoSansCJKjp-Bold', 'SourceHanSansJP-Bold', 'KozGoPr6N-Bold', 'HiraginoSans-W6', 'YuGothic-Bold', 'Meiryo-Bold'],
    gothic_med: ['NotoSansJP-Medium', 'NotoSansCJKjp-Medium', 'SourceHanSansJP-Medium', 'KozGoPr6N-Medium', 'HiraginoSans-W4', 'YuGothic-Medium', 'Meiryo'],
    gothic_light: ['NotoSansJP-Light', 'NotoSansCJKjp-Light', 'KozGoPr6N-Light', 'HiraginoSans-W2', 'YuGothic-Light', 'Meiryo'],
    dela: ['DelaGothicOne-Regular', 'NotoSansJP-Black', 'KozGoPr6N-Heavy', 'YuGothic-Bold'],
    zenkaku: ['ZenKakuGothicNew-Black', 'NotoSansJP-Black', 'KozGoPr6N-Heavy', 'YuGothic-Bold'],
    mincho_black: ['ZenOldMincho-Black', 'NotoSerifJP-Black', 'KozMinPr6N-Heavy', 'HiraMinProN-W6', 'YuMincho-Demibold'],
    mincho_bold: ['NotoSerifJP-Bold', 'NotoSerifCJKjp-Bold', 'SourceHanSerifJP-Bold', 'KozMinPr6N-Bold', 'HiraMinProN-W6', 'YuMincho-Demibold'],
    mincho: ['NotoSerifJP-Medium', 'NotoSerifCJKjp-Medium', 'SourceHanSerifJP-Medium', 'KozMinPr6N-Medium', 'HiraMinProN-W3', 'YuMincho-Regular', 'MS-Mincho'],
    mincho_light: ['NotoSerifJP-Light', 'NotoSerifCJKjp-Light', 'KozMinPr6N-Light', 'HiraMinProN-W3', 'YuMincho-Light', 'YuMincho-Regular'],
    tokumin: ['KaiseiTokumin-ExtraBold', 'ZenOldMincho-Black', 'KozMinPr6N-Heavy', 'YuMincho-Demibold'],
    round: ['MPLUSRounded1c-ExtraBold', 'RoundedMplus1c-Black', 'HiraMaruProN-W4', 'NotoSansJP-Black', 'YuGothic-Bold'],
    pop: ['MochiyPopOne-Regular', 'MPLUSRounded1c-ExtraBold', 'YuGothic-Bold'],
    dot: ['DotGothic16-Regular', 'MS-Gothic', 'YuGothic-Regular'],
    brush: ['YujiSyuku-Regular', 'YuMincho-Demibold'],
    mono: ['IBMPlexMono-Medium', 'Consolas', 'CourierNewPSMT'],
    sansui: ['IBMPlexSansJP-Medium', 'NotoSansJP-Medium', 'YuGothic-Medium', 'Meiryo'],
    reggae: ['ReggaeOne-Regular', 'DelaGothicOne-Regular', 'NotoSansJP-Black', 'YuGothic-Bold'],
    rampart: ['RampartOne-Regular', 'NotoSansJP-Black', 'YuGothic-Bold'],
    potta: ['PottaOne-Regular', 'MochiyPopOne-Regular', 'YuGothic-Bold'],
    kiwi: ['KiwiMaru-Medium', 'MPLUSRounded1c-ExtraBold', 'YuGothic-Medium'],
    klee: ['KleeOne-SemiBold', 'Klee-Demibold', 'Klee-Medium', 'YuMincho-Demibold'],
    shippori: ['ShipporiMinchoB1-ExtraBold', 'ZenOldMincho-Black', 'KozMinPr6N-Heavy', 'HiraMinProN-W6', 'YuMincho-Demibold']
};
var JZ_ROLE_DEFAULT = { display: 'YuGothic-Bold', serif: 'YuMincho-Demibold', body: 'YuGothic-Medium', mono: 'Consolas' };
var JZ_FONT_CACHE = {};
function jzFontExists(ps) {
    if (JZ_FONT_CACHE.hasOwnProperty(ps)) return JZ_FONT_CACHE[ps];
    var ok = null;
    try { if (app.fonts && app.fonts.getFontsByPostScriptName) { var r = app.fonts.getFontsByPostScriptName(ps); ok = !!(r && r.length); } } catch (e) { ok = null; }
    JZ_FONT_CACHE[ps] = ok;
    return ok;
}
function jzRoleOf(key) {
    if (!key) return 'display';
    if (/mincho|tokumin|brush|shippori|klee/.test(key)) return 'serif';
    if (/mono/.test(key)) return 'mono';
    if (/med|light|sansui/.test(key)) return 'body';
    return 'display';
}
// resolve: explicit user role font > key candidates that exist > role default
// JZ_FONT_MISSING collects the keys whose own typeface (the browser's Google Font) is not installed, for the build report
var JZ_FONT_MISSING = {}, JZ_FONT_NOAPI = false;
// ---- lyric language (plan.lang from the browser): Chinese / Korean lyrics are drawn with faces that have their glyphs.
// Per language: key \u2192 PostScript candidates (the browser's Google Font first, then OS fonts that always cover the script).
var JZ_LANG = 'ja';
var JZ_LANG_SYS = {
    'zh-Hant': { sansB: ['PingFangTC-Semibold', 'MicrosoftJhengHeiBold', 'NotoSansCJKtc-Bold', 'SourceHanSansTC-Bold'], sans: ['PingFangTC-Regular', 'MicrosoftJhengHeiRegular', 'NotoSansCJKtc-Regular', 'SourceHanSansTC-Regular'],
                 serifB: ['STSongti-TC-Bold', 'NotoSerifCJKtc-Bold', 'SourceHanSerifTC-Bold', 'PMingLiU'], serif: ['STSongti-TC-Regular', 'NotoSerifCJKtc-Regular', 'SourceHanSerifTC-Regular', 'PMingLiU'] },
    'zh-Hans': { sansB: ['PingFangSC-Semibold', 'MicrosoftYaHei-Bold', 'NotoSansCJKsc-Bold', 'SourceHanSansSC-Bold'], sans: ['PingFangSC-Regular', 'MicrosoftYaHei', 'NotoSansCJKsc-Regular', 'SourceHanSansSC-Regular'],
                 serifB: ['STSongti-SC-Bold', 'NotoSerifCJKsc-Bold', 'SourceHanSerifSC-Bold', 'SimSun'], serif: ['STSongti-SC-Regular', 'NotoSerifCJKsc-Regular', 'SourceHanSerifSC-Regular', 'SimSun'] },
    ko: { sansB: ['AppleSDGothicNeo-Bold', 'MalgunGothicBold', 'NotoSansCJKkr-Bold', 'SourceHanSansKR-Bold'], sans: ['AppleSDGothicNeo-Regular', 'MalgunGothic', 'NotoSansCJKkr-Regular', 'SourceHanSansKR-Regular'],
          serifB: ['AppleMyungjo', 'NotoSerifCJKkr-Bold', 'SourceHanSerifKR-Bold', 'Batang'], serif: ['AppleMyungjo', 'NotoSerifCJKkr-Regular', 'SourceHanSerifKR-Regular', 'Batang'] }
};
// [family (for messages), own PostScript names\u2026, system group]
var JZ_LANG_FONTS = {
    'zh-Hant': {
        gothic_black: ['Noto Sans TC', 'NotoSansTC-Black', 'NotoSansTCThin-Black', 'sansB'], gothic_bold: ['Noto Sans TC', 'NotoSansTC-Bold', 'NotoSansTCThin-Bold', 'sansB'],
        gothic_med: ['Noto Sans TC', 'NotoSansTC-Medium', 'NotoSansTCThin-Medium', 'sans'], gothic_light: ['Noto Sans TC', 'NotoSansTC-Light', 'NotoSansTCThin-Light', 'sans'],
        zenkaku: ['Noto Sans TC', 'NotoSansTC-Black', 'NotoSansTCThin-Black', 'sansB'], sansui: ['Noto Sans TC', 'NotoSansTC-Medium', 'NotoSansTCThin-Medium', 'sans'],
        dot: ['Noto Sans TC', 'NotoSansTC-Medium', 'NotoSansTCThin-Medium', 'sans'],
        mincho_black: ['Noto Serif TC', 'NotoSerifTC-Black', 'NotoSerifTCExtraLight-Black', 'serifB'], mincho_bold: ['Noto Serif TC', 'NotoSerifTC-Bold', 'NotoSerifTCExtraLight-Bold', 'serifB'],
        mincho: ['Noto Serif TC', 'NotoSerifTC-Medium', 'NotoSerifTCExtraLight-Medium', 'serif'], mincho_light: ['Noto Serif TC', 'NotoSerifTC-Light', 'NotoSerifTCExtraLight-Light', 'serif'],
        tokumin: ['Noto Serif TC', 'NotoSerifTC-ExtraBold', 'NotoSerifTCExtraLight-ExtraBold', 'serifB'], shippori: ['Noto Serif TC', 'NotoSerifTC-ExtraBold', 'NotoSerifTCExtraLight-ExtraBold', 'serifB'],
        dela: ['WDXL Lubrifont TC', 'WDXLLubrifontTC-Regular', 'NotoSansTC-Black', 'sansB'], round: ['Chiron GoRound TC', 'ChironGoRoundTC-ExtraBold', 'ChironGoRoundTCExtraLight-ExtraBold', 'sansB'],
        pop: ['Huninn', 'Huninn-Regular', 'sansB'], kiwi: ['Huninn', 'Huninn-Regular', 'sans'],
        klee: ['LXGW WenKai TC', 'LXGWWenKaiTC-Bold', 'serifB'], brush: ['LXGW WenKai TC', 'LXGWWenKaiTC-Bold', 'serifB'],
        reggae: ['LXGW Marker Gothic', 'LXGWMarkerGothic-Regular', 'sansB'], rampart: ['LXGW Marker Gothic', 'LXGWMarkerGothic-Regular', 'sansB'], potta: ['LXGW Marker Gothic', 'LXGWMarkerGothic-Regular', 'sansB']
    },
    'zh-Hans': {
        gothic_black: ['Noto Sans SC', 'NotoSansSC-Black', 'NotoSansSCThin-Black', 'sansB'], gothic_bold: ['Noto Sans SC', 'NotoSansSC-Bold', 'NotoSansSCThin-Bold', 'sansB'],
        gothic_med: ['Noto Sans SC', 'NotoSansSC-Medium', 'NotoSansSCThin-Medium', 'sans'], gothic_light: ['Noto Sans SC', 'NotoSansSC-Light', 'NotoSansSCThin-Light', 'sans'],
        zenkaku: ['Noto Sans SC', 'NotoSansSC-Black', 'NotoSansSCThin-Black', 'sansB'], sansui: ['Noto Sans SC', 'NotoSansSC-Medium', 'NotoSansSCThin-Medium', 'sans'],
        dot: ['Noto Sans SC', 'NotoSansSC-Medium', 'NotoSansSCThin-Medium', 'sans'],
        mincho_black: ['Noto Serif SC', 'NotoSerifSC-Black', 'NotoSerifSCExtraLight-Black', 'serifB'], mincho_bold: ['Noto Serif SC', 'NotoSerifSC-Bold', 'NotoSerifSCExtraLight-Bold', 'serifB'],
        mincho: ['Noto Serif SC', 'NotoSerifSC-Medium', 'NotoSerifSCExtraLight-Medium', 'serif'], mincho_light: ['Noto Serif SC', 'NotoSerifSC-Light', 'NotoSerifSCExtraLight-Light', 'serif'],
        tokumin: ['Noto Serif SC', 'NotoSerifSC-ExtraBold', 'NotoSerifSCExtraLight-ExtraBold', 'serifB'], shippori: ['Noto Serif SC', 'NotoSerifSC-ExtraBold', 'NotoSerifSCExtraLight-ExtraBold', 'serifB'],
        dela: ['ZCOOL QingKe HuangYou', 'ZCOOLQingKeHuangYou-Regular', 'sansB'], round: ['ZCOOL KuaiLe', 'ZCOOLKuaiLe-Regular', 'sansB'],
        pop: ['ZCOOL KuaiLe', 'ZCOOLKuaiLe-Regular', 'sansB'], kiwi: ['ZCOOL KuaiLe', 'ZCOOLKuaiLe-Regular', 'sans'],
        klee: ['ZCOOL XiaoWei', 'ZCOOLXiaoWei-Regular', 'serif'], brush: ['Ma Shan Zheng', 'MaShanZheng-Regular', 'serifB'],
        reggae: ['ZCOOL QingKe HuangYou', 'ZCOOLQingKeHuangYou-Regular', 'sansB'], rampart: ['ZCOOL QingKe HuangYou', 'ZCOOLQingKeHuangYou-Regular', 'sansB'], potta: ['Ma Shan Zheng', 'MaShanZheng-Regular', 'sansB']
    },
    ko: {
        gothic_black: ['Noto Sans KR', 'NotoSansKR-Black', 'NotoSansKRThin-Black', 'sansB'], gothic_bold: ['Noto Sans KR', 'NotoSansKR-Bold', 'NotoSansKRThin-Bold', 'sansB'],
        gothic_med: ['Noto Sans KR', 'NotoSansKR-Medium', 'NotoSansKRThin-Medium', 'sans'], gothic_light: ['Noto Sans KR', 'NotoSansKR-Light', 'NotoSansKRThin-Light', 'sans'],
        zenkaku: ['Noto Sans KR', 'NotoSansKR-Black', 'NotoSansKRThin-Black', 'sansB'], sansui: ['IBM Plex Sans KR', 'IBMPlexSansKR-Medium', 'NotoSansKR-Medium', 'sans'],
        dot: ['Noto Sans KR', 'NotoSansKR-Medium', 'NotoSansKRThin-Medium', 'sans'],
        mincho_black: ['Noto Serif KR', 'NotoSerifKR-Black', 'NotoSerifKRExtraLight-Black', 'serifB'], mincho_bold: ['Noto Serif KR', 'NotoSerifKR-Bold', 'NotoSerifKRExtraLight-Bold', 'serifB'],
        mincho: ['Noto Serif KR', 'NotoSerifKR-Medium', 'NotoSerifKRExtraLight-Medium', 'serif'], mincho_light: ['Noto Serif KR', 'NotoSerifKR-Light', 'NotoSerifKRExtraLight-Light', 'serif'],
        tokumin: ['Noto Serif KR', 'NotoSerifKR-ExtraBold', 'NotoSerifKRExtraLight-ExtraBold', 'serifB'], shippori: ['Noto Serif KR', 'NotoSerifKR-ExtraBold', 'NotoSerifKRExtraLight-ExtraBold', 'serifB'],
        dela: ['Black Han Sans', 'BlackHanSans-Regular', 'sansB'], round: ['Jua', 'Jua-Regular', 'sansB'],
        pop: ['Do Hyeon', 'DoHyeon-Regular', 'sansB'], kiwi: ['Gowun Dodum', 'GowunDodum-Regular', 'sans'],
        klee: ['Gowun Batang', 'GowunBatang-Bold', 'serifB'], brush: ['Nanum Brush Script', 'NanumBrush', 'NanumBrushScript-Regular', 'serifB'],
        reggae: ['Black Han Sans', 'BlackHanSans-Regular', 'sansB'], rampart: ['Black Han Sans', 'BlackHanSans-Regular', 'sansB'], potta: ['Nanum Brush Script', 'NanumBrush', 'NanumBrushScript-Regular', 'sansB']
    }
};
// random characters in the lyric's own writing system (same sets as the browser's J.pool \u2014 issue #16)
var JZ_POOLS = {
    ja: { kana: '\u30A2\u30A4\u30A6\u30A8\u30AA\u30AB\u30AD\u30AF\u30B1\u30B3\u30B5\u30B7\u30B9\u30BB\u30BD\u30BF\u30C1\u30C4\u30C6\u30C8\u30CA\u30CB\u30CC\u30CD\u30CE\u30CF\u30D2\u30D5\u30D8\u30DB\u30DE\u30DF\u30E0\u30E1\u30E2\u30E4\u30E6\u30E8\u30E9\u30EA\u30EB\u30EC\u30ED\u30EF\u30F2\u30F3', hira: '\u3042\u3044\u3046\u3048\u304A\u304B\u304D\u304F\u3051\u3053\u3055\u3057\u3059\u305B\u305D\u305F\u3061\u3064\u3066\u3068\u306A\u306B\u306C\u306D\u306E\u306F\u3072\u3075\u3078\u307B\u307E\u307F\u3080\u3081\u3082\u3084\u3086\u3088\u3089\u308A\u308B\u308C\u308D\u308F\u3092\u3093' },
    'zh-Hant': { kana: '\u7684\u4E00\u662F\u4E0D\u4E86\u4EBA\u6211\u5728\u6709\u4ED6\u9019\u4E2D\u5927\u4F86\u4E0A\u570B\u500B\u5230\u8AAA\u5011\u70BA\u5B50\u548C\u4F60\u5730\u51FA\u9053\u4E5F\u6642\u5E74\u5F97\u5C31\u90A3\u8981\u4E0B\u4EE5\u751F\u6703\u81EA\u8457\u53BB\u4E4B\u904E\u5BB6\u5B78\u5C0D\u53EF\u5979\u88E1\u5F8C\u5C0F\u9EBC\u5FC3\u591A\u5929\u800C\u80FD\u597D\u90FD\u7136\u6C92\u65E5\u65BC\u8D77\u9084\u767C\u6210\u4E8B\u53EA\u4F5C\u7576\u60F3\u770B\u6587\u7121\u958B\u624B\u5341\u7528\u4E3B\u884C\u65B9\u53C8\u5982\u524D\u6240\u672C\u898B\u7D93\u982D\u9762\u516C\u540C\u4E09\u5DF2\u8001\u5F9E\u52D5\u5169\u9577\u77E5\u6C11\u6A23\u73FE\u5206\u5C07\u5916\u4F46\u8EAB\u4E9B\u8207\u9AD8\u610F\u9032\u628A\u6CD5\u6B64\u5BE6\u56DE\u4E8C\u7406\u7F8E\u9EDE\u6708\u660E\u5176\u7A2E\u8072\u5168\u5DE5\u5DF1\u8A71\u5152\u8005\u5411\u60C5\u90E8\u6B63\u540D\u5B9A\u5973\u554F\u529B\u6A5F\u7D66\u7B49\u5E7E\u5F88\u6700\u9593\u65B0\u4EC0\u6253\u4FBF\u4F4D\u56E0\u91CD\u88AB\u8D70\u96FB\u56DB\u7B2C\u9580\u76F8\u6B21\u6771\u6D77\u53E3\u4F7F\u897F\u518D\u5E73\u771F\u807D\u4E16\u6C23\u4FE1\u5317\u5C11\u95DC\u611B\u5922\u5149\u5F71\u7A7A\u591C\u661F\u96E8\u6DDA\u6200\u82B1\u98A8', hira: '\u7684\u4E00\u662F\u4E0D\u4E86\u4EBA\u6211\u5728\u6709\u4ED6\u9019\u4E2D\u5927\u4F86\u4E0A\u570B\u500B\u5230\u8AAA\u5011\u70BA\u5B50\u548C\u4F60\u5730\u51FA\u9053\u4E5F\u6642\u5E74\u5F97\u5C31\u90A3\u8981\u4E0B\u4EE5\u751F\u6703\u81EA\u8457\u53BB\u4E4B\u904E\u5BB6\u5B78\u5C0D\u53EF\u5979\u88E1\u5F8C\u5C0F\u9EBC\u5FC3\u591A\u5929\u800C\u80FD\u597D\u90FD\u7136\u6C92\u65E5\u65BC\u8D77\u9084\u767C\u6210\u4E8B\u53EA\u4F5C\u7576\u60F3\u770B\u6587\u7121\u958B\u624B\u5341\u7528\u4E3B\u884C\u65B9\u53C8\u5982\u524D\u6240\u672C\u898B\u7D93\u982D\u9762\u516C\u540C\u4E09\u5DF2\u8001\u5F9E\u52D5\u5169\u9577\u77E5\u6C11\u6A23\u73FE\u5206\u5C07\u5916\u4F46\u8EAB\u4E9B\u8207\u9AD8\u610F\u9032\u628A\u6CD5\u6B64\u5BE6\u56DE\u4E8C\u7406\u7F8E\u9EDE\u6708\u660E\u5176\u7A2E\u8072\u5168\u5DE5\u5DF1\u8A71\u5152\u8005\u5411\u60C5\u90E8\u6B63\u540D\u5B9A\u5973\u554F\u529B\u6A5F\u7D66\u7B49\u5E7E\u5F88\u6700\u9593\u65B0\u4EC0\u6253\u4FBF\u4F4D\u56E0\u91CD\u88AB\u8D70\u96FB\u56DB\u7B2C\u9580\u76F8\u6B21\u6771\u6D77\u53E3\u4F7F\u897F\u518D\u5E73\u771F\u807D\u4E16\u6C23\u4FE1\u5317\u5C11\u95DC\u611B\u5922\u5149\u5F71\u7A7A\u591C\u661F\u96E8\u6DDA\u6200\u82B1\u98A8' },
    'zh-Hans': { kana: '\u7684\u4E00\u662F\u4E0D\u4E86\u4EBA\u6211\u5728\u6709\u4ED6\u8FD9\u4E2D\u5927\u6765\u4E0A\u56FD\u4E2A\u5230\u8BF4\u4EEC\u4E3A\u5B50\u548C\u4F60\u5730\u51FA\u9053\u4E5F\u65F6\u5E74\u5F97\u5C31\u90A3\u8981\u4E0B\u4EE5\u751F\u4F1A\u81EA\u7740\u53BB\u4E4B\u8FC7\u5BB6\u5B66\u5BF9\u53EF\u5979\u91CC\u540E\u5C0F\u4E48\u5FC3\u591A\u5929\u800C\u80FD\u597D\u90FD\u7136\u6CA1\u65E5\u4E8E\u8D77\u8FD8\u53D1\u6210\u4E8B\u53EA\u4F5C\u5F53\u60F3\u770B\u6587\u65E0\u5F00\u624B\u5341\u7528\u4E3B\u884C\u65B9\u53C8\u5982\u524D\u6240\u672C\u89C1\u7ECF\u5934\u9762\u516C\u540C\u4E09\u5DF2\u8001\u4ECE\u52A8\u4E24\u957F\u77E5\u6C11\u6837\u73B0\u5206\u5C06\u5916\u4F46\u8EAB\u4E9B\u4E0E\u9AD8\u610F\u8FDB\u628A\u6CD5\u6B64\u5B9E\u56DE\u4E8C\u7406\u7F8E\u70B9\u6708\u660E\u5176\u79CD\u58F0\u5168\u5DE5\u5DF1\u8BDD\u513F\u8005\u5411\u60C5\u90E8\u6B63\u540D\u5B9A\u5973\u95EE\u529B\u673A\u7ED9\u7B49\u51E0\u5F88\u6700\u95F4\u65B0\u4EC0\u6253\u4FBF\u4F4D\u56E0\u91CD\u88AB\u8D70\u7535\u56DB\u7B2C\u95E8\u76F8\u6B21\u4E1C\u6D77\u53E3\u4F7F\u897F\u518D\u5E73\u771F\u542C\u4E16\u6C14\u4FE1\u5317\u5C11\u5173\u7231\u68A6\u5149\u5F71\u7A7A\u591C\u661F\u96E8\u6CEA\u604B\u82B1\u98CE', hira: '\u7684\u4E00\u662F\u4E0D\u4E86\u4EBA\u6211\u5728\u6709\u4ED6\u8FD9\u4E2D\u5927\u6765\u4E0A\u56FD\u4E2A\u5230\u8BF4\u4EEC\u4E3A\u5B50\u548C\u4F60\u5730\u51FA\u9053\u4E5F\u65F6\u5E74\u5F97\u5C31\u90A3\u8981\u4E0B\u4EE5\u751F\u4F1A\u81EA\u7740\u53BB\u4E4B\u8FC7\u5BB6\u5B66\u5BF9\u53EF\u5979\u91CC\u540E\u5C0F\u4E48\u5FC3\u591A\u5929\u800C\u80FD\u597D\u90FD\u7136\u6CA1\u65E5\u4E8E\u8D77\u8FD8\u53D1\u6210\u4E8B\u53EA\u4F5C\u5F53\u60F3\u770B\u6587\u65E0\u5F00\u624B\u5341\u7528\u4E3B\u884C\u65B9\u53C8\u5982\u524D\u6240\u672C\u89C1\u7ECF\u5934\u9762\u516C\u540C\u4E09\u5DF2\u8001\u4ECE\u52A8\u4E24\u957F\u77E5\u6C11\u6837\u73B0\u5206\u5C06\u5916\u4F46\u8EAB\u4E9B\u4E0E\u9AD8\u610F\u8FDB\u628A\u6CD5\u6B64\u5B9E\u56DE\u4E8C\u7406\u7F8E\u70B9\u6708\u660E\u5176\u79CD\u58F0\u5168\u5DE5\u5DF1\u8BDD\u513F\u8005\u5411\u60C5\u90E8\u6B63\u540D\u5B9A\u5973\u95EE\u529B\u673A\u7ED9\u7B49\u51E0\u5F88\u6700\u95F4\u65B0\u4EC0\u6253\u4FBF\u4F4D\u56E0\u91CD\u88AB\u8D70\u7535\u56DB\u7B2C\u95E8\u76F8\u6B21\u4E1C\u6D77\u53E3\u4F7F\u897F\u518D\u5E73\u771F\u542C\u4E16\u6C14\u4FE1\u5317\u5C11\u5173\u7231\u68A6\u5149\u5F71\u7A7A\u591C\u661F\u96E8\u6CEA\u604B\u82B1\u98CE' },
    ko: { kana: '\uAC00\uB098\uB2E4\uB77C\uB9C8\uBC14\uC0AC\uC544\uC790\uCC28\uCE74\uD0C0\uD30C\uD558\uAC70\uB108\uB354\uB7EC\uBA38\uBC84\uC11C\uC5B4\uC800\uCC98\uCEE4\uD130\uD37C\uD5C8\uACE0\uB178\uB3C4\uB85C\uBAA8\uBCF4\uC18C\uC624\uC870\uCD08\uCF54\uD1A0\uD3EC\uD638\uAD6C\uB204\uB450\uB8E8\uBB34\uBD80\uC218\uC6B0\uC8FC\uCD94\uCFE0\uD22C\uD478\uD6C4\uADF8\uB290\uB4DC\uB974\uBBC0\uBE0C\uC2A4\uC73C\uC988\uCE20\uD06C\uD2B8\uD504\uD750\uAE30\uB2C8\uB514\uB9AC\uBBF8\uBE44\uC2DC\uC774\uC9C0\uCE58\uD0A4\uD2F0\uD53C\uD788\uC0AC\uB791\uBCC4\uBE5B\uB9C8\uC74C\uB178\uB798\uD558\uB298\uBC14\uB78C\uAFC8\uB208\uBB3C\uB108\uB098\uC6B0\uB9AC', hira: '\uAC00\uB098\uB2E4\uB77C\uB9C8\uBC14\uC0AC\uC544\uC790\uCC28\uCE74\uD0C0\uD30C\uD558\uAC70\uB108\uB354\uB7EC\uBA38\uBC84\uC11C\uC5B4\uC800\uCC98\uCEE4\uD130\uD37C\uD5C8\uACE0\uB178\uB3C4\uB85C\uBAA8\uBCF4\uC18C\uC624\uC870\uCD08\uCF54\uD1A0\uD3EC\uD638\uAD6C\uB204\uB450\uB8E8\uBB34\uBD80\uC218\uC6B0\uC8FC\uCD94\uCFE0\uD22C\uD478\uD6C4\uADF8\uB290\uB4DC\uB974\uBBC0\uBE0C\uC2A4\uC73C\uC988\uCE20\uD06C\uD2B8\uD504\uD750\uAE30\uB2C8\uB514\uB9AC\uBBF8\uBE44\uC2DC\uC774\uC9C0\uCE58\uD0A4\uD2F0\uD53C\uD788\uC0AC\uB791\uBCC4\uBE5B\uB9C8\uC74C\uB178\uB798\uD558\uB298\uBC14\uB78C\uAFC8\uB208\uBB3C\uB108\uB098\uC6B0\uB9AC' },
    en: { kana: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', hira: 'abcdefghijklmnopqrstuvwxyz' }
};
function jzPool(kind) { var P = JZ_POOLS[JZ_POOL_LANG] || JZ_POOLS.ja; return P[kind] || JZ_POOLS.ja[kind]; }
var JZ_POOL_LANG = 'ja';
function jzSetLang(l) { JZ_LANG = (l && JZ_LANG_FONTS.hasOwnProperty(l)) ? l : 'ja'; JZ_POOL_LANG = (l && JZ_POOLS.hasOwnProperty(l)) ? l : 'ja'; }
// the language's own candidates for a key (null = Japanese faces); JZ_FONT_MISSING gets the family name when its own face is missing
function jzLangFont(key) {
    var T = JZ_LANG_FONTS[JZ_LANG], e = T && T[key], i, ex, sys;
    if (!e) return null;
    for (i = 1; i < e.length; i++) {
        if (JZ_LANG_SYS[JZ_LANG].hasOwnProperty(e[i])) break;
        ex = jzFontExists(e[i]);
        if (i === 1 && ex === false) JZ_FONT_MISSING[key] = e[0];
        if (ex === null) { JZ_FONT_NOAPI = true; return e[1]; }     // no font API (before AE 2024): trust the first name
        if (ex === true) return e[i];
    }
    sys = JZ_LANG_SYS[JZ_LANG][e[e.length - 1]] || [];
    for (i = 0; i < sys.length; i++) if (jzFontExists(sys[i]) === true) return sys[i];
    return null;
}
function jzFont(key, roles) {
    roles = roles || JZ_ROLE_DEFAULT;
    var role = JZ_ROLE_DEFAULT.hasOwnProperty(key) ? key : jzRoleOf(key);
    if (roles.__force && roles[role]) return roles[role];
    if (JZ_LANG !== 'ja') { var lf = jzLangFont(key); if (lf) return lf; }
    var cands = JZ_FONT_CANDIDATES[key] || [];
    for (var i = 0; i < cands.length; i++) {
        var ex = jzFontExists(cands[i]);
        if (i === 0 && ex === false) JZ_FONT_MISSING[key] = true;
        if (ex === null) JZ_FONT_NOAPI = true;
        if (ex === true) return cands[i];
    }
    return roles[role] || JZ_ROLE_DEFAULT[role];
}
// family names of the missing typefaces (e.g. "Klee One"), for messages
function jzMissingFonts() {
    var out = [], seen = {}, k, fam;
    for (k in JZ_FONT_MISSING) if (JZ_FONT_MISSING.hasOwnProperty(k)) {
        fam = typeof JZ_FONT_MISSING[k] === 'string' ? JZ_FONT_MISSING[k] : (JZ_DATA.fonts && JZ_DATA.fonts[k] && JZ_DATA.fonts[k].family) || k;
        if (!seen[fam]) { seen[fam] = true; out.push(fam); }
    }
    return out;
}

// ---- text layers
function jzText(ctx, str, o) {
    var comp = ctx.comp;
    var L = comp.layers.addText(str);
    try { L.name = (o.name || String(str).replace(/\r/g, '')).substr(0, 28); } catch (e) {}
    var src = L.property('ADBE Text Properties').property('ADBE Text Document');
    var td = src.value;
    try { td.resetCharStyle(); } catch (e1) {}
    try { td.resetParagraphStyle(); } catch (e2) {}
    td.fontSize = Math.max(1, o.size || 100);
    var f = jzFont(o.font || 'display', ctx.roles);
    try { td.font = f; } catch (e3) { jzWarn('font not set: ' + f); }
    td.applyFill = o.fill !== false;
    if (td.applyFill) td.fillColor = jzHex(o.color || '#ffffff');
    if (o.stroke) { td.applyStroke = true; td.strokeColor = jzHex(o.strokeColor || o.color || '#ffffff'); td.strokeWidth = o.stroke; try { td.strokeOverFill = !!o.strokeOver; } catch (e4) {} }
    else td.applyStroke = false;
    td.tracking = Math.round((o.track || 0) * 1000);
    td.justification = o.align === 'left' ? ParagraphJustification.LEFT_JUSTIFY : (o.align === 'right' ? ParagraphJustification.RIGHT_JUSTIFY : ParagraphJustification.CENTER_JUSTIFY);
    if (o.leading) { try { td.autoLeading = false; td.leading = o.leading; } catch (e5) {} }
    src.setValue(td);
    if (o.maxW || o.maxH) jzFit(L, o.maxW || 1e6, o.maxH || 1e6, o.maxSize);
    jzAnchor(L, o.align);
    var tr = L.property('ADBE Transform Group');
    tr.property('ADBE Position').setValue([o.x, o.y]);
    if (o.sx || o.sy) tr.property('ADBE Scale').setValue([(o.sx || 1) * 100, (o.sy || 1) * 100]);
    if (o.rot) tr.property('ADBE Rotate Z').setValue(o.rot);
    if (o.opacity != null) tr.property('ADBE Opacity').setValue(o.opacity * 100);
    try { // per-character anchor for rotation / scale animators
        var more = L.property('ADBE Text Properties').property('ADBE Text More Options');
        more.property('ADBE Text Anchor Point Option').setValue(1);
        more.property('ADBE Text Anchor Point Align').setValue([0, -50]);
    } catch (e6) {}
    if (o.blend) L.blendingMode = o.blend;
    return L;
}
function jzVertical(str) { return jzChars(String(str).replace(/[\s\u3000]+/g, '')).join('\r'); }
function jzRect(L) { try { return L.sourceRectAtTime(0, false); } catch (e) { return { left: 0, top: 0, width: 100, height: 100 }; } }
function jzAnchor(L, align) {
    var r = jzRect(L);
    var ax = align === 'left' ? r.left : (align === 'right' ? r.left + r.width : r.left + r.width / 2);
    L.property('ADBE Transform Group').property('ADBE Anchor Point').setValue([ax, r.top + r.height / 2]);
    return r;
}
function jzFit(L, maxW, maxH, maxSize) {
    var r = jzRect(L), src = L.property('ADBE Text Properties').property('ADBE Text Document'), td = src.value;
    var k = Math.min(maxW / Math.max(1, r.width), maxH / Math.max(1, r.height));
    var s = td.fontSize * k;
    if (maxSize) s = Math.min(s, maxSize);
    td.fontSize = Math.max(1, s); src.setValue(td);
}
function jzSize(L) { var r = jzRect(L); var sc = L.property('ADBE Transform Group').property('ADBE Scale').value; return [r.width * sc[0] / 100, r.height * sc[1] / 100]; }
function jzFontSize(L) { return L.property('ADBE Text Properties').property('ADBE Text Document').value.fontSize; }

// ---- shape layers
function jzShapeLayer(ctx, name, x, y) {
    var L = ctx.comp.layers.addShape(); L.name = name || 'shape';
    L.property('ADBE Transform Group').property('ADBE Position').setValue([x || 0, y || 0]);
    return L;
}
function jzGrp(L, name) { var g = L.property('ADBE Root Vectors Group').addProperty('ADBE Vector Group'); if (name) g.name = name; return g; }
function jzVecs(g) { return g.property('ADBE Vectors Group'); }
function jzAddRect(g, w, h, round, x, y) {
    var r = jzVecs(g).addProperty('ADBE Vector Shape - Rect');
    r.property('ADBE Vector Rect Size').setValue([w, h]);
    if (round) r.property('ADBE Vector Rect Roundness').setValue(round);
    if (x || y) r.property('ADBE Vector Rect Position').setValue([x || 0, y || 0]);
    return r;
}
function jzAddEllipse(g, w, h, x, y) {
    var r = jzVecs(g).addProperty('ADBE Vector Shape - Ellipse');
    r.property('ADBE Vector Ellipse Size').setValue([w, h]);
    if (x || y) r.property('ADBE Vector Ellipse Position').setValue([x || 0, y || 0]);
    return r;
}
function jzAddPath(g, pts, closed) {
    var p = jzVecs(g).addProperty('ADBE Vector Shape - Group');
    var sh = new Shape(); sh.vertices = pts; sh.closed = !!closed;
    p.property('ADBE Vector Shape').setValue(sh);
    return p;
}
function jzAddStar(g, pts, r1, r2) {
    var s = jzVecs(g).addProperty('ADBE Vector Shape - Star');
    s.property('ADBE Vector Star Type').setValue(1);
    s.property('ADBE Vector Star Points').setValue(pts);
    s.property('ADBE Vector Star Outer Radius').setValue(r1);
    s.property('ADBE Vector Star Inner Radius').setValue(r2);
    return s;
}
function jzAddFill(g, hex, op) {
    var f = jzVecs(g).addProperty('ADBE Vector Graphic - Fill');
    f.property('ADBE Vector Fill Color').setValue(jzHex(hex));
    if (op != null) f.property('ADBE Vector Fill Opacity').setValue(op);
    return f;
}
function jzAddStroke(g, hex, w, op) {
    var s = jzVecs(g).addProperty('ADBE Vector Graphic - Stroke');
    s.property('ADBE Vector Stroke Color').setValue(jzHex(hex));
    s.property('ADBE Vector Stroke Width').setValue(w || 2);
    if (op != null) s.property('ADBE Vector Stroke Opacity').setValue(op);
    return s;
}
function jzAddTrimPaths(g, endExpr, startExpr) {
    var t = jzVecs(g).addProperty('ADBE Vector Filter - Trim');
    if (endExpr) t.property('ADBE Vector Trim End').expression = endExpr;
    if (startExpr) t.property('ADBE Vector Trim Start').expression = startExpr;
    return t;
}
function jzGX(g) { return g.property('ADBE Vector Transform Group'); }

// ---- effects (parameters are addressed by index so localized AE versions work)
function jzEffect(L, mn, name) {
    try { var e = L.property('ADBE Effect Parade').addProperty(mn); if (name) e.name = name; return e; }
    catch (err) { jzWarn('effect unavailable: ' + mn); return null; }
}
function jzEP(e, idx, v) { if (!e) return; try { e.property(idx).setValue(v); } catch (err) { jzWarn('param ' + e.matchName + '#' + idx + ': ' + err.toString()); } }
function jzEX(e, idx, ex) { if (!e) return; try { e.property(idx).expression = ex; } catch (err) { jzWarn('expr ' + e.matchName + '#' + idx + ': ' + err.toString()); } }
function jzXf(L, mn) { return L.property('ADBE Transform Group').property(mn); }
function jzSetExpr(prop, ex) { try { prop.expression = ex; } catch (err) { jzWarn('expr: ' + err.toString()); } }

// ---- text animators with an Expression Selector
function jzAnimator(L, name, props, amountExpr) {
    var anims = L.property('ADBE Text Properties').property('ADBE Text Animators');
    var an = anims.addProperty('ADBE Text Animator');
    an.name = name;
    var idx = an.propertyIndex, i;
    for (i = 0; i < props.length; i++) anims.property(idx).property('ADBE Text Animator Properties').addProperty(props[i][0]);
    for (i = 0; i < props.length; i++) {
        try { anims.property(idx).property('ADBE Text Animator Properties').property(props[i][0]).setValue(props[i][1]); }
        catch (err) { jzWarn('animator ' + props[i][0] + ': ' + err.toString()); }
    }
    anims.property(idx).property('ADBE Text Selectors').addProperty('ADBE Text Expressible Selector');
    try { anims.property(idx).property('ADBE Text Selectors').property(1).property('ADBE Text Expressible Amount').expression = amountExpr; }
    catch (err2) { jzWarn('selector expr: ' + err2.toString()); }
    return anims.property(idx);
}

// ================================================================ porting helpers (shared by the expression packs ae/p_*.jsx)
// sizes: the browser layouts work in "design px" with U = min(W, H); ctx.W / ctx.H here are the comp's real pixels,
// so jzU(ctx) is the same quantity at comp scale (use it wherever the browser code uses U(env)).
function jzU(ctx) { return Math.min(ctx.W, ctx.H); }
function jzPortrait(ctx) { return ctx.H > ctx.W * 1.08; }
function jzPal(sc) { var o = [], k = ['bg', 'fg', 'ink', 'sub', 'accent', 'accent2', 'dim']; for (var i = 0; i < k.length; i++) if (sc[k[i]]) o.push(sc[k[i]]); return o; }
function jzLightest(sc) { var p = jzPal(sc), b = p[0]; for (var i = 1; i < p.length; i++) if (jzLum(p[i]) > jzLum(b)) b = p[i]; return b; }
function jzDarkest(sc) { var p = jzPal(sc), b = p[0]; for (var i = 1; i < p.length; i++) if (jzLum(p[i]) < jzLum(b)) b = p[i]; return b; }
function jzFontKeyOf(st, role, i) { var r = st.fonts[role] || st.fonts.display; return r[(i || 0) % r.length]; }
// timing header for secondary layers inside the content comp (time 0 = cut start):
//   DUR, IN, OS (= exit start), OD, plus easing functions (cl oe ioe oc ic iq ie ob bo) and
//   PO = exit progress 0..1, K = 1 - ic(PO) (fade-out factor that follows the cut's exit)
function jzTH(ctx) {
    // outDur 0 (hard cut out): nothing fades before the cut ends (the browser's pOut stays 0)
    var c = ctx.cut, hard = c.outDur != null && c.outDur <= 0, od = hard ? 0.05 : Math.max(0.12, c.outDur || 0.15);
    return 'var DUR=' + jzN(c.dur) + ',IN=' + jzN(Math.max(0.02, c.inDur || 0.3)) + ',OS=' + jzN(hard ? c.dur : c.dur - od) + ',OD=' + jzN(od) + ',SD=' + (c.seed % 99991) + ';\n' + JZ_FNS +
        'var PO=cl((time-OS)/OD),K=1-ic(PO);\n';
}
// opacity: fade in from `delay` over `len` s, follow the cut's exit
function jzFade(ctx, L, delay, len) { jzSetExpr(jzXf(L, 'ADBE Opacity'), jzTH(ctx) + 'value*oc((time-' + jzN(delay || 0) + ')/' + jzN(len || 0.3) + ')*K'); }
// scale from 0 (or along one axis: axis 'x' / 'y') with a soft overshoot, follows the cut's exit
function jzGrow(ctx, L, delay, len, axis) {
    var e = 'var q=cl((time-' + jzN(delay || 0) + ')/' + jzN(len || 0.35) + ');var s=q<=0?0:ob(q,1.4);s*=K;';
    jzSetExpr(jzXf(L, 'ADBE Scale'), jzTH(ctx) + e + (axis === 'x' ? '[value[0]*s,value[1]]' : axis === 'y' ? '[value[0],value[1]*s]' : '[value[0]*s,value[1]*s]'));
}
// slide in from an offset (px) with an expo ease, follows the cut's exit with a fade
function jzSlideIn(ctx, L, dx, dy, delay, len) {
    jzSetExpr(jzXf(L, 'ADBE Position'), jzTH(ctx) + 'var e=oe((time-' + jzN(delay || 0) + ')/' + jzN(len || 0.4) + ');[value[0]+' + jzN(dx || 0) + '*(1-e),value[1]+' + jzN(dy || 0) + '*(1-e)]');
    jzFade(ctx, L, delay, (len || 0.4) * 0.5);
}
// ---- one-shape layers (position = centre); o: { round, stroke, strokeW, rot, opacity, fillOpacity }
function jzRectLayer(ctx, name, cx, cy, w, h, fill, o) {
    o = o || {};
    var S = jzShapeLayer(ctx, name, cx, cy), g = jzGrp(S, name);
    jzAddRect(g, w, h, o.round || 0);
    if (o.stroke) jzAddStroke(g, o.stroke, o.strokeW || 2);
    if (fill) jzAddFill(g, fill, o.fillOpacity);
    if (o.rot) jzXf(S, 'ADBE Rotate Z').setValue(o.rot);
    if (o.opacity != null) jzXf(S, 'ADBE Opacity').setValue(o.opacity * 100);
    return S;
}
function jzEllipseLayer(ctx, name, cx, cy, w, h, fill, o) {
    o = o || {};
    var S = jzShapeLayer(ctx, name, cx, cy), g = jzGrp(S, name);
    jzAddEllipse(g, w, h);
    if (o.stroke) jzAddStroke(g, o.stroke, o.strokeW || 2);
    if (fill) jzAddFill(g, fill, o.fillOpacity);
    if (o.opacity != null) jzXf(S, 'ADBE Opacity').setValue(o.opacity * 100);
    return S;
}
// polyline / polygon in comp px (the layer sits at 0,0); o: { closed, fill, width, cap, trim: 'expr' }
function jzPathLayer(ctx, name, pts, color, o) {
    o = o || {};
    var S = jzShapeLayer(ctx, name, 0, 0), g = jzGrp(S, name);
    jzAddPath(g, pts, !!o.closed);
    if (o.fill) jzAddFill(g, o.fill);
    if (color) { var st = jzAddStroke(g, color, o.width || 2); if (o.cap) try { st.property('ADBE Vector Stroke Line Cap').setValue(o.cap); } catch (e) {} }
    if (o.trim) jzAddTrimPaths(g, o.trim);
    return S;
}
// ---- per-glyph arrangement inside ONE text layer (keeps the lyric editable as a single text):
// values are given per glyph in textIndex order (1-based in expressions; line breaks are not glyphs, spaces are).
function jzArrExpr(a) { var s = []; for (var i = 0; i < a.length; i++) s.push(a[i] instanceof Array ? '[' + jzN(a[i][0]) + ',' + jzN(a[i][1]) + ']' : jzN(a[i])); return '[' + s.join(',') + ']'; }
function jzCharOffsets(L, offs, name) {       // offs: [[dx, dy], ...] px
    var K = 1, i; for (i = 0; i < offs.length; i++) K = Math.max(K, Math.abs(offs[i][0]), Math.abs(offs[i][1]));
    return jzAnimator(L, name || 'JZ Place', [['ADBE Text Position 3D', [K, K, 0]]], 'var a=' + jzArrExpr(offs) + ';var q=a[textIndex-1]||[0,0];[q[0]/' + jzN(K) + '*100,q[1]/' + jzN(K) + '*100,0]');
}
function jzCharRotations(L, rots, name) {    // degrees per glyph
    var K = 1, i; for (i = 0; i < rots.length; i++) K = Math.max(K, Math.abs(rots[i]));
    return jzAnimator(L, name || 'JZ Tilt', [['ADBE Text Rotation', K]], 'var a=' + jzArrExpr(rots) + ';var q=a[textIndex-1]||0;q/' + jzN(K) + '*100');
}
function jzCharScales(L, scales, name) {     // factor per glyph (1 = 100%); range 0..3
    return jzAnimator(L, name || 'JZ Size', [['ADBE Text Scale 3D', [300, 300, 100]]], 'var a=' + jzArrExpr(scales) + ';var q=a[textIndex-1];q=q==null?1:q;(q-1)/2*100');
}
function jzCharColors(L, hex, flags, name) { // flags: [true/false per glyph] -> those glyphs take the colour
    var f = []; for (var i = 0; i < flags.length; i++) f.push(flags[i] ? 1 : 0);
    return jzAnimator(L, name || 'JZ Colour', [['ADBE Text Fill Color', jzHex(hex)]], 'var a=' + jzArrExpr(f) + ';(a[textIndex-1]||0)*100');
}
// glyphs of a string the way AE counts them for textIndex (line breaks removed)
function jzGlyphs(str) { return jzChars(String(str).replace(/[\r\n]/g, '')); }

// ---- equivalents of the small helpers the browser packs share (src/11p_*.js)
function jzFontsOf(st, roles) { var o = [], i, j; for (i = 0; i < roles.length; i++) { var r = st.fonts[roles[i]] || []; for (j = 0; j < r.length; j++) o.push(r[j]); } return o.length ? o : st.fonts.display; }
function jzSmallSize(ctx) { return jzClamp(jzU(ctx) * 0.024, 14 * ctx.u, 34 * ctx.u); }
function jzMonoF(ctx) { return (ctx.st.fonts.mono && ctx.st.fonts.mono[0]) || 'mono'; }
function jzBodyF(ctx) { return (ctx.st.fonts.body && ctx.st.fonts.body[0]) || 'gothic_med'; }
function jzSerifF(ctx) { return (ctx.st.fonts.serif && ctx.st.fonts.serif[0]) || 'mincho'; }
function jzStrip(t) { return String(t || '').replace(/[\s\u3000]+/g, ''); }
function jzHasLatin(t) { return /[A-Za-z]/.test(t); }
function jzFlat(t) { return jzHasLatin(t) ? jzTrim(String(t || '')).replace(/\s+/g, ' ') : jzStrip(t); }
function jzFmtTime(t) { t = Math.max(0, t); var m = Math.floor(t / 60), s = Math.floor(t % 60), f = Math.floor((t % 1) * 100); return jzPad(m, 2) + ':' + jzPad(s, 2) + '.' + jzPad(f, 2); }
function jzRomajiOf(ctx) { var t = ctx.cut.text; if (jzHasLatin(t) || !t) return null; var r = jzRomaji(jzStrip(t)); return r ? r.toUpperCase() : null; }
function jzLineNo(ctx) { return jzPad(Math.max(0, ctx.cut.line || 0) + 1, 2); }
// secondary copy: the full line if this cut is a part of it, else the note, else romaji, else "No.xx"
function jzAltCopy(ctx) { var c = ctx.cut; if (c.lineText && jzStrip(c.lineText) !== jzStrip(c.text)) return c.lineText; return c.note || jzRomajiOf(ctx) || 'No.' + jzLineNo(ctx); }
// text colour that reads on a plate colour
function jzOnCol(sc, plate) { var c = [sc.fg, sc.bg, sc.ink, '#FFFFFF', '#111111'], b = c[0], bs = 0; for (var i = 0; i < c.length; i++) { if (!c[i]) continue; var k = jzContrast(c[i], plate); if (k > bs) { bs = k; b = c[i]; } } return b; }
// change a text layer's TextDocument in place: jzTextDoc(L, function (td) { td.applyStroke = true; ... })
function jzTextDoc(L, fn) {
    var src = L.property('ADBE Text Properties').property('ADBE Text Document'), td = src.value;
    fn(td); src.setValue(td); return L;
}
function jzTextColor(L) { try { var c = L.property('ADBE Text Properties').property('ADBE Text Document').value.fillColor; return jzToHex(c[0] * 255, c[1] * 255, c[2] * 255); } catch (e) { return '#FFFFFF'; } }
// rectangular layer mask in layer coordinates (for text layers use the source rect: jzRect(L))
function jzMaskRect(L, x0, y0, x1, y1, feather) {
    var m = L.property('ADBE Mask Parade').addProperty('ADBE Mask Atom'), sh = new Shape();
    sh.vertices = [[x1, y0], [x1, y1], [x0, y1], [x0, y0]]; sh.closed = true;
    m.property('ADBE Mask Shape').setValue(sh);
    if (feather) m.property('ADBE Mask Feather').setValue([feather, feather]);
    return m;
}

// keep a layer out of the tinted ghost copies (the browser's ghost:false \u2014 drawn on the main pass only).
// jzBuild then feeds the ghosts from a duplicate of the content comp in which these layers are switched off
// (they still work as parents / track mattes / expression targets there).
function jzNoGhost(L) { try { var c = String(L.comment || ''); if (c.indexOf('JZ_NOGHOST') < 0) L.comment = (c ? c + ' ' : '') + 'JZ_NOGHOST'; } catch (e) {} return L; }
function jzIsNoGhost(L) { try { return String(L.comment || '').indexOf('JZ_NOGHOST') >= 0; } catch (e) { return false; } }

// ================================================================ planner (AE standalone mode)
// Same decisions as the browser app: chunks -> cuts -> weighted recipe picks -> events.
function jzW(obj, k, d) { return (obj && obj[k] != null) ? obj[k] : d; }
function jzNovelty(hist, key, val) {
    var w = 1;
    for (var i = hist.length - 1, d = 0; i >= 0 && d < 6; i--, d++) if (hist[i][key] === val) w *= d < 2 ? 0.2 : 0.6;
    return w;
}
function jzParams(layout, rng, st, text) {
    var n = jzCount(text), D = st.fonts.display, S = st.fonts.serif, B = st.fonts.body;
    function both(a, b) { return a.concat(b); }
    switch (layout) {
        case 'center': return { font: rng.pick(rng.chance(0.7) ? D : S), sx: rng.pick([1, 1, 1, 1.25, 1.45, 0.78]), track: rng.range(0.02, 0.14), sub: rng.chance(0.45), under: rng.chance(0.3), accent: rng.chance(0.18), ox: rng.range(-0.05, 0.05), oy: rng.range(-0.06, 0.06) };
        case 'mixed': return { fontBig: rng.pick(both(D, S)), fontSmall: rng.pick(both(S, B)), mode: rng.pick(['line', 'stair', 'line', 'wave']), rotAmp: rng.range(2, 10), smallK: rng.range(0.42, 0.6), accentIdx: rng.int(0, 20) };
        case 'vcols': return { variant: n <= 5 ? rng.pick(['repeat', 'repeat', 'split']) : (n <= 9 ? rng.pick(['split', 'repeat']) : 'split'), cols: n <= 4 ? rng.pick([3, 5, 5]) : 3, font: rng.pick(both(S, D)), side: rng.pick(['same', 'outline', 'dim']), perCol: rng.int(3, 6) };
        case 'marquee': return { rows: rng.pick([2, 4, 4, 2]), rowStyle: rng.pick(['outline', 'dim', 'box']), speed: rng.range(0.5, 1.2), font: rng.pick(D), sx: rng.pick([1.25, 1.45, 1.6]) };
        case 'tile': return { unit: rng.pick(['chunk', 'line', 'chunk']), knock: rng.pick(['stroke', 'box']), font: rng.pick(D), tileFont: rng.pick(both(S, B)), rowsN: rng.pick([12, 14, 16]) };
        case 'scatter': return { font: rng.pick(D), fontB: rng.pick(both(S, D)), extras: rng.chance(0.65) };
        case 'ring': return { center: rng.pick(['word', 'disc', 'word', 'none']), speed: rng.range(4, 12) * rng.pick([1, -1]), R: rng.range(0.28, 0.35), font: rng.pick(both(D, S)), fontC: rng.pick(both(D, S)) };
        case 'wave': return { amp: rng.range(0.08, 0.17), freq: rng.range(0.8, 1.6), trail: rng.pick([5, 7, 9]), font: rng.pick(D), travel: rng.range(0.25, 0.5) * rng.pick([1, -1]) };
        case 'huge': return { font: rng.pick(D), grad: !!st.useGrad && rng.chance(0.75), dir: rng.pick([1, -1]), label: rng.chance(0.8) };
        case 'labels': return { variant: rng.pick(['radial', 'rows', 'scatter']), unit: n <= 6 ? 'char' : rng.pick(['char', 'word']), center: rng.pick(['orb', 'word', 'none']), font: rng.pick(both(D, B)), fontC: rng.pick(D) };
        case 'condensed': return { count: n <= 4 ? rng.pick([3, 2, 1]) : (n <= 7 ? rng.pick([2, 1]) : 1), sx: rng.range(0.42, 0.58), sy: rng.range(1.1, 1.3), font: rng.pick(both(D, B)) };
        case 'gloss': return { font: rng.pick(both(S, D)), side: rng.pick(['right', 'left']), bgText: rng.chance(0.6), vertNote: rng.chance(0.45) };
        case 'type': return { font: rng.pick(both(B, S)), align: rng.pick(['left', 'center']), prompt: rng.chance(0.6) };
        case 'diag': return { ang: rng.range(10, 22) * rng.pick([1, -1]), band: rng.pick(['accent', 'ink']), second: rng.chance(0.7), font: rng.pick(D) };
        case 'circle': return { variant: rng.pick(['disc', 'eclipse', 'ring']), vertical: n <= 4 && rng.chance(0.5), font: rng.pick(both(D, S)), off: rng.range(-0.12, 0.12) };
        case 'stack': return { copies: rng.pick([3, 4, 5]), dir: rng.pick([1, -1]), style: rng.pick(['fade', 'outline', 'fade']), font: rng.pick(both(D, S)), gap: rng.range(0.82, 1.02), xs: rng.range(-0.04, 0.04) };
        case 'pill': return { grad: !!st.useGrad || rng.chance(0.35), font: rng.pick(both(D, B)), smalls: rng.chance(0.75) };
    }
    return {};
}
function jzPartition(chunks, k) {
    var lens = [], tot = 0, i;
    for (i = 0; i < chunks.length; i++) { lens.push(jzChars(chunks[i]).length + 1); tot += lens[i]; }
    var target = tot / k, groups = [], cur = [], acc = 0, remG = k;
    for (i = 0; i < chunks.length; i++) {
        var remC = chunks.length - i;
        if (cur.length && (acc + lens[i] / 2 > target || remC < remG) && groups.length < k - 1) { groups.push(cur); cur = []; acc = 0; remG--; }
        cur.push(chunks[i]); acc += lens[i];
    }
    if (cur.length) groups.push(cur);
    return groups;
}

// ---------------------------------------------------------------- picks (ES3 port of the browser planner; weights come from JZ_DATA.meta)
var JZ_PORTRAIT_W = { vcols: 1.9, condensed: 1.3, huge: 1.3, center: 1.2, stack: 1.1, mixed: 0.7, marquee: 0.6, wave: 0.6, diag: 0.8, type: 0.8, gloss: 0.5 };
var JZ_LAYOUT_ENTER = { type: { type: 4, scramble: 1.5 }, ring: { pop: 2, spin: 2, cut: 1, assemble: 0.4, slice: 0.2, wipe: 0.2 }, labels: { cut: 3, pop: 1 }, wave: { pop: 1.5, drop: 1.5, blur: 1, slice: 0.3 }, tile: { assemble: 1.3, slice: 1.4, zoom: 1.4 }, huge: { zoom: 1.5, wipe: 1.5, slice: 1.4, stretch: 1.3, type: 0.2 }, mixed: { pop: 1.6, drop: 1.6, spin: 1.3 }, scatter: { pop: 1.5, spin: 1.5, drop: 1.2, assemble: 1.3 }, vcols: { assemble: 1.8, type: 1.2 }, pill: { wipe: 1.8, type: 1.2 } };
var JZ_HOLD_W = { still: 1, jitter: 1.2, drift: 1, breathe: 0.7, wave: 0.4, glitchtick: 0.9 };
function jzInList(k, list) { return jzIndexOf(list, k) >= 0; }
function jzBias(st, g) { return st.bias && st.bias[g] ? st.bias[g] : null; }
function jzHasTag(m, t) { return m.tags ? jzIndexOf(m.tags, t) >= 0 : false; }
function jzPickLayout(rng, st, en, n, dur, hist, emph, recap, portrait) {
    var c = [], order = jzOrder('layout');
    for (var i = 0; i < order.length; i++) {
        var k = order[i], m = jzMeta('layout', k);
        if (!en.layout[k] || m.special || JZ_REG.layout[k].special || !jzFitsN(k, n)) continue;
        var w = jzW(jzBias(st, 'layout'), k, m.w != null ? m.w : 1) * jzNovelty(hist, 'layout', k);
        if (portrait) w *= m.portrait != null ? m.portrait : jzW(JZ_PORTRAIT_W, k, 1);
        if (emph && m.emph) w *= m.emph;
        if (emph && jzInList(k, ['huge', 'center', 'tile', 'marquee', 'condensed'])) w *= 2;
        if (recap && jzInList(k, ['center', 'stack', 'marquee', 'tile', 'mixed', 'type', 'gloss'])) w *= 1.8;
        if (dur < 0.5 && jzInList(k, ['wave', 'ring', 'labels', 'gloss', 'type', 'tile'])) w *= 0.3;
        if (dur < 0.5 && jzInList(k, ['center', 'huge', 'condensed', 'vcols'])) w *= 1.4;
        c.push([k, w]);
    }
    return c.length ? rng.wpick(c) : 'center';
}
function jzPickEnter(rng, st, en, layout, dur, hist, emph, n) {
    var c = [], order = jzOrder('enter'), LM = jzMeta('layout', layout);
    for (var i = 0; i < order.length; i++) {
        var k = order[i], m = jzMeta('enter', k); if (!en.enter[k]) continue;
        var w = jzW(jzBias(st, 'enter'), k, m.w != null ? m.w : 1) * jzNovelty(hist, 'enter', k) * jzW(JZ_LAYOUT_ENTER[layout] || LM.enterBias, k, 1);
        if (m.minDur && dur < m.minDur) w *= 0.15;
        if (m.maxChars && n > m.maxChars) w *= 0.2;
        if (k === 'cut') w *= 0.5;
        if (dur < 0.45 && jzInList(k, ['type', 'assemble', 'drop', 'spin', 'pop', 'flicker'])) w *= 0.25;
        if (dur < 0.45 && jzInList(k, ['cut', 'slice', 'zoom', 'stretch'])) w *= 1.8;
        if (k === 'type' && n > 18) w *= 0.3;
        if (emph && jzInList(k, ['zoom', 'assemble', 'slice'])) w *= 1.8;
        c.push([k, w]);
    }
    return c.length ? rng.wpick(c) : 'cut';
}
function jzPickExit(rng, st, en, layout, dur, last, hist) {
    var c = [], order = jzOrder('exit');
    for (var i = 0; i < order.length; i++) {
        var k = order[i], m = jzMeta('exit', k); if (!en.exit[k]) continue;
        var w = jzW(jzBias(st, 'exit'), k, m.w != null ? m.w : 1) * jzNovelty(hist, 'exit', k);
        if (m.minDur && dur < m.minDur) w *= 0.15;
        if (k === 'cut') w *= dur < 0.6 ? 4 : (last ? 1.2 : 2.2);
        if (dur < 0.6 && k !== 'cut') w *= 0.4;
        if (jzInList(layout, ['labels', 'ring', 'tile']) && jzInList(k, ['explode', 'fall', 'drift'])) w *= 0.3;
        c.push([k, w]);
    }
    return c.length ? rng.wpick(c) : 'cut';
}
function jzPickHold(rng, en, fx, hist) {
    var c = [], order = jzOrder('hold');
    for (var i = 0; i < order.length; i++) {
        var k = order[i], m = jzMeta('hold', k); if (!en.hold[k]) continue;
        var w = JZ_HOLD_W[k] != null ? JZ_HOLD_W[k] : (m.w != null ? m.w : 0.8);
        if (k === 'jitter' || jzHasTag(m, 'glitch')) w *= 0.4 + fx.motion;
        if (k === 'glitchtick') w *= fx.glitch;
        c.push([k, w * jzNovelty(hist, 'hold', k)]);
    }
    return c.length ? rng.wpick(c) : 'still';
}
function jzDecorParams(rng, k) {
    return { id: k, seed: rng.int(1, 999999999), n: rng.int(1, 3) + (k === 'shapes' ? 3 : 0) + (k === 'sparks' ? 4 : 0), right: rng.chance(0.5), low: rng.chance(0.5), accent: rng.chance(0.4), corner: rng.chance(0.5), big: rng.chance(0.4), mode: rng.pick(['count', 'index']), from: rng.int(0, 20), to: rng.int(30, 999), v: rng.int(0, 5), r: rng.next() };
}
function jzPickDecor(rng, st, en, fx, layout, hist) {
    var count = Math.round(fx.decor * 2.8 * rng.range(0.45, 1.15)), c = [], out = [], order = jzOrder('decor'), LM = jzMeta('layout', layout), recent = {}, i, j;
    for (i = Math.max(0, hist.length - 2); i < hist.length; i++) for (j = 0; j < (hist[i].decor || []).length; j++) recent[hist[i].decor[j]] = 1;
    for (i = 0; i < order.length; i++) {
        var k = order[i], m = jzMeta('decor', k);
        if (!en.decor[k] || (LM.busy && m.layer === 'back' && !m.subtle)) continue;
        c.push([k, jzW(st.decor, k, m.w != null ? m.w * 0.5 : 0.35) * (recent[k] ? 0.35 : 1)]);
    }
    for (i = 0; i < count && c.length; i++) {
        var pk = rng.wpick(c);
        for (j = 0; j < c.length; j++) if (c[j][0] === pk) { c.splice(j, 1); break; }
        out.push(jzDecorParams(rng, pk));
    }
    return out;
}
function jzPickTreat(rng, st, en, fx, layout, emph, hist) {
    var LM = jzMeta('layout', layout);
    if (LM.treat === false) return 'none';
    if (!rng.chance(0.18 + 0.42 * fx.decor + (emph ? 0.15 : 0))) return 'none';
    var c = [], order = jzOrder('treat');
    for (var i = 0; i < order.length; i++) {
        var k = order[i], m = jzMeta('treat', k);
        if (k === 'none' || !en.treat[k] || (LM.treat === 'safe' && !m.safe)) continue;
        c.push([k, jzW(jzBias(st, 'treat'), k, m.w != null ? m.w : 1) * jzNovelty(hist, 'treat', k)]);
    }
    return c.length ? rng.wpick(c) : 'none';
}
// side bands (same as the browser's J.sideZones): wide frames left / right, tall frames top / bottom
function jzSideZones(W, H, dir) {
    if (H > W * 1.1 && dir === 'lr') { var w0 = Math.round(W * 0.34); return [{ x: 0, y: 0, w: w0, h: H, side: 'left' }, { x: W - w0, y: 0, w: w0, h: H, side: 'right' }]; }
    if (H > W * 1.1) { var h = Math.round(H * 0.33); return [{ x: 0, y: 0, w: W, h: h, side: 'top' }, { x: 0, y: H - h, w: W, h: h, side: 'bottom' }]; }
    var w = Math.round(W * (W / H > 2 ? 0.3 : 0.36));
    return [{ x: 0, y: 0, w: w, h: H, side: 'left' }, { x: W - w, y: 0, w: w, h: H, side: 'right' }];
}
function jzZoneOf(zones, li) { var z = zones[Math.max(0, li || 0) % 2]; return { x: z.x, y: z.y, w: z.w, h: z.h, side: z.side }; }
// \u4E2D\u592E\u3092\u7A7A\u3051\u308B: the lyric split in two (same as the browser's splitCut): first half left / top, second half right / bottom,
// same layout, motion, decorations and camera
function jzSplitHalf(t, lang) {
    t = jzTrim(String(t || ''));
    var n = jzChars(t.replace(/\s+/g, '')).length, k;
    var words = lang === 'en' ? jzPhraseChunks(jzChunk(t)) : jzChunk(t);
    if (words.length >= 2) {
        var total = 0, acc = 0, best = 1, bd = 1e9, sep = /[A-Za-z]/.test(t) ? ' ' : '';
        for (k = 0; k < words.length; k++) total += jzChars(String(words[k]).replace(/\s+/g, '')).length;
        for (k = 1; k < words.length; k++) { acc += jzChars(String(words[k - 1]).replace(/\s+/g, '')).length; var d = Math.abs(acc - total / 2); if (d < bd) { bd = d; best = k; } }
        return [jzTrim(words.slice(0, best).join(sep)), jzTrim(words.slice(best).join(sep))];
    }
    if (n <= 3 || /^[A-Za-z0-9'\u2019-]+$/.test(t)) return [t, t];
    var all = jzChars(t), cut = Math.ceil(all.length / 2);
    // a half never starts with a particle, punctuation or a small kana
    for (var g = 0; g < 3 && cut < all.length - 1 && /[\u3001\u3002\uFF0C\uFF0E,.!?\uFF01\uFF1F\u2026\u30FB\u30FC\u3063\u30C3\u3083\u3085\u3087\u30E3\u30E5\u30E7\u3041\u3043\u3045\u3047\u3049\u30A1\u30A3\u30A5\u30A7\u30A9\u3092\u304C\u306F\u306B\u3067\u3068\u306E\u3078\u3082\u3084\u3088\u306D\u300D\u300F\uFF09)]/.test(all[cut]); g++) cut++;
    return [all.slice(0, cut).join(''), all.slice(cut).join('')];
}
function jzSplitCut(cut, zones, st, dur, lang) {
    var hv = jzSplitHalf(cut.text, lang), seed = jzHash(cut.seed, 23);
    function planFor(text, z) { return jzPlanOf('layout', cut.layout, new JzRng(seed), st, { text: text, n: jzChars(text.replace(/\s+/g, '')).length, W: z.w, H: z.h, dur: dur }); }
    cut.text = hv[0]; cut.lineText = hv[0]; cut.words = jzChunk(hv[0]); cut.zone = jzZoneOf(zones, 0); cut.params = planFor(hv[0], zones[0]);
    var tw = {}, k2;
    for (k2 in cut) if (cut.hasOwnProperty(k2)) tw[k2] = cut[k2];
    var delay = Math.min(0.12, dur * 0.08);
    tw.text = hv[1]; tw.lineText = hv[1]; tw.words = jzChunk(hv[1]); tw.zone = jzZoneOf(zones, 1); tw.params = planFor(hv[1], zones[1]);
    tw.start = cut.start + delay; tw.dur = cut.end - tw.start; tw.bg = 'none'; tw.bgP = {}; tw.trans = null; tw.transP = {}; tw.transDur = 0; tw.companion = true;
    cut.companion = tw;
}
function jzPickBg(rng, st, en, fx, bgHist) {
    if (!rng.chance(0.2 + 0.35 * fx.decor + 0.2 * fx.bgSwitch)) return 'none';
    var c = [], order = jzOrder('bg'), last = bgHist.slice(Math.max(0, bgHist.length - 3));
    for (var i = 0; i < order.length; i++) {
        var k = order[i], m = jzMeta('bg', k);
        if (k === 'none' || !en.bg[k]) continue;
        c.push([k, jzW(jzBias(st, 'bg'), k, m.w != null ? m.w : 1) * (jzInList(k, last) ? 0.25 : 1)]);
    }
    return c.length ? rng.wpick(c) : 'none';
}
function jzPickCam(rng, st, en, fx, layout, emph, hist) {
    var c = [], order = jzOrder('cam'), LM = jzMeta('layout', layout);
    for (var i = 0; i < order.length; i++) {
        var k = order[i], m = jzMeta('cam', k); if (!en.cam[k]) continue;
        var w = jzW(jzBias(st, 'cam'), k, m.w != null ? m.w : 1) * jzNovelty(hist, 'cam', k);
        if (m.strong) w *= 0.25 + 0.9 * fx.motion + (emph ? 0.6 : 0);
        if (LM.cam === false && k !== 'push') w *= 0.05;
        c.push([k, w]);
    }
    return c.length ? rng.wpick(c) : 'push';
}
function jzPickTrans(rng, st, en, fx, emph, hist) {
    var order = jzOrder('trans');
    if (!order.length) return null;
    if (!rng.chance(0.1 + 0.22 * fx.motion + (emph ? 0.08 : 0))) return null;
    var c = [];
    for (var i = 0; i < order.length; i++) { var k = order[i], m = jzMeta('trans', k); if (!en.trans[k]) continue; c.push([k, jzW(jzBias(st, 'trans'), k, m.w != null ? m.w : 1) * jzNovelty(hist, 'trans', k)]); }
    return c.length ? rng.wpick(c) : null;
}
function jzPickFx(rng, st, en, fx, emph, fxHist, kind) {
    var g = fx.glitch, p = kind === 'edge' ? 0.12 + 0.38 * g + 0.12 * fx.motion + (emph ? 0.15 : 0) : 0.05 + 0.2 * g;
    if (!rng.chance(p)) return null;
    var last = fxHist.slice(Math.max(0, fxHist.length - 3)), c = [], order = jzOrder('fx');
    for (var i = 0; i < order.length; i++) {
        var k = order[i], m = jzMeta('fx', k);
        if (m.builtin || JZ_REG.fx[k].builtin || !en.fx[k]) continue;
        if (kind === 'edge' ? m.edge === false : !m.mid) continue;
        var w = jzW(jzBias(st, 'fx'), k, m.w != null ? m.w : 1) * (jzInList(k, last) ? 0.2 : 1);
        if (m.glitchy) w *= 0.3 + g * 1.4;
        c.push([k, w]);
    }
    return c.length ? rng.wpick(c) : null;
}
function jzPlanOf(g, k, rng, st, extra) { var D = JZ_REG[g][k]; if (!D || !D.plan) return {}; try { return (g === 'layout' ? D.plan(rng, extra || {}, st) : D.plan(rng, st)) || {}; } catch (e) { jzWarn(g + ' ' + k + ' plan: ' + e.toString()); return {}; } }

// ---- lyric language (same rule as the browser, src/02b_lang.js): Latin only \u2192 en, kana \u2192 ja, hangul \u2192 ko, Han only \u2192 Traditional / Simplified
var JZ_TC = '\u5011\u500B\u8AAA\u9019\u6703\u5C0D\u6642\u4F86\u9084\u5F8C\u904E\u570B\u958B\u95DC\u8207\u70BA\u5F9E\u554F\u9593\u898B\u9577\u6771\u8ECA\u9580\u611B\u807D\u5B78\u8B93\u8A71\u865F\u767C\u9EDE\u7121\u73FE\u9AD4\u7D93\u96FB\u5BE6\u6A23\u8072\u8B8A\u96E2\u6C23\u5922\u7D66\u89BA\u7576\u6B61\u967D\u6200\u908A\u982D\u6DDA\u8AB0\u6B72\u9060\u55CE\u842C\u96E3\u5BEB\u61C9\u8B80\u61B6\u6A02\u9EBC\u9E97\u50B7\u5C07\u7E3D\u7D50\u7D42\u7D05\u7DA0\u7DDA\u984F\u98A8\u98DB\u9CE5\u8B1D\u8A9E\u8ACB\u8A8D\u8B58\u71B1\u71C8\u9858\u7368\u5920\u7D00\u5E36\u6EFF\u975C\u8F15\u5225\u8166\u81C9\u61F7\u8B0A\u932F\u9846\u9663\u5834\u8B9A\u6DFA\u6EAB\u8A18\u6191\u8B77\u58DE\u6B78\u5ABD\u96A8\u9280\u805E\u614B\u865B\u9059';
var JZ_SC = '\u4EEC\u4E2A\u8BF4\u8FD9\u4F1A\u5BF9\u65F6\u6765\u8FD8\u540E\u8FC7\u56FD\u5F00\u5173\u4E0E\u4E3A\u4ECE\u95EE\u95F4\u89C1\u957F\u4E1C\u8F66\u95E8\u7231\u542C\u5B66\u8BA9\u8BDD\u53F7\u53D1\u70B9\u65E0\u73B0\u4F53\u7ECF\u7535\u5B9E\u6837\u58F0\u53D8\u79BB\u6C14\u68A6\u7ED9\u89C9\u5F53\u6B22\u9633\u604B\u8FB9\u5934\u6CEA\u8C01\u5C81\u8FDC\u5417\u4E07\u96BE\u5199\u5E94\u8BFB\u5FC6\u4E50\u4E48\u4E3D\u4F24\u5C06\u603B\u7ED3\u7EC8\u7EA2\u7EFF\u7EBF\u989C\u98CE\u98DE\u9E1F\u8C22\u8BED\u8BF7\u8BA4\u8BC6\u70ED\u706F\u613F\u72EC\u591F\u7EAA\u5E26\u6EE1\u9759\u8F7B\u522B\u8111\u8138\u6000\u8C0E\u9519\u9897\u9635\u573A\u8D5E\u6D45\u6E29\u8BB0\u51ED\u62A4\u574F\u5F52\u5988\u968F\u94F6\u95FB\u6001\u865A\u9065';
function jzDetectLangText(text) {
    var kana = 0, hangul = 0, han = 0, tc = 0, sc = 0, latin = 0, i, u, c;
    text = String(text || '');
    for (i = 0; i < text.length; i++) {
        u = text.charCodeAt(i); c = text.charAt(i);
        if ((u >= 0x41 && u <= 0x5a) || (u >= 0x61 && u <= 0x7a) || (u >= 0xc0 && u <= 0x24f && u !== 0xd7 && u !== 0xf7) || (u >= 0xff21 && u <= 0xff3a) || (u >= 0xff41 && u <= 0xff5a)) latin++;
        else if ((u >= 0x3041 && u <= 0x30ff && u !== 0x30fb && u !== 0x30fc) || (u >= 0xff66 && u <= 0xff9d)) kana++;
        else if ((u >= 0xac00 && u <= 0xd7a3) || (u >= 0x1100 && u <= 0x11ff) || (u >= 0x3130 && u <= 0x318f)) hangul++;
        else if ((u >= 0x4e00 && u <= 0x9fff) || (u >= 0x3400 && u <= 0x4dbf)) { han++; if (JZ_TC.indexOf(c) >= 0) tc++; if (JZ_SC.indexOf(c) >= 0) sc++; }
    }
    if (latin >= 6 && latin >= (latin + (kana + hangul + han) * 3) * 0.9) return 'en';   // almost only Latin letters (English / romaji)
    if (hangul >= 2 && hangul > kana) return 'ko';
    if (kana >= 2 || (kana > 0 && kana >= han * 0.03)) return 'ja';
    if (han >= 2 && (tc || sc)) return tc >= sc ? 'zh-Hant' : 'zh-Hans';
    return 'ja';
}
// English lyrics: cut by short phrases (2\u20133 words), not word by word \u2014 same as the browser (J.phraseChunks)
function jzPhraseChunks(words) {
    var out = [], cur = [], letters = 0, i, w, m;
    function flush() { if (cur.length) out.push(cur.join(' ')); cur = []; letters = 0; }
    for (i = 0; i < words.length; i++) {
        w = words[i]; m = String(w).match(/[A-Za-z\u00c0-\u024f0-9]/g);
        cur.push(w); letters += m ? m.length : 0;
        if (letters >= 9 || cur.length >= 3 || /[,.;:!?]$/.test(w)) flush();
    }
    flush();
    if (out.length >= 2 && out[out.length - 1].replace(/[^A-Za-z]/g, '').length <= 4) { var last = out.pop(); out[out.length - 1] += ' ' + last; }
    return out.length ? out : words;
}
// a plan without .lang (older JSON): detect from its lines
function jzDetectLang(plan) {
    var t = [], i;
    for (i = 0; plan && plan.lines && i < plan.lines.length; i++) t.push(plan.lines[i].text);
    if (!t.length) for (i = 0; plan && plan.cuts && i < plan.cuts.length; i++) t.push(plan.cuts[i].text);
    return jzDetectLangText(t.join(' '));
}
// o: {lyrics, title, artist, style, seed, fx, width, height, fps, bpm, starts[], enabled{group:{key:false}}, offset, lineScale, duration, extra, wa, lang}
function jzMakePlan(o) {
    var st = JZ_DATA.styles[o.style] || JZ_DATA.styles.noir;
    var fx = o.fx, parsed = jzParseLyrics(o.lyrics), lines = parsed.lines;
    if (fx.decor == null) fx.decor = 0.5; if (fx.bgSwitch == null) fx.bgSwitch = 0.35;
    var title = o.title || parsed.meta.ti || '', artist = o.artist || parsed.meta.ar || '';
    var beat = o.bpm > 0 ? 60 / o.bpm : 0, starts = [], ends = [], i, allLrc = lines.length > 0, g0, k0;
    // enabled map: implemented + not switched off + allowed by the \u8FFD\u52A0\u5206 / \u548C\u98A8 switches
    var en = {};
    for (g0 = 0; g0 < JZ_GROUPS.length; g0++) {
        var gg = JZ_GROUPS[g0], ord = jzOrder(gg); en[gg] = {};
        for (k0 = 0; k0 < ord.length; k0++) { var kk = ord[k0]; en[gg][kk] = !(o.enabled && o.enabled[gg] && o.enabled[gg][kk] === false) && jzRandomOk(o, gg, kk); }
    }
    for (i = 0; i < lines.length; i++) if (lines[i].lrc == null) allLrc = false;
    for (i = 0; i < lines.length; i++) {
        var s;
        if (o.starts && o.starts[i] != null) s = o.starts[i];
        else if (allLrc) s = lines[i].lrc;
        else if (i === 0) s = o.offset || 0.4;
        else {
            var pl = lines[i - 1], n0 = jzChars(pl.text).length, d0 = pl.interlude ? (pl.secs > 0 ? pl.secs : 4) : jzClamp(0.8 + n0 * 0.17, 1.3, 5.2) * (o.lineScale || 1);
            if (beat && !(pl.interlude && pl.secs > 0)) d0 = Math.max(2, Math.round(d0 / beat)) * beat;
            s = starts[i - 1] + d0 + (lines[i].gapBefore ? (beat ? beat * 2 : 0.8) : 0);
        }
        starts.push(s);
    }
    for (i = 0; i < lines.length; i++) {
        if (i < lines.length - 1) ends.push(Math.max(starts[i] + 0.35, starts[i + 1]));
        else { var Li = lines[i], nl = jzChars(Li.text).length, dl = Li.interlude ? (Li.secs > 0 ? Li.secs : 4) : jzClamp(0.8 + nl * 0.17, 1.5, 5.2) * (o.lineScale || 1); if (beat && !(Li.interlude && Li.secs > 0)) dl = Math.max(2, Math.round(dl / beat)) * beat; ends.push(starts[i] + dl); }
    }
    var duration = o.duration || ((ends.length ? ends[ends.length - 1] : 3) + 0.9);
    var W = o.width, H = o.height, portrait = H > W;
    // \u4E2D\u592E\u3092\u7A7A\u3051\u308B: cuts laid out in side bands (left / right, or top / bottom on tall frames), alternating per line
    var zones = o.centerFree ? jzSideZones(W, H, o.centerDir) : null;
    if (zones && en.bg) en.bg.bigChar = false;
    var plan = { version: 2, generator: 'JIZURA-AE', title: title, artist: artist, W: W, H: H, width: W, height: H, fps: o.fps, duration: duration, style: st, styleKey: o.style, fx: fx, lines: [], cuts: [], events: [], hud: fx.hud,
        lang: (o.lang && o.lang !== 'auto') ? o.lang : jzDetectLangText(o.lyrics + ' ' + title), centerFree: !!zones, zones: zones };
    jzSetLang(plan.lang);
    var hist = [], bgHist = [], fxHist = [], schemeIdx = 0, nS = st.schemes.length;
    function ev(t, type, amp, dur) { plan.events.push({ t: t, type: type, amp: amp, dur: dur }); }
    if (title && starts.length && starts[0] >= 1.1) {
        var tr = new JzRng(jzHash(o.seed, 999));
        plan.cuts.push({ text: title, note: artist, lineText: title, line: -1, start: 0.1, end: starts[0] - 0.04, layout: 'title', enter: tr.pick(['blur', 'type', 'wipe', 'assemble']), exit: tr.pick(['blur', 'drift', 'wipe']), hold: 'still', inDur: 0.3, outDur: 0.3, params: { font: tr.pick(st.fonts.display) }, decor: [], scheme: 0, seed: jzHash(o.seed, 999, 1) % 1000000, treat: 'none', bg: 'none', cam: 'push' });
    }
    var F = 1 / 24;
    for (var li = 0; li < lines.length; li++) {
        var ln = lines[li], s0 = starts[li], e0 = ends[li], rng = new JzRng(jzHash(o.seed, li + 1));
        if (ln.interlude) {                                // [\u9593\u594F]: background, decorations and effects only
            plan.lines.push({ index: li, text: '', interlude: true, start: s0, end: e0, visEnd: e0, note: null, impact: false });
            var ibg = jzPickBg(rng, st, en, fx, bgHist); bgHist.push(ibg);
            var idur = e0 - s0, ishow = idur >= 6 && !!(title || artist);
            plan.cuts.push({ index: plan.cuts.length, text: '', lineText: '', line: li, start: s0, end: e0, layout: 'interlude', enter: 'blur', exit: 'blur', hold: 'still', inDur: 0.4, outDur: 0.4,
                params: { variant: 'quiet', showTitle: ishow, titleText: ishow ? (title + (title && artist ? '  /  ' : '') + artist) : '' }, decor: jzPickDecor(rng, st, en, { decor: Math.max(0.6, fx.decor) }, 'interlude', hist), scheme: schemeIdx, seed: jzHash(o.seed, li, 405) % 1000000,
                treat: 'none', bg: ibg, bgP: ibg !== 'none' ? jzPlanOf('bg', ibg, rng, st) : {}, cam: 'push' });
            for (var itt = s0 + 1.2; itt < e0 - 0.8; itt += jzClamp(idur / 4, 1.6, 3.2)) { var ipk = jzPickFx(rng, st, en, fx, false, fxHist, 'mid'); if (ipk) { var IM = jzMeta('fx', ipk); ev(itt, ipk, (IM.amp || 1) * 0.6, (IM.dur || 3) * F); fxHist.push(ipk); } }
            continue;
        }
        var nch = jzCount(ln.text), visEnd = Math.min(e0, s0 + Math.max(3.6, nch * 0.5 + 1.2)), D = visEnd - s0;
        plan.lines.push({ index: li, text: ln.text, start: s0, end: e0, visEnd: visEnd, note: ln.note, impact: ln.impact });
        var chunks = ln.manual || (plan.lang === 'en' ? jzPhraseChunks(jzChunk(ln.text)) : jzChunk(ln.text)), L = jzLerp(1.3, 0.5, fx.density), nC = Math.round(D / L);
        var maxC = chunks.length + (chunks.length >= 2 && D > 2 ? 1 : 0); nC = jzClamp(nC, 1, Math.max(1, maxC));
        if (zones) nC = Math.max(1, Math.min(nC, Math.floor(chunks.length / 2)));   // \u4E2D\u592E\u3092\u7A7A\u3051\u308B: \u2265 2 words per cut (each cut is split in two)
        var nG = Math.min(nC, chunks.length), groups = [];
        if (nG <= 1) groups = [ln.text];
        else { var pg = jzPartition(chunks, nG); for (i = 0; i < pg.length; i++) groups.push(pg[i].join(/[A-Za-z]/.test(pg[i].join('')) ? ' ' : '')); }
        var recap = nC > groups.length && groups.length >= 2, units = [], tot = 0;
        for (i = 0; i < groups.length; i++) { units.push({ text: groups[i], w: jzChars(groups[i]).length + 1.6 }); tot += units[i].w; }
        if (recap) { var rw = tot / units.length * 1.25; units.push({ text: ln.text, w: rw, recap: true }); tot += rw; }
        var bounds = [s0], acc = s0;
        for (i = 0; i < units.length; i++) { acc += D * units[i].w / tot; bounds.push(i === units.length - 1 ? visEnd : acc); }
        if (nS > 1 && li > 0 && rng.chance(fx.bgSwitch * (ln.impact ? 1.8 : 1))) schemeIdx = (schemeIdx + 1 + rng.int(0, nS - 2)) % nS;
        var lineBg = jzPickBg(rng, st, en, fx, bgHist); bgHist.push(lineBg);
        var lineBgP = lineBg !== 'none' ? jzPlanOf('bg', lineBg, rng, st) : {};
        for (var k = 0; k < units.length; k++) {
            var u = units[k], cs = bounds[k], ce = bounds[k + 1], dur = ce - cs, nn = jzCount(u.text);
            var emph = (ln.impact && (k === 0 || u.recap));
            for (var q = 0; q < ln.emph.length; q++) if (u.text.indexOf(ln.emph[q]) >= 0) emph = true;
            var Z = zones ? jzZoneOf(zones, li) : null, LW = Z ? Z.w : W, LH = Z ? Z.h : H;
            var layout = jzPickLayout(rng, st, en, nn, dur, hist, emph, u.recap, Z ? LH > LW : portrait);
            var enter = jzPickEnter(rng, st, en, layout, dur, hist, emph, nn);
            var exit = jzPickExit(rng, st, en, layout, dur, k === units.length - 1, hist);
            var hold = jzPickHold(rng, en, fx, hist);
            var inDur = jzClamp(dur * 0.36, 0.12, 0.6);
            if (enter === 'type') inDur = jzClamp(nn * 0.055 + 0.1, 0.15, dur * 0.65);
            if (enter === 'assemble') inDur = jzClamp(dur * 0.45, 0.22, 0.75);
            var mIn = jzTab(jzMeta('enter', enter).inDur, dur, nn); if (mIn != null) inDur = mIn;
            if (enter === 'cut') inDur = 0.12;
            var outDur = exit === 'cut' ? 0 : jzClamp(dur * 0.3, 0.14, 0.55);
            if (/^(explode|fall|drift)$/.test(exit)) outDur = jzClamp(dur * 0.38, 0.25, 0.7);
            var mOut = jzTab(jzMeta('exit', exit).outDur, dur, nn); if (mOut != null) outDur = mOut;
            if (inDur + outDur > dur * 0.92) { var f = dur * 0.92 / (inDur + outDur); inDur *= f; outDur *= f; }
            var sch = schemeIdx; if (nS > 1 && k > 0 && rng.chance(0.12 * fx.bgSwitch)) sch = (schemeIdx + 1) % nS;
            var params = jzPlanOf('layout', layout, rng, st, { text: u.text, n: nn, W: LW, H: LH, dur: dur });
            var decor = jzPickDecor(rng, st, en, fx, layout, hist);
            var treat = jzPickTreat(rng, st, en, fx, layout, emph, hist), treatP = treat !== 'none' ? jzPlanOf('treat', treat, rng, st) : {};
            if (k > 0 && rng.chance(0.18 * fx.bgSwitch + 0.04)) { lineBg = jzPickBg(rng, st, en, fx, bgHist); lineBgP = lineBg !== 'none' ? jzPlanOf('bg', lineBg, rng, st) : {}; }
            var LM = jzMeta('layout', layout), bg = LM.busy && !jzMeta('bg', lineBg).subtle ? 'none' : lineBg;
            var cam = jzPickCam(rng, st, en, fx, layout, emph, hist), camP = jzPlanOf('cam', cam, rng, st);
            var prevCut = plan.cuts[plan.cuts.length - 1], trans = null, transP = {}, transDur = 0;
            if (prevCut && Math.abs(prevCut.end - cs) < 0.06 && prevCut.layout !== 'interlude' && dur > 0.5) {
                trans = jzPickTrans(rng, st, en, fx, emph, hist);
                if (trans) {
                    transDur = jzClamp(jzMeta('trans', trans).dur || 0.35, 0.12, Math.min(0.6, dur * 0.45));
                    transP = jzPlanOf('trans', trans, rng, st);
                    enter = 'cut'; inDur = 0.12; prevCut.exit = 'cut'; prevCut.outDur = 0;
                }
            }
            var dids = []; for (q = 0; q < decor.length; q++) dids.push(decor[q].id);
            plan.cuts.push({ index: plan.cuts.length, text: u.text, lineText: ln.text, note: ln.note, line: li, start: cs, end: ce, dur: dur, layout: layout, enter: enter, exit: exit, hold: hold, inDur: inDur, outDur: outDur,
                params: params, decor: decor, scheme: sch, seed: jzHash(o.seed, li, k) % 1000000, emph: emph, recap: !!u.recap, words: jzChunk(u.text), stagger: rng.range(0.025, 0.06),
                treat: treat, treatP: treatP, bg: bg, bgP: bg === lineBg ? lineBgP : {}, cam: cam, camP: camP, trans: trans, transP: transP, transDur: transDur });
            hist.push({ layout: layout, enter: enter, exit: exit, hold: hold, treat: treat, cam: cam, trans: trans, decor: dids });
            if (zones) jzSplitCut(plan.cuts[plan.cuts.length - 1], zones, st, dur, plan.lang);
            var g = fx.glitch * (st.glitchBoost || 1);
            if (en.fx.chroma !== false) ev(cs, 'chroma', 1.4 + rng.range(0, 2) * fx.chroma + (emph ? 2.5 : 0), 0.25);
            if (en.fx.slice !== false && rng.chance(g * 0.5 + (emph ? 0.3 : 0))) ev(cs, 'slice', 0.6 + rng.range(0, 0.8) * g + (emph ? 0.5 : 0), rng.pick([2, 3, 4]) * F);
            if (en.fx.block !== false && rng.chance(g * 0.22)) ev(cs + rng.range(0, 0.05), 'block', 0.5 + g, rng.pick([2, 4]) * F);
            if (en.fx.shake !== false && (emph || rng.chance(fx.motion * 0.18))) ev(cs, 'shake', (emph ? 1 : 0.5) * fx.motion, 0.3);
            if (en.fx.flash !== false && fx.flash && ln.impact && k === 0) ev(cs, 'flash', 1, 3 * F);
            if (en.fx.invert !== false && rng.chance(0.035 * g)) ev(cs, 'invert', 1, 2 * F);
            if (en.fx.zoom !== false && ((emph && rng.chance(0.6)) || rng.chance(0.06 * fx.motion))) ev(cs, 'zoom', 0.7 + 0.5 * fx.motion, 0.22);
            if (en.fx.mosaic !== false && rng.chance(0.04 * g)) ev(cs, 'mosaic', 1, 3 * F);
            if (en.fx.slice !== false && dur > 0.8 && rng.chance(g * 0.4)) ev(cs + rng.range(0.35, 0.8) * dur, 'slice', 0.4 + g * 0.4, 2 * F);
            if (plan.cuts.length > 1 || k > 0 || li > 0) {
                var pe = jzPickFx(rng, st, en, fx, emph, fxHist, 'edge');
                if (pe) { var M1 = jzMeta('fx', pe); ev(cs - (M1.pre ? M1.pre * F : 0), pe, (M1.amp || 1) * (0.7 + 0.5 * g + (emph ? 0.3 : 0)), (M1.dur || 4) * F); fxHist.push(pe); }
            }
            if (dur > 1.1) { var pm = jzPickFx(rng, st, en, fx, emph, fxHist, 'mid'); if (pm) { var M2 = jzMeta('fx', pm); ev(cs + rng.range(0.4, 0.75) * dur, pm, (M2.amp || 1) * (0.5 + 0.4 * g), (M2.dur || 3) * F); } }
        }
        if (li < lines.length - 1 && starts[li + 1] - visEnd > 1.3 && !lines[li + 1].interlude) {
            var r2 = new JzRng(jzHash(o.seed, li, 404));
            plan.cuts.push({ index: plan.cuts.length, text: title || '', lineText: '', line: li, start: visEnd, end: starts[li + 1], layout: 'interlude', enter: 'blur', exit: 'blur', hold: 'still', inDur: 0.3, outDur: 0.3, params: { variant: r2.pick(['counter', 'rings']) }, decor: jzPickDecor(r2, st, en, { decor: 1 }, 'interlude', []), scheme: schemeIdx, seed: jzHash(o.seed, li, 405) % 1000000, treat: 'none', bg: 'none', cam: 'push' });
        }
    }
    plan.cuts.sort(function (a, b) { return a.start - b.start; });
    for (i = 0; i < plan.cuts.length; i++) {
        var pc0 = plan.cuts[i]; pc0.index = i;
        if (!zones || pc0.zone) continue;
        if (pc0.layout === 'interlude') { pc0.params.showTitle = false; continue; }     // no lyric: the whole frame
        pc0.zone = jzZoneOf(zones, 0);
    }
    plan.events.sort(function (a, b) { return a.t - b.t; });
    return plan;
}

// ================================================================ \u304A\u307E\u304B\u305B + random accent / ghost colours (ES3 port of the web engine)
function jzToHex(r, g, b) {  // 0..255 -> #RRGGBB
    function h2(v) { v = Math.round(jzClamp(v, 0, 255)); var s = v.toString(16).toUpperCase(); return s.length < 2 ? '0' + s : s; }
    return '#' + h2(r) + h2(g) + h2(b);
}
function jzHsl(h, s, l) {    // h 0..360, s/l 0..1 -> hex
    h = ((h % 360) + 360) % 360 / 360;
    var q = l < 0.5 ? l * (1 + s) : l + s - l * s, p = 2 * l - q;
    function f(t) { t = (t + 1) % 1; return t < 1 / 6 ? p + (q - p) * 6 * t : t < 0.5 ? q : t < 2 / 3 ? p + (q - p) * (2 / 3 - t) * 6 : p; }
    return jzToHex(f(h + 1 / 3) * 255, f(h) * 255, f(h - 1 / 3) * 255);
}
function jzToHsl(hex) {
    var c = jzHex(hex), r = c[0], g = c[1], b = c[2];
    var mx = Math.max(r, g, b), mn = Math.min(r, g, b), l = (mx + mn) / 2;
    if (mx === mn) return [0, 0, l];
    var d = mx - mn, s = l > 0.5 ? d / (2 - mx - mn) : d / (mx + mn);
    var h = mx === r ? (g - b) / d + (g < b ? 6 : 0) : (mx === g ? (b - r) / d + 2 : (r - g) / d + 4);
    return [h * 60, s, l];
}
function jzRelLum(hex) {
    var c = jzHex(hex), o = [], k, v;
    for (k = 0; k < 3; k++) { v = c[k]; o.push(v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4)); }
    return 0.2126 * o[0] + 0.7152 * o[1] + 0.0722 * o[2];
}
function jzContrast(a, b) { var x = jzRelLum(a), y = jzRelLum(b); return (Math.max(x, y) + 0.05) / (Math.min(x, y) + 0.05); }
// nudge lightness away from the background until the colour reads
function jzFitContrast(hex, bg, min) {
    if (jzContrast(hex, bg) >= min) return String(hex).toUpperCase();
    var hsl = jzToHsl(hex), l = hsl[2], dark = jzLum(bg) < 0.5, i, c;
    for (i = 0; i < 24; i++) {
        l = dark ? Math.min(0.96, l + 0.035) : Math.max(0.04, l - 0.035);
        c = jzHsl(hsl[0], hsl[1], l);
        if (jzContrast(c, bg) >= min) return c;
    }
    return dark ? '#FFFFFF' : '#111111';
}
function jzMixHex(h1, h2, t) { var a = jzHex(h1), b = jzHex(h2); return jzToHex((a[0] + (b[0] - a[0]) * t) * 255, (a[1] + (b[1] - a[1]) * t) * 255, (a[2] + (b[2] - a[2]) * t) * 255); }
function jzCleanHex(s) {     // '#abc123' / 'abc123' -> '#ABC123', anything else -> null
    s = jzTrim(s || '').replace(/^#/, '');
    if (/^[0-9A-Fa-f]{3}$/.test(s)) s = s.charAt(0) + s.charAt(0) + s.charAt(1) + s.charAt(1) + s.charAt(2) + s.charAt(2);
    return /^[0-9A-Fa-f]{6}$/.test(s) ? '#' + s.toUpperCase() : null;
}
// accent + chromatic ghost pair that works on the given background
function jzRandomPalette(bg, rnd) {
    rnd = rnd || Math.random;
    var dark = jzLum(bg) < 0.5, a, b, t;
    if (rnd() < 0.4) {
        var pr = JZ_DATA.ghostPairs[Math.floor(rnd() * JZ_DATA.ghostPairs.length) % JZ_DATA.ghostPairs.length];
        a = pr[0]; b = pr[1];
        if (rnd() < 0.5) { t = a; a = b; b = t; }
        if (!dark) { a = jzHsl(jzToHsl(a)[0], 0.95, 0.47); b = jzHsl(jzToHsl(b)[0], 0.95, 0.47); }
    } else {
        var h = rnd() * 360, gap = [180, 165, 150, 135][Math.floor(rnd() * 4) % 4] * (rnd() < 0.5 ? 1 : -1);
        var s = 0.82 + rnd() * 0.18, l = dark ? 0.52 + rnd() * 0.1 : 0.44 + rnd() * 0.08;
        a = jzHsl(h, s, l); b = jzHsl(h + gap, s, l);
    }
    var r = rnd(), ha = jzToHsl(a)[0], hb = jzToHsl(b)[0];
    var acc = r < 0.35 ? a : (r < 0.6 ? b : jzHsl((ha + hb) / 2 + (rnd() < 0.5 ? 0 : 180), 0.9, dark ? 0.6 : 0.45));
    return { accent: jzFitContrast(acc, bg, 3), ghostA: a, ghostB: b };
}
// copy of a style pack with the accent / ghost colours replaced in every scheme (the shared data is never mutated)
function jzStyleWithPalette(st, pal) {
    var o = jzCopy(st), out = [], i, s, acc;
    for (i = 0; i < st.schemes.length; i++) {
        s = jzCopy(st.schemes[i]);
        if (pal.accent) {
            acc = jzFitContrast(pal.accent, s.bg, 2.4);
            if (s.ink === s.accent) s.ink = acc;
            s.accent = acc;
            if (s.grad) s.grad = [acc, jzMixHex(pal.accent, '#000000', 0.7)];
        }
        if (pal.ghostA) s.ghostA = jzFitContrast(pal.ghostA, s.bg, 1.35);
        if (pal.ghostB) s.ghostB = jzFitContrast(pal.ghostB, s.bg, 1.35);
        out.push(s);
    }
    o.schemes = out;
    return o;
}
// \u5408\u6210\u7528\u306E\u80CC\u666F (green / black back): every scheme white-on-black, no textures \u2014 same as the browser's keyStyle()
function jzKeyStyle(st) {
    var o = jzCopy(st), out = [], i, s;
    for (i = 0; i < st.schemes.length; i++) {
        s = { bg: '#000000', fg: '#FFFFFF', sub: '#D2D2D2', accent: '#FFFFFF', accent2: '#BDBDBD', ink: '#FFFFFF', dim: '#1E1E1E', ghostA: '#9A9A9A', ghostB: '#5E5E5E' };
        if (st.schemes[i].grad) s.grad = ['#FFFFFF', '#A8A8A8'];
        out.push(s);
    }
    o.schemes = out; o.texture = { grain: 0, paper: 0, scan: 0 }; o.key = true;
    return o;
}
// technique on/off map for a mood \u2014 deterministic from the seed so a rebuild reproduces it
function jzAllEnabled() {
    var en = {}, g, j;
    for (g = 0; g < JZ_GROUPS.length; g++) { var ord = jzOrder(JZ_GROUPS[g]); en[JZ_GROUPS[g]] = {}; for (j = 0; j < ord.length; j++) en[JZ_GROUPS[g]][ord[j]] = true; }
    return en;
}
// technique on/off map for a mood (same idea as the browser's \u304A\u307E\u304B\u305B): everything tagged with the mood plus the
// mood's hand-picked core items, a sprinkle of everything else, and a minimum count per group. Deterministic from the seed.
// the mood a part set belongs to ('horror' \u2192 \u30DB\u30E9\u30FC), or null
function jzMoodOfSet(s) { var k; for (k in JZ_DATA.moods) if (JZ_DATA.moods.hasOwnProperty(k) && JZ_DATA.moods[k].set === s) return k; return null; }
var JZ_MOOD_MIN = { layout: 6, enter: 5, exit: 5, hold: 3, decor: 6, treat: 4, bg: 4, cam: 3, fx: 4, trans: 3 };
function jzMoodEnabled(moodKey, seed, o) {
    var M = moodKey ? JZ_DATA.moods[moodKey] : null;
    if (!M) return jzAllEnabled();
    var rng = new JzRng((jzHash(seed || 1, 4242) % 2147483646) + 1), en = {}, g, i, k;
    for (g = 0; g < JZ_GROUPS.length; g++) {
        var grp = JZ_GROUPS[g], ord = jzOrder(grp), items = [], on = {}, n = 0;
        for (i = 0; i < ord.length; i++) {
            // a set tied to a mood (\u30DB\u30E9\u30FC) is only used in that mood
            if (jzMeta(grp, ord[i]).set && jzMoodOfSet(jzMeta(grp, ord[i]).set) && jzMoodOfSet(jzMeta(grp, ord[i]).set) !== moodKey) { on[ord[i]] = false; continue; }
            if (!jzMeta(grp, ord[i]).special && (!o || jzRandomOk(o, grp, ord[i]))) items.push(ord[i]);
        }
        var hand = (grp === 'layout' || grp === 'enter' || grp === 'exit') && M[grp] instanceof Array ? M[grp] : [];
        for (i = 0; i < items.length; i++) {
            k = items[i];
            var pref = moodKey === 'chaos' ? null : (jzIndexOf(hand, k) >= 0 || jzHasTag(jzMeta(grp, k), moodKey));
            on[k] = pref === null ? rng.chance(0.8) : (pref || rng.chance(0.22));
            if (on[k]) n++;
        }
        var min = Math.min(JZ_MOOD_MIN[grp] || 3, items.length), guard = 0;
        while (n < min && guard++ < 500) { k = items[rng.int(0, items.length - 1)]; if (!on[k]) { on[k] = true; n++; } }
        en[grp] = on;
    }
    en.enter.cut = true; en.exit.cut = true; en.hold.still = true;
    if (en.treat) en.treat.none = true; if (en.bg) en.bg.none = true; if (en.cam) en.cam.push = true;
    for (k = 0; k < (M.noHold || []).length; k++) en.hold[M.noHold[k]] = false;
    return en;
}
// roll a whole new look: mood, style, effect strengths, switches, seed, maybe a palette
// o: the \u8FFD\u52A0\u5206 / \u548C\u98A8 switches ({ extra, wa }) \u2014 styles they rule out are never picked
function jzOmakase(curMood, curStyle, rnd, o) {
    rnd = rnd || Math.random;
    function pick(a) { return a[Math.floor(rnd() * a.length) % a.length]; }
    function range(r) { return r[0] + (r[1] - r[0]) * rnd(); }
    var moods = [], i, k;
    function moodOk(m) { var s = JZ_DATA.moods[m].set; return !s || jzSetOn(o || {}, s); }
    for (i = 0; i < JZ_DATA.moodOrder.length; i++) if (JZ_DATA.moodOrder[i] !== curMood && moodOk(JZ_DATA.moodOrder[i])) moods.push(JZ_DATA.moodOrder[i]);
    // with the \u30DB\u30E9\u30FC switch on, \u304A\u307E\u304B\u305B leans to the \u30DB\u30E9\u30FC mood (it may repeat)
    var mood = JZ_DATA.moods.horror && moodOk('horror') && rnd() < 0.55 ? 'horror' : pick(moods), M = JZ_DATA.moods[mood];
    function okStyle(k) { var S = JZ_DATA.styles[k]; return S && jzRandomOk(o || {}, 'style', k) && (!S.set || !jzMoodOfSet(S.set) || jzMoodOfSet(S.set) === mood); }
    var moodStyles = [], seen = {};
    for (i = 0; i < (M.styles || []).length; i++) if (okStyle(M.styles[i]) && !seen[M.styles[i]]) { moodStyles.push(M.styles[i]); seen[M.styles[i]] = 1; }
    for (i = 0; i < JZ_DATA.styleOrder.length; i++) { k = JZ_DATA.styleOrder[i]; if (!seen[k] && okStyle(k) && jzIndexOf(JZ_DATA.styles[k].moods || [], mood) >= 0) { moodStyles.push(k); seen[k] = 1; } }
    var base = [], pool = [];
    if (moodStyles.length && rnd() < 0.72) base = moodStyles; else for (i = 0; i < JZ_DATA.styleOrder.length; i++) if (okStyle(JZ_DATA.styleOrder[i])) base.push(JZ_DATA.styleOrder[i]);
    for (i = 0; i < base.length; i++) if (base[i] !== curStyle) pool.push(base[i]);
    if (!pool.length) for (i = 0; i < JZ_DATA.styleOrder.length; i++) if (JZ_DATA.styleOrder[i] !== curStyle && okStyle(JZ_DATA.styleOrder[i])) pool.push(JZ_DATA.styleOrder[i]);
    if (!pool.length) pool = [curStyle || 'noir'];
    var style = pick(pool), fx = {};
    for (k in M.fx) if (M.fx.hasOwnProperty(k)) fx[k] = range(M.fx[k]);
    var r = { mood: mood, style: style, fx: fx, onTwos: rnd() < 0.75, flash: rnd() < 0.65, hud: pick([0, 0, 1, 2]), seed: Math.floor(rnd() * 999999999), palette: null };
    if (rnd() < 0.38) r.palette = jzRandomPalette(JZ_DATA.styles[style].schemes[0].bg, rnd);
    return r;
}

// ================================================================ motion (enter / hold / exit)
// Everything is expressed as text-animator Expression Selectors or layer
// transform expressions, so the result stays editable and re-timable in AE.
var JZ_FNS = 'function cl(x){return Math.max(0,Math.min(1,x));}' +
    'function oe(x){x=cl(x);return x>=1?1:1-Math.pow(2,-10*x);}' +
    'function ioe(x){x=cl(x);if(x<=0||x>=1)return x;return x<0.5?Math.pow(2,20*x-10)/2:(2-Math.pow(2,-20*x+10))/2;}' +
    'function oc(x){x=cl(x);return 1-Math.pow(1-x,3);}' +
    'function ic(x){x=cl(x);return x*x*x;}' +
    'function iq(x){x=cl(x);return x*x;}' +
    'function ie(x){x=cl(x);return x<=0?0:Math.pow(2,10*x-10);}' +
    'function ob(x,s){x=cl(x);var c=s+1;return 1+c*Math.pow(x-1,3)+s*Math.pow(x-1,2);}' +
    'function bo(x){x=cl(x);var n=7.5625,d=2.75;if(x<1/d)return n*x*x;if(x<2/d){x-=1.5/d;return n*x*x+0.75;}if(x<2.5/d){x-=2.25/d;return n*x*x+0.9375;}x-=2.625/d;return n*x*x+0.984375;}' +
    'function hh(n){var x=Math.sin(n*127.1+311.7)*43758.5453;return x-Math.floor(x);}\n';

function jzHead(ctx, o) {
    var c = ctx.cut;
    return 'var IN=' + jzN(Math.max(0.02, c.inDur || 0.3)) + ',DL=' + jzN((o.mi || 0) * (c.stagger || 0.04)) +
        ',OS=' + jzN(c.dur - (c.outDur || 0)) + ',OD=' + jzN(Math.max(0.001, c.outDur || 0)) + ',DUR=' + jzN(c.dur) +
        ',SD=' + ((c.seed % 99991) + (o.mi || 0) * 101) + ',M=' + jzN(ctx.fx.motion) + ',G=' + jzN(ctx.fx.glitch) + ',SZ=' + jzN(o.size || 100) + ';\n' + JZ_FNS +
        'var P=cl((time-DL)/IN), PO=(OD>0.002?cl((time-OS)/OD):0), AMT=cl((time-DL-IN*0.85)/0.25)*(1-PO);\n';
}

// Motion context passed to every enter / hold / exit implementation:
//   m.ctx, m.L (text layer), m.o (options from the layout: size, mi, \u2026), m.c (cut), m.W, m.H, m.u (= H/1080)
//   m.size  font size of the layer (px)        m.HD  expression header (IN, DL, OS, OD, DUR, SD, M, G, SZ, P, PO, AMT + easing fns)
//   m.seedR 'seedRandom(textIndex*7919+SD,true);' for per-glyph randomness inside selector expressions
//   m.parts { sc:[], pos:[], op:[], rot:[] } \u2014 snippets combined into ONE expression per layer transform property:
//           sc  modifies f=[1,1] (scale factors)   pos modifies d=[0,0] (px offset)   op modifies f=1   rot modifies r=0 (degrees)
// Expression variables: P = enter progress 0..1 (from DL over IN seconds), PO = exit progress 0..1, AMT = hold amount
// (0 during the entrance, 1 while holding, fades out with the exit). Use jzAnimator() for per-glyph work.
function jzMotion(ctx, L, o) {
    var c = ctx.cut, isText = o.text !== false;
    var m = { ctx: ctx, L: L, o: o, c: c, W: ctx.W, H: ctx.H, u: ctx.H / 1080, size: o.size || 100, HD: jzHead(ctx, o),
        seedR: 'seedRandom(textIndex*7919+SD,true);', parts: { sc: [], pos: [], op: [], rot: [] }, isText: isText,
        enter: jzFallback('enter', o.enter || c.enter || 'cut', 'blur'), exit: jzFallback('exit', o.exit || c.exit || 'cut', 'blur'),
        hold: o.noHold ? 'still' : jzFallback('hold', c.hold || 'still', 'still') };
    var parts = m.parts, HD = m.HD;
    if (!isText) {       // shapes / groups: map to layer-level motion
        if (m.enter !== 'cut') { parts.sc.push('var q=oe(P);f=[f[0]*(q<1?ob(P,2):1),f[1]*(q<1?ob(P,2):1)];'); }
        if (m.exit !== 'cut') parts.sc.push('f=[f[0]*(1-ic(PO)),f[1]*(1-ic(PO))];');
        parts.op.push('f*=time<DL?0:1;');
    } else {
        var E = JZ_REG.enter[m.enter], X = JZ_REG.exit[m.exit], Ho = JZ_REG.hold[m.hold];
        if (E && E.apply) { try { E.apply(m); } catch (e1) { jzWarn('enter ' + m.enter + ': ' + e1.toString()); } }
        if (!E || !E.selfHide) parts.op.push('f*=time<DL?0:1;');
        if (Ho && Ho.apply) { try { Ho.apply(m); } catch (e2) { jzWarn('hold ' + m.hold + ': ' + e2.toString()); } }
        if (X && X.apply) { try { X.apply(m); } catch (e3) { jzWarn('exit ' + m.exit + ': ' + e3.toString()); } }
    }
    // compose layer transform expressions
    if (parts.sc.length) jzSetExpr(jzXf(L, 'ADBE Scale'), HD + 'var f=[1,1];' + parts.sc.join('') + '[value[0]*f[0],value[1]*f[1]]');
    if (parts.pos.length) jzSetExpr(jzXf(L, 'ADBE Position'), HD + 'var d=[0,0];' + parts.pos.join('') + '[value[0]+d[0],value[1]+d[1]]');
    if (parts.op.length) jzSetExpr(jzXf(L, 'ADBE Opacity'), HD + 'var f=1;' + parts.op.join('') + 'value*f');
    if (parts.rot.length) jzSetExpr(jzXf(L, 'ADBE Rotate Z'), HD + 'var r=0;' + parts.rot.join('') + 'value+r');
    return L;
}

// ---------------------------------------------------------------- core enters
jzReg('enter', 'cut', { apply: function (m) {} });
jzReg('enter', 'assemble', { selfHide: true, apply: function (m) {
    var L = m.L, HD = m.HD, sp = m.size * 3.2 * (0.6 + m.ctx.fx.motion * 0.7);
    var core = HD + m.seedR + 'var dl=random(0,IN*0.45);var x=(time-DL-dl)/(IN*0.62);var k=(1-oe(x))*100;';
    jzAnimator(L, 'JZ In Move', [['ADBE Text Position 3D', [sp, sp, 0]], ['ADBE Text Rotation', 190]], core + 'var a=random(0,Math.PI*2);var r=random(0.35,1);[Math.cos(a)*k*r,Math.sin(a)*k*r,0]');
    jzAnimator(L, 'JZ In Scale', [['ADBE Text Scale 3D', [210, 210, 100]]], core + 'Math.max(-55,Math.min(100,k*random(-0.55,1.1)))');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], core + 'x<0?100:0');
} });
jzReg('enter', 'slice', { apply: function (m) {
    var HD = m.HD, ww = jzEffect(m.L, 'ADBE Wave Warp', 'JZ In Slice');
    jzEP(ww, 1, 2); jzEP(ww, 4, 0); jzEP(ww, 5, 0); jzEP(ww, 6, 1);
    jzEX(ww, 2, HD + '(1-oe(P*1.15))*thisComp.width*0.45');
    jzEX(ww, 3, HD + 'posterizeTime(12);seedRandom(SD+Math.floor(time*12),true);random(SZ*0.18,SZ*0.9)');
    jzEX(ww, 7, HD + 'posterizeTime(12);seedRandom(SD+Math.floor(time*12)+3,true);random(0,360)');
} });
jzReg('enter', 'type', { selfHide: true, apply: function (m) {
    jzAnimator(m.L, 'JZ In Type', [['ADBE Text Opacity', 0]], m.HD + 'textIndex>Math.floor(P*(textTotal+0.999))?100:0');
} });
function jzPopSpin(m, spin) {
    var qs = m.HD + 'var d=textTotal>1?(textIndex-1)/(textTotal-1)*0.45:0;var q=cl((P-d)/0.55);';
    if (!spin) {
        jzAnimator(m.L, 'JZ In Pop', [['ADBE Text Scale 3D', [0, 0, 100]]], qs + 'q<=0?100:(1-ob(q,2.6))*100');
        jzAnimator(m.L, 'JZ In Tilt', [['ADBE Text Rotation', 28]], qs + m.seedR + '(1-oc(q))*100*random(-1,1)');
    } else {
        jzAnimator(m.L, 'JZ In Spin', [['ADBE Text Rotation', 200]], qs + m.seedR + '(1-oe(q))*100*(random()<0.5?-1:1)');
        jzAnimator(m.L, 'JZ In SpinScale', [['ADBE Text Scale 3D', [15, 15, 100]]], qs + '(1-oe(q))*100');
    }
    jzAnimator(m.L, 'JZ In Hide', [['ADBE Text Opacity', 0]], qs + 'q<=0?100:0');
}
jzReg('enter', 'pop', { selfHide: true, apply: function (m) { jzPopSpin(m, false); } });
jzReg('enter', 'spin', { selfHide: true, apply: function (m) { jzPopSpin(m, true); } });
jzReg('enter', 'drop', { selfHide: true, apply: function (m) {
    var qd = m.HD + m.seedR + 'var d=textTotal>1?random(0,0.5):0;var q=cl((P-d)/0.5);';
    jzAnimator(m.L, 'JZ In Drop', [['ADBE Text Position 3D', [0, -m.size * 2.4, 0]]], qd + 'q<=0?100:(1-bo(q))*100');
    jzAnimator(m.L, 'JZ In Hide', [['ADBE Text Opacity', 0]], qd + 'q<=0?100:0');
} });
jzReg('enter', 'stretch', { apply: function (m) {
    m.parts.sc.push('var e=oe(P);f=[f[0]*(4.2+(1-4.2)*e),f[1]];');
    var db = jzEffect(m.L, 'ADBE Motion Blur', 'JZ In Streak');
    jzEP(db, 1, 90); jzEX(db, 2, m.HD + '(1-oe(P))*SZ*0.7');
} });
jzReg('enter', 'wipe', { apply: function (m) {
    var lw = jzEffect(m.L, 'ADBE Linear Wipe', 'JZ In Wipe');
    jzEP(lw, 2, (m.c.seed % 2) ? 90 : 270); jzEP(lw, 3, 0);
    jzEX(lw, 1, m.HD + '100*(1-ioe(P))');
} });
jzReg('enter', 'blur', { apply: function (m) {
    jzAnimator(m.L, 'JZ In Blur', [['ADBE Text Blur', [26, 26]], ['ADBE Text Opacity', 0], ['ADBE Text Tracking Amount', 60]], m.HD + '(1-oc(P))*100');
} });
jzReg('enter', 'flicker', { selfHide: true, apply: function (m) {
    jzAnimator(m.L, 'JZ In Flicker', [['ADBE Text Opacity', 0]], m.HD + 'posterizeTime(12);seedRandom(textIndex*31+SD+Math.floor(time*12)*7,true);P>=1?0:(random()<P*1.25?0:100)');
} });
jzReg('enter', 'scramble', { selfHide: true, apply: function (m) {
    jzAnimator(m.L, 'JZ In Scramble', [['ADBE Text Character Offset', 60]], m.HD + 'var st=0.25+0.75*(textTotal>1?(textIndex-1)/(textTotal-1):1);posterizeTime(12);seedRandom(textIndex+SD+Math.floor(time*12)*97,true);P>=st?0:random(15,100)');
    jzAnimator(m.L, 'JZ In Hide', [['ADBE Text Opacity', 0]], m.HD + 'var st=0.25+0.75*(textTotal>1?(textIndex-1)/(textTotal-1):1);posterizeTime(12);seedRandom(textIndex*3+SD+Math.floor(time*12),true);(P<st*0.25&&random()<0.5)||time<DL?100:0');
} });
jzReg('enter', 'zoom', { apply: function (m) {
    m.parts.sc.push('var e=oe(P);f=[f[0]*(1.7+(1-1.7)*e),f[1]*(1.7+(1-1.7)*e)];');
    m.parts.op.push('f*=Math.min(1,P*4);');
    var gb = jzEffect(m.L, 'ADBE Gaussian Blur 2', 'JZ In Blur');
    jzEX(gb, 1, m.HD + '(1-oe(P))*14');
} });

// ---------------------------------------------------------------- core holds
jzReg('hold', 'still', { apply: function (m) {} });
jzReg('hold', 'jitter', { apply: function (m) {
    var A = m.size * 0.025 * m.ctx.fx.motion;
    jzAnimator(m.L, 'JZ Hold Jitter', [['ADBE Text Position 3D', [A, A, 0]], ['ADBE Text Rotation', 4]], m.HD + 'posterizeTime(12);seedRandom(textIndex*13+SD+Math.floor(time*12)*7,true);[random(-100,100)*AMT,random(-100,100)*AMT,0]');
} });
jzReg('hold', 'wave', { apply: function (m) {
    jzAnimator(m.L, 'JZ Hold Wave', [['ADBE Text Position 3D', [0, m.size * 0.07, 0]], ['ADBE Text Rotation', 5]], m.HD + 'var v=Math.sin(time*7+textIndex*0.75)*100*AMT;[v,v,0]');
} });
jzReg('hold', 'drift', { apply: function (m) {
    m.parts.pos.push('d=[d[0]+' + ((m.c.seed % 2) ? 1 : -1) + '*(time/DUR-0.5)*thisComp.width*0.035*M,d[1]];');
    m.parts.sc.push('f=[f[0]*(1+0.05*time/DUR*M),f[1]*(1+0.05*time/DUR*M)];');
} });
jzReg('hold', 'breathe', { apply: function (m) {
    m.parts.sc.push('var b=1+0.035*Math.sin(time*Math.PI*1.8)*AMT;f=[f[0]*b,f[1]*b];');
} });
jzReg('hold', 'glitchtick', { apply: function (m) {
    var gw = jzEffect(m.L, 'ADBE Wave Warp', 'JZ Hold Glitch');
    jzEP(gw, 1, 2); jzEP(gw, 4, 0); jzEP(gw, 5, 0); jzEP(gw, 6, 1);
    jzEX(gw, 2, m.HD + 'posterizeTime(12);seedRandom(SD+Math.floor(time*12),true);random()<0.22*G+0.02?random(0.1,0.35)*SZ*AMT:0');
    jzEX(gw, 3, m.HD + 'posterizeTime(12);seedRandom(SD+Math.floor(time*12)+5,true);random(SZ*0.2,SZ*0.8)');
} });

// ---------------------------------------------------------------- core exits
jzReg('exit', 'cut', { apply: function (m) {} });
function jzExplodeLike(m, ex) {
    var W = m.W, H = m.H, big = ex === 'explode' ? Math.max(W, H) * 0.9 * (0.5 + m.ctx.fx.motion * 0.6) : W * 0.7;
    var xe = m.HD + 'seedRandom(textIndex*977+SD,true);var dl=random(0,OD*0.3);var x=cl((time-OS-dl)/(OD*0.7));var e=x*x*x;';
    jzAnimator(m.L, 'JZ Out ' + (ex === 'explode' ? 'Explode' : 'Scatter'), [['ADBE Text Position 3D', [big, big, 0]], ['ADBE Text Rotation', ex === 'explode' ? 260 : 540]],
        xe + 'var mid=(textTotal+1)/2;var dx=(textIndex-mid)/Math.max(1,textTotal)*1.6+random(-0.6,0.6);var dy=random(-1,1);var l=Math.sqrt(dx*dx+dy*dy)+0.0001;var r=random(0.35,1);[dx/l*e*100*r,dy/l*e*100*r,0]');
    jzAnimator(m.L, 'JZ Out Fade', [['ADBE Text Opacity', 0], ['ADBE Text Scale 3D', [60, 60, 100]]], xe + 'x*x*100');
}
jzReg('exit', 'explode', { apply: function (m) { jzExplodeLike(m, 'explode'); } });
jzReg('exit', 'scatter', { apply: function (m) { jzExplodeLike(m, 'scatter'); } });
jzReg('exit', 'fall', { apply: function (m) {
    var H = m.H, G2 = H * 5.5;
    jzAnimator(m.L, 'JZ Out Fall', [['ADBE Text Position 3D', [0, H * 1.3, 0]], ['ADBE Text Rotation', 200]],
        m.HD + m.seedR + 'var x=time-OS-random(0,OD*0.4);var f=x<=0?0:Math.min(100,0.5*' + jzN(G2) + '*x*x/' + jzN(H * 1.3) + '*100);[random(-1,1)*Math.min(100,Math.max(0,x)*120),f,0]');
} });
jzReg('exit', 'drift', { apply: function (m) {
    var ds = m.size * 1.6;
    var xd = m.HD + 'seedRandom(textIndex*433+SD,true);var x=cl((time-OS-random(0,OD*0.3))/(OD*0.7));var e=iq(x);';
    jzAnimator(m.L, 'JZ Out Drift', [['ADBE Text Position 3D', [ds, ds, 0]], ['ADBE Text Blur', [18, 18]]], xd + 'var a=random(0,Math.PI*2);[Math.cos(a)*e*100,Math.sin(a)*e*100-30*e,0]');
    jzAnimator(m.L, 'JZ Out Fade', [['ADBE Text Opacity', 0]], xd + 'e*e*100');
} });
function jzSliceGlitch(m, ex) {
    var HD = m.HD, sw = jzEffect(m.L, 'ADBE Wave Warp', ex === 'slice' ? 'JZ Out Slice' : 'JZ Out Glitch');
    jzEP(sw, 1, 2); jzEP(sw, 4, 0); jzEP(sw, 5, 0); jzEP(sw, 6, 1);
    if (ex === 'slice') jzEX(sw, 2, HD + 'ie(PO)*thisComp.width*0.55');
    else { jzEX(sw, 2, HD + 'posterizeTime(12);seedRandom(SD+Math.floor(time*12)*3,true);PO>0?SZ*(0.3+PO*2.2)*random(0,1):0'); m.parts.op.push('if(PO>0.55){seedRandom(SD+Math.floor(time*12)*5,true);f*=random()<0.5?0.15:1;} f*=1-cl((PO-0.8)/0.2);'); }
    jzEX(sw, 3, HD + 'posterizeTime(12);seedRandom(SD+Math.floor(time*12)+9,true);random(SZ*0.15,SZ*0.9)');
}
jzReg('exit', 'slice', { apply: function (m) { jzSliceGlitch(m, 'slice'); } });
jzReg('exit', 'glitch', { apply: function (m) { jzSliceGlitch(m, 'glitch'); } });
jzReg('exit', 'wipe', { apply: function (m) {
    var lw2 = jzEffect(m.L, 'ADBE Linear Wipe', 'JZ Out Wipe');
    jzEP(lw2, 2, (m.c.seed % 2) ? 90 : 270); jzEP(lw2, 3, 0);
    jzEX(lw2, 1, m.HD + '100*ioe(PO)');
} });
jzReg('exit', 'shrink', { apply: function (m) { m.parts.sc.push('var s2=1-0.96*ic(PO);f=[f[0]*s2,f[1]*s2];'); m.parts.op.push('f*=1-ic(PO)*ic(PO);'); } });
jzReg('exit', 'blur', { apply: function (m) { jzAnimator(m.L, 'JZ Out Blur', [['ADBE Text Blur', [30, 30]], ['ADBE Text Opacity', 0]], m.HD + 'iq(PO)*100'); } });
jzReg('exit', 'stretch', { apply: function (m) { m.parts.sc.push('var e2=ie(PO);f=[f[0]*(1+5*e2),f[1]*(1-0.4*e2)];'); m.parts.op.push('f*=1-cl((PO-0.7)/0.3);'); } });

// simple pop for non-text elements (decor, labels): scale in with overshoot, out with the cut
function jzPop(ctx, L, delay, outToo) {
    var c = ctx.cut;
    var ex = 'var st=' + jzN(delay || 0) + ',du=0.24,OS=' + jzN(c.dur - (c.outDur || 0.15)) + ',OD=' + jzN(Math.max(0.12, c.outDur || 0.15)) + ';' + JZ_FNS +
        'var q=cl((time-st)/du);var s=q<=0?0:ob(q,1.9);' + (outToo !== false ? 's*=1-ic((time-OS)/OD);' : '') + '[value[0]*s,value[1]*s]';
    jzSetExpr(jzXf(L, 'ADBE Scale'), ex);
}
function jzFadeIO(ctx, L, delay, inDur) {
    var c = ctx.cut;
    jzSetExpr(jzXf(L, 'ADBE Opacity'), 'var st=' + jzN(delay || 0) + ',du=' + jzN(inDur || 0.3) + ',OS=' + jzN(c.dur - Math.max(0.12, c.outDur || 0.15)) + ',OD=' + jzN(Math.max(0.12, c.outDur || 0.15)) + ';' + JZ_FNS + 'value*oc((time-st)/du)*(1-ic((time-OS)/OD))');
}

// ================================================================ layouts (AE)
function jzSplitLines(text, maxPer) {
    var arr = jzChars(text);
    if (arr.length <= maxPer) return text;
    var nL = Math.ceil(arr.length / maxPer), per = arr.length / nL, out = [], start = 0;
    for (var l = 1; l < nL; l++) {
        var target = Math.round(per * l), best = target, bestS = -99;
        for (var k = Math.max(start + 1, target - 3); k <= Math.min(arr.length - 1, target + 3); k++) {
            var a = arr[k - 1], b = arr[k], s = 3 - Math.abs(k - target);
            if (jzIsHira(a) && !jzIsHira(b)) s += 3;
            if (jzIsPunct(a) || a === ' ' || a === '\u3000') s += 5;
            if ('\u3063\u3083\u3085\u3087\u30FC\u3001\u3002'.indexOf(b) >= 0) s -= 6;
            if (s > bestS) { bestS = s; best = k; }
        }
        out.push(jzTrim(arr.slice(start, best).join(''))); start = best;
    }
    out.push(jzTrim(arr.slice(start).join('')));
    return out.join('\r');
}
var JZ_KANA = { '\u3042': 'a', '\u3044': 'i', '\u3046': 'u', '\u3048': 'e', '\u304A': 'o', '\u304B': 'ka', '\u304D': 'ki', '\u304F': 'ku', '\u3051': 'ke', '\u3053': 'ko', '\u3055': 'sa', '\u3057': 'shi', '\u3059': 'su', '\u305B': 'se', '\u305D': 'so', '\u305F': 'ta', '\u3061': 'chi', '\u3064': 'tsu', '\u3066': 'te', '\u3068': 'to', '\u306A': 'na', '\u306B': 'ni', '\u306C': 'nu', '\u306D': 'ne', '\u306E': 'no', '\u306F': 'ha', '\u3072': 'hi', '\u3075': 'fu', '\u3078': 'he', '\u307B': 'ho', '\u307E': 'ma', '\u307F': 'mi', '\u3080': 'mu', '\u3081': 'me', '\u3082': 'mo', '\u3084': 'ya', '\u3086': 'yu', '\u3088': 'yo', '\u3089': 'ra', '\u308A': 'ri', '\u308B': 'ru', '\u308C': 're', '\u308D': 'ro', '\u308F': 'wa', '\u3092': 'wo', '\u3093': 'n', '\u304C': 'ga', '\u304E': 'gi', '\u3050': 'gu', '\u3052': 'ge', '\u3054': 'go', '\u3056': 'za', '\u3058': 'ji', '\u305A': 'zu', '\u305C': 'ze', '\u305E': 'zo', '\u3060': 'da', '\u3062': 'ji', '\u3065': 'zu', '\u3067': 'de', '\u3069': 'do', '\u3070': 'ba', '\u3073': 'bi', '\u3076': 'bu', '\u3079': 'be', '\u307C': 'bo', '\u3071': 'pa', '\u3074': 'pi', '\u3077': 'pu', '\u307A': 'pe', '\u307D': 'po' };
function jzRomaji(s) {
    var a = jzChars(s), out = '', i;
    for (i = 0; i < a.length; i++) { var c = a[i]; if (jzIsKata(c) && c !== '\u30FC') a[i] = String.fromCharCode(c.charCodeAt(0) - 0x60); }
    for (i = 0; i < a.length; i++) {
        var ch = a[i], nx = a[i + 1];
        if (ch === '\u3063') { var q = JZ_KANA[nx]; if (q) out += q.charAt(0); continue; }
        if (ch === '\u30FC') { out += out.charAt(out.length - 1); continue; }
        if ((nx === '\u3083' || nx === '\u3085' || nx === '\u3087') && JZ_KANA[ch]) {
            var b = JZ_KANA[ch], y = nx === '\u3083' ? 'ya' : nx === '\u3085' ? 'yu' : 'yo';
            out += (b === 'shi' || b === 'chi' || b === 'ji') ? b.substr(0, b.length - 1) + y.substr(1) : b.substr(0, b.length - 1) + y; i++; continue;
        }
        if (JZ_KANA[ch]) out += JZ_KANA[ch]; else if (/[A-Za-z0-9 ]/.test(ch)) out += ch; else return null;
    }
    return out;
}
function jzBB(L) {
    var s = jzSize(L), p = jzXf(L, 'ADBE Position').value;
    return { x0: p[0] - s[0] / 2, x1: p[0] + s[0] / 2, y0: p[1] - s[1] / 2, y1: p[1] + s[1] / 2, cx: p[0], cy: p[1] };
}
function jzUnion(a, b) { if (!a) return b; if (!b) return a; var r = { x0: Math.min(a.x0, b.x0), y0: Math.min(a.y0, b.y0), x1: Math.max(a.x1, b.x1), y1: Math.max(a.y1, b.y1) }; r.cx = (r.x0 + r.x1) / 2; r.cy = (r.y0 + r.y1) / 2; return r; }
function jzP(ctx, k, d) { return (ctx.P && ctx.P[k] != null) ? ctx.P[k] : d; }
function jzFontOf(ctx, k, role) { var v = jzP(ctx, k, null); return v || (ctx.st.fonts[role] ? ctx.st.fonts[role][0] : 'gothic_black'); }
// the lyric itself: text + the cut's enter / hold / exit + the cut's text treatment
function jzMain(ctx, str, o) { var L = jzText(ctx, str, o); return jzAnimate(ctx, L, o); }
// motion + treatment for a text layer made with jzText (use when the layer has to be measured / placed first)
function jzAnimate(ctx, L, o) {
    o = o || {};
    jzMotion(ctx, L, { size: jzFontSize(L), mi: o.mi || 0, noHold: o.noHold, enter: o.enter, exit: o.exit });
    var tk = ctx.cut.treat && ctx.cut.treat !== 'none' && o.treat !== false ? jzFallback('treat', ctx.cut.treat, null) : null;
    if (tk) { try { JZ_REG.treat[tk].apply(ctx, L, ctx.cut.treatP || {}, o); } catch (e) { jzWarn('treat ' + tk + ': ' + e.toString() + (e.line ? ' (line ' + e.line + ')' : '')); } }
    return L;
}
function jzSmall(ctx, str, o) { o.font = o.font || jzFontOf(ctx, '_', 'body'); var L = jzText(ctx, str, o); return L; }

var JZ_LAYOUTS = {};

JZ_LAYOUTS.center = function (ctx) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, sx = jzP(ctx, 'sx', 1);
    var L = jzMain(ctx, jzSplitLines(c.text, 11), { font: jzFontOf(ctx, 'font', 'display'), size: 200, color: jzP(ctx, 'accent', false) ? sc.accent : sc.fg, x: W / 2 + jzP(ctx, 'ox', 0) * W, y: H / 2 + jzP(ctx, 'oy', 0) * H, track: jzP(ctx, 'track', 0.06), maxW: W * 0.84 / sx, maxH: H * 0.5, maxSize: H * 0.33, sx: sx });
    var bb = jzBB(L);
    if (jzP(ctx, 'sub', false) && c.lineText && c.lineText !== c.text) {
        var s = jzSmall(ctx, c.lineText, { size: Math.max(16, H * 0.026), color: sc.sub, x: bb.cx, y: bb.y1 + H * 0.07, track: 0.22 });
        jzFadeIO(ctx, s, c.inDur * 0.5, 0.3); jzNoGhost(s);       // helper layers drawn with ghost off in the browser: jzNoGhost
    }
    if (jzP(ctx, 'under', false)) {
        var U = jzShapeLayer(ctx, 'underline', bb.x0, bb.y1 + H * 0.02);
        var g = jzGrp(U, 'bar'); jzAddRect(g, bb.x1 - bb.x0, Math.max(3, H * 0.006), 0, (bb.x1 - bb.x0) / 2, 0); jzAddFill(g, sc.accent);
        jzSetExpr(jzXf(U, 'ADBE Scale'), JZ_FNS + 'var e=oe((time-' + jzN(c.inDur * 0.3) + ')/0.5);[value[0]*e,value[1]]');
    }
    return bb;
};

JZ_LAYOUTS.mixed = function (ctx) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, seed = c.seed;
    var chars = jzChars(c.text.replace(/[\s\u3000]+/g, '')), n = chars.length, rows = n > 9 ? 2 : 1, per = Math.ceil(n / rows);
    var fB = jzFontOf(ctx, 'fontBig', 'display'), fS = jzFontOf(ctx, 'fontSmall', 'serif'), smallK = jzP(ctx, 'smallK', 0.52), rotA = jzP(ctx, 'rotAmp', 6), mode = jzP(ctx, 'mode', 'line');
    var bb = null, r, i;
    for (r = 0; r < rows; r++) {
        var row = chars.slice(r * per, (r + 1) * per), Ls = [], ks = [], widths = [], sum = 0;
        for (i = 0; i < row.length; i++) {
            var ch = row[i], gi = r * per + i;
            var k = jzIsKanji(ch) ? 1 : jzIsKata(ch) ? 0.88 : jzIsLatin(ch) ? 0.8 : jzIsPunct(ch) ? 0.42 : smallK + jzR(seed, gi, 3) * 0.14;
            var font = (jzIsKanji(ch) || jzIsKata(ch)) ? fB : (jzR(seed, gi, 4) < 0.55 ? fS : fB);
            var L = jzText(ctx, ch, { font: font, size: 100 * k, color: sc.fg, x: 0, y: 0 });
            var sz = jzSize(L); Ls.push(L); ks.push(k); widths.push(Math.max(sz[0], 100 * k * 0.6)); sum += widths[i];
        }
        var s = Math.min(W * 0.86 / sum, (H * (rows > 1 ? 0.3 : 0.4)) / 100);
        var x = W / 2 - sum * s / 2, base = 100 * s, baseline = H / 2 + base * 0.38 + (r - (rows - 1) / 2) * base * 1.05;
        for (i = 0; i < Ls.length; i++) {
            var gi2 = r * per + i, src = Ls[i].property('ADBE Text Properties').property('ADBE Text Document'), td = src.value;
            td.fontSize = 100 * ks[i] * s; if (gi2 === (jzP(ctx, 'accentIdx', 99) % n) && !jzIsKanji(row[i])) td.fillColor = jzHex(sc.accent);
            src.setValue(td); jzAnchor(Ls[i]);
            var size = 100 * ks[i] * s, y = baseline - size / 2 + (jzR(seed, gi2, 5) * 2 - 1) * base * 0.06;
            if (mode === 'stair') y += (i - (Ls.length - 1) / 2) * base * 0.12;
            if (mode === 'wave') y += Math.sin(i * 1.1) * base * 0.1;
            jzXf(Ls[i], 'ADBE Position').setValue([x + widths[i] * s / 2, y]);
            jzXf(Ls[i], 'ADBE Rotate Z').setValue((jzR(seed, gi2, 6) * 2 - 1) * rotA);
            jzMotion(ctx, Ls[i], { size: size, mi: gi2 });
            bb = jzUnion(bb, jzBB(Ls[i]));
            x += widths[i] * s;
        }
    }
    return bb;
};

JZ_LAYOUTS.vcols = function (ctx) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, text = c.text.replace(/[\s\u3000]+/g, ''), n = jzCount(text);
    var font = jzFontOf(ctx, 'font', 'serif'), variant = jzP(ctx, 'variant', n <= 5 ? 'repeat' : 'split'), bb = null, i;
    if (variant === 'repeat' && n <= 9) {
        var cols = jzP(ctx, 'cols', 3), size = Math.min(H * 0.8 / (n * 1.04), W * 0.86 / (cols * 1.75)), mid = (cols - 1) / 2, side = jzP(ctx, 'side', 'same');
        for (i = 0; i < cols; i++) {
            var isSide = i !== Math.round(mid);
            var o = { font: font, size: size, color: sc.fg, x: W / 2 + (i - mid) * size * 1.75, y: H / 2, leading: size * 1.04, mi: Math.abs(i - mid) * 2 };
            if (isSide && side === 'outline') { o.fill = false; o.stroke = Math.max(1.2, size * 0.012); o.strokeColor = sc.fg; }
            if (isSide && side === 'dim') o.opacity = 0.38;
            var L = jzMain(ctx, jzVertical(text), o);
            if (!isSide) bb = jzBB(L);
        }
        return bb;
    }
    var arr = jzChars(text), perCol = Math.max(2, Math.min(jzP(ctx, 'perCol', 4) + 1, Math.ceil(n / Math.ceil(n / 7)))), colsA = [];
    for (i = 0; i < arr.length; i += perCol) colsA.push(arr.slice(i, i + perCol).join(''));
    var sz = Math.min(H * 0.74 / (perCol * 1.03), W * 0.8 / (colsA.length * 1.4)), top = H / 2 - perCol * sz * 1.03 / 2;
    for (i = 0; i < colsA.length; i++) {
        var x = W / 2 + ((colsA.length - 1) / 2 - i) * sz * 1.4;
        var cL = jzText(ctx, jzVertical(colsA[i]), { font: font, size: sz, color: sc.fg, x: x, y: 0, leading: sz * 1.03 });
        var h = jzSize(cL)[1]; jzXf(cL, 'ADBE Position').setValue([x, top + h / 2]);
        jzAnimate(ctx, cL, { size: sz, mi: i * 3 });
        bb = jzUnion(bb, jzBB(cL));
    }
    return bb;
};

function jzScrollRow(ctx, text, o) {
    // one repeated row that scrolls sideways forever (period measured from the unit)
    var unit = text + '\u3000';
    var probe = jzText(ctx, unit, { font: o.font, size: o.size, color: o.color, x: -9999, y: -9999, track: o.track || 0.05 });
    var per = Math.max(10, jzSize(probe)[0]); probe.remove();
    var reps = Math.ceil(ctx.W * 2.6 / per) + 1, s = '';
    for (var i = 0; i < reps; i++) s += unit;
    var L = jzText(ctx, s, { font: o.font, size: o.size, color: o.color, x: ctx.W / 2, y: o.y, track: o.track || 0.05, fill: o.fill, stroke: o.stroke, strokeColor: o.strokeColor, opacity: o.opacity, sx: o.sx });
    jzSetExpr(jzXf(L, 'ADBE Position'), 'var per=' + jzN(per * (o.sx || 1)) + ',sp=' + jzN(o.speed) + ',of=' + jzN(o.offset || 0) + ';var d=((time*sp+of)%per+per)%per-per/2;[value[0]+d,value[1]]');
    return L;
}

JZ_LAYOUTS.marquee = function (ctx) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, font = jzFontOf(ctx, 'font', 'display'), sx = jzP(ctx, 'sx', 1.4), style = jzP(ctx, 'rowStyle', 'outline'), rowsN = jzP(ctx, 'rows', 2), speed = jzP(ctx, 'speed', 0.8);
    var L = jzMain(ctx, c.text, { font: font, size: 200, color: sc.fg, x: W / 2, y: H / 2, track: 0.05, maxW: W * 0.84 / sx, maxH: H * 0.3, maxSize: H * 0.3, sx: sx });
    var size = jzFontSize(L), rs = size * 0.42, ks = rowsN === 2 ? [-1, 1] : [-2, -1, 1, 2];
    for (var r = 0; r < ks.length; r++) {
        var k = ks[r], y = H / 2 + (k > 0 ? 1 : -1) * (size * 0.5 + rs * 0.95) + (Math.abs(k) - 1) * (k > 0 ? 1 : -1) * rs * 1.25;
        var o = { font: font, size: rs, color: sc.fg, y: y, speed: (r % 2 ? 1 : -1) * speed * W * 0.22, offset: r * 137, sx: sx };
        if (style === 'outline') { o.fill = false; o.stroke = Math.max(1.2, rs * 0.02); o.strokeColor = sc.fg; }
        else if (style === 'dim') { o.color = sc.sub; o.opacity = 0.35; }
        else {
            var B = jzNoGhost(jzShapeLayer(ctx, 'row band', W / 2, y)); var g = jzGrp(B); jzAddRect(g, W + 40, rs * 1.24); jzAddFill(g, sc.ink); B.moveToEnd();
            o.color = sc.bg;
        }
        var R = jzNoGhost(jzScrollRow(ctx, c.text, o));
        jzFadeIO(ctx, R, Math.abs(k) * 0.05, 0.12);
    }
    return jzBB(L);
};

JZ_LAYOUTS.tile = function (ctx) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, n = jzP(ctx, 'rowsN', 14), unit = jzP(ctx, 'unit', 'chunk') === 'line' ? c.lineText : c.text;
    var tf = jzFontOf(ctx, 'tileFont', 'serif'), rowH = H / n, ts = rowH * 0.72;
    for (var r = 0; r <= n; r++) {
        var R = jzNoGhost(jzScrollRow(ctx, unit, { font: tf, size: ts, color: sc.sub, y: (r + 0.5) * rowH, speed: (r % 2 ? 26 : -26), offset: r * 91, track: 0.02, opacity: 0.42 }));
        jzFadeIO(ctx, R, jzR(c.seed, r, 91) * c.inDur * 1.6, 0.1);
    }
    var font = jzFontOf(ctx, 'font', 'display');
    var o = { font: font, size: 200, color: sc.fg, x: W / 2, y: H / 2, track: 0.04, maxW: W * 0.8, maxH: H * 0.34, maxSize: H * 0.3 };
    if (jzP(ctx, 'knock', 'stroke') === 'box') {
        var M = jzMain(ctx, c.text, o), bb = jzBB(M), pad = jzFontSize(M) * 0.35;
        var B = jzNoGhost(jzShapeLayer(ctx, 'knockout box', W / 2, H / 2)); var g = jzGrp(B); jzAddRect(g, bb.x1 - bb.x0 + pad * 2, bb.y1 - bb.y0 + pad * 1.6); jzAddFill(g, sc.bg);
        B.moveAfter(M); jzSetExpr(jzXf(B, 'ADBE Scale'), JZ_FNS + 'var e=oe(time/0.4);[value[0]*e,value[1]]');
        return bb;
    }
    var K = jzText(ctx, c.text, { font: font, size: 200, color: sc.bg, x: W / 2, y: H / 2, track: 0.04, maxW: W * 0.8, maxH: H * 0.34, maxSize: H * 0.3, stroke: 1, strokeColor: sc.bg });
    var ks = jzFontSize(K), src = K.property('ADBE Text Properties').property('ADBE Text Document'), td = src.value; td.strokeWidth = ks * 0.16; src.setValue(td);
    jzMotion(ctx, K, { size: ks, mi: 0 }); jzNoGhost(K);   // knock-out stroke (a helper, ghost: false in the browser)
    var M2 = jzMain(ctx, c.text, o);
    return jzBB(M2);
};

JZ_LAYOUTS.scatter = function (ctx) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, s = c.seed, chars = jzChars(c.text.replace(/[\s\u3000]+/g, '')), n = chars.length, bb = null, i;
    if (jzP(ctx, 'extras', true)) for (i = 0; i < 8; i++) {
        var top = jzR(s, i, 82) < 0.5;
        var E = jzSmall(ctx, c.text, { size: jzLerp(H * 0.022, H * 0.045, jzR(s, i, 83)), color: sc.sub, x: jzLerp(W * 0.08, W * 0.92, jzR(s, i, 84)), y: top ? jzLerp(H * 0.08, H * 0.26, jzR(s, i, 85)) : jzLerp(H * 0.74, H * 0.92, jzR(s, i, 85)), rot: (jzR(s, i, 86) * 2 - 1) * 18, opacity: 0.75 });
        jzFadeIO(ctx, E, jzR(s, i, 81) * c.dur * 0.5, 0.1); jzNoGhost(E);
    }
    var base = Math.min(H * 0.3, W * 0.9 / n * 1.15), fA = jzFontOf(ctx, 'font', 'display'), fB = jzFontOf(ctx, 'fontB', 'serif');
    for (i = 0; i < n; i++) {
        var k = 0.62 + jzR(s, i, 73) * 0.85 * (jzIsKanji(chars[i]) ? 1 : 0.7);
        var L = jzMain(ctx, chars[i], { font: i % 3 === 1 ? fB : fA, size: base * k, color: jzR(s, i, 75) < 0.15 ? sc.accent : sc.fg, x: W * (0.1 + 0.8 * (i + 0.5) / n) + (jzR(s, i, 71) * 2 - 1) * W * 0.035, y: H / 2 + (jzR(s, i, 72) * 2 - 1) * H * 0.18, rot: (jzR(s, i, 74) * 2 - 1) * 24, mi: i });
        bb = jzUnion(bb, jzBB(L));
    }
    return bb;
};

function jzCircleShape(cx, cy, R) {
    var k = 0.5523 * R, sh = new Shape();
    sh.vertices = [[cx, cy - R], [cx + R, cy], [cx, cy + R], [cx - R, cy]];
    sh.inTangents = [[-k, 0], [0, -k], [k, 0], [0, k]];
    sh.outTangents = [[k, 0], [0, k], [-k, 0], [0, -k]];
    sh.closed = true; return sh;
}
function jzTextOnPath(L, shape, firstMarginExpr, ctx) {
    // anchor == position == comp centre, so layer space equals comp space and scaling happens about the centre
    L.property('ADBE Transform Group').property('ADBE Anchor Point').setValue([ctx.W / 2, ctx.H / 2]);
    L.property('ADBE Transform Group').property('ADBE Position').setValue([ctx.W / 2, ctx.H / 2]);
    var m = L.property('ADBE Mask Parade').addProperty('ADBE Mask Atom');
    m.property('ADBE Mask Shape').setValue(shape);
    try { m.maskMode = MaskMode.NONE; } catch (e) {}
    var po = L.property('ADBE Text Properties').property('ADBE Text Path Options');
    try { po.property('ADBE Text Path').setValue(1); } catch (e1) { jzWarn('text path: ' + e1.toString()); }
    try { po.property('ADBE Text Perpendicular To Path').setValue(1); } catch (e2) {}
    if (firstMarginExpr) { try { L.property('ADBE Text Properties').property('ADBE Text Path Options').property('ADBE Text First Margin').expression = firstMarginExpr; } catch (e3) { jzWarn('first margin: ' + e3.toString()); } }
}

JZ_LAYOUTS.ring = function (ctx) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, text = c.text.replace(/[\s\u3000]+/g, ''), n = jzCount(text), R = H * jzP(ctx, 'R', 0.31), cx = W / 2, cy = H / 2;
    var font = jzFontOf(ctx, 'font', 'display'), fontC = jzFontOf(ctx, 'fontC', 'display'), center = jzP(ctx, 'center', 'word'), speed = jzP(ctx, 'speed', 8);
    var ringS = jzNoGhost(jzShapeLayer(ctx, 'ring lines', cx, cy)), g = jzGrp(ringS); jzAddEllipse(g, R * 1.72, R * 1.72); jzAddStroke(g, sc.sub, 1.2, 55);
    var g2 = jzGrp(ringS); jzAddEllipse(g2, R * 2.3, R * 2.3); jzAddStroke(g2, sc.sub, 1.2, 35); ringS.moveToEnd();
    var bb = null;
    if (center === 'disc') {
        var D = jzShapeLayer(ctx, 'disc', cx, cy), gd = jzGrp(D); jzAddEllipse(gd, R * 1.44, R * 1.44); jzAddFill(gd, sc.accent); jzPop(ctx, D, 0, true);
        bb = jzBB(jzMain(ctx, text, { font: fontC, size: 200, color: sc.bg, x: cx, y: cy, maxW: R * 1.15, maxH: R * 0.8, maxSize: H * 0.2 }));
    } else if (center === 'word') {
        bb = jzBB(jzMain(ctx, text, { font: fontC, size: 200, color: sc.fg, x: cx, y: cy, maxW: R * 1.3, maxH: R * 0.85, maxSize: H * 0.22 }));
    }
    var sizeRing = Math.min(H * 0.07, 2 * Math.PI * R / ((n + 1) * 1.25));
    var unit = text + '\u30FB', probe = jzText(ctx, unit, { font: font, size: sizeRing, x: -9999, y: -9999, color: sc.fg });
    var uw = Math.max(10, jzSize(probe)[0]); probe.remove();
    var reps = Math.max(1, Math.floor(2 * Math.PI * R / uw)), s = '';
    for (var i = 0; i < reps; i++) s += unit;
    var T = jzText(ctx, s, { font: font, size: sizeRing, color: sc.fg, x: 0, y: 0, align: 'left' });
    jzTextOnPath(T, jzCircleShape(cx, cy, R), 'time*' + jzN(speed * R * Math.PI / 180), ctx);
    var okIn = { pop: 1, spin: 1, flicker: 1, scramble: 1, type: 1, blur: 1, drop: 1 };
    jzMotion(ctx, T, { size: sizeRing, mi: 0, noHold: true, enter: okIn[c.enter] ? c.enter : 'pop', exit: (c.exit === 'stretch' || c.exit === 'slice' || c.exit === 'wipe') ? 'blur' : c.exit });
    return bb || { x0: cx - R, x1: cx + R, y0: cy - R, y1: cy + R, cx: cx, cy: cy };
};

JZ_LAYOUTS.wave = function (ctx) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, text = c.text.replace(/[\s\u3000]+/g, ''), n = jzCount(text);
    var amp = H * jzP(ctx, 'amp', 0.12), freq = jzP(ctx, 'freq', 1.2), size = Math.min(H * 0.2, W * 0.72 / n), travel = jzP(ctx, 'travel', 0.35);
    var sh = new Shape(), pts = [], seg = 24, i;
    for (i = 0; i <= seg; i++) { var u = i / seg; pts.push([jzLerp(-W * 0.1, W * 1.1, u), H / 2 + amp * Math.sin(Math.PI * 2 * freq * u)]); }
    sh.vertices = pts; sh.closed = false;
    var T = jzText(ctx, text, { font: jzFontOf(ctx, 'font', 'display'), size: size, color: sc.fg, x: 0, y: 0, align: 'left' });
    var pathLen = W * 1.2 * (1 + amp / W * 2);
    var tw = jzSize(T)[0];
    jzTextOnPath(T, sh, 'var u=time/' + jzN(c.dur) + ';' + jzN(pathLen / 2 - tw / 2) + '+(u-0.5)*' + jzN(-travel * W * (travel < 0 ? -1 : 1)), ctx);
    var ec = jzEffect(T, 'ADBE Echo', 'JZ Trails');
    jzEP(ec, 1, -0.033); jzEP(ec, 2, jzP(ctx, 'trail', 7)); jzEP(ec, 3, 1); jzEP(ec, 4, 0.72); jzEP(ec, 5, 5);
    var okW = { pop: 1, spin: 1, flicker: 1, scramble: 1, type: 1, blur: 1, drop: 1, assemble: 1 };
    jzMotion(ctx, T, { size: size, mi: 0, noHold: true, enter: okW[c.enter] ? c.enter : 'pop', exit: (c.exit === 'stretch' || c.exit === 'shrink') ? 'blur' : c.exit });
    return { x0: W * 0.2, x1: W * 0.8, y0: H / 2 - amp - size / 2, y1: H / 2 + amp + size / 2, cx: W / 2, cy: H / 2 };
};

JZ_LAYOUTS.huge = function (ctx) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, t0 = c.text.replace(/[\s\u3000]+/g, ''), n = jzCount(t0);
    var text = n >= 5 ? jzSplitLines(t0, Math.ceil(n / 2)) : t0, two = n >= 5;
    var size = two ? Math.min(H * 0.56, W * 1.2 / (Math.ceil(n / 2) * 0.98)) : Math.min(H * 0.98, W * 1.3 / (n * 0.96));
    var L = jzMain(ctx, text, { font: jzFontOf(ctx, 'font', 'display'), size: size, color: sc.fg, x: W / 2, y: H / 2 + H * 0.02, track: -0.02, leading: two ? size * 0.98 : null });
    var dir = jzP(ctx, 'dir', 1);
    jzSetExpr(jzXf(L, 'ADBE Anchor Point'), '[value[0]-(0.5-time/' + jzN(c.dur) + ')*thisComp.width*0.16*' + dir + ',value[1]]');
    if (sc.grad && jzP(ctx, 'grad', false)) {
        var gr = jzEffect(L, 'ADBE Ramp', 'JZ Gradient');
        jzEP(gr, 1, [W / 2, H / 2 - size / 2]); jzEP(gr, 2, jzHex(sc.grad[0])); jzEP(gr, 3, [W / 2, H / 2 + size / 2]); jzEP(gr, 4, jzHex(sc.grad[1]));
    }
    if (jzP(ctx, 'label', true)) {
        var ls = Math.max(16, H * 0.028);
        var T = jzSmall(ctx, c.text, { size: ls, color: sc.bg, x: W * 0.05 + ls * 0.7, y: H * 0.86, align: 'left', track: 0.12 });
        var tw = jzSize(T)[0];
        var B = jzShapeLayer(ctx, 'label box', W * 0.05 + (tw + ls * 1.4) / 2, H * 0.86), g = jzGrp(B); jzAddRect(g, tw + ls * 1.4, ls * 2); jzAddFill(g, sc.ink);
        B.moveAfter(T); jzFadeIO(ctx, B, c.inDur * 0.5, 0.2); jzFadeIO(ctx, T, c.inDur * 0.5, 0.2); jzNoGhost(B); jzNoGhost(T);
    }
    return jzBB(L);
};

JZ_LAYOUTS.labels = function (ctx) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, s = c.seed, text = c.text.replace(/[\s\u3000]+/g, ''), variant = jzP(ctx, 'variant', 'radial');
    var units = [], i, ch = jzChars(text);
    if (jzP(ctx, 'unit', 'char') === 'char' || !(c.words && c.words.length)) { for (i = 0; i < ch.length; i++) if (!jzIsPunct(ch[i])) units.push(ch[i]); }
    else units = c.words;
    if (!units.length) units = [text];
    var font = jzFontOf(ctx, 'font', 'display');
    function label(u, x, y, rot, fs, delay) {
        var T = jzText(ctx, u, { font: font, size: fs, color: sc.bg, x: x, y: y, track: 0.04 });
        var sz = jzSize(T), B = jzShapeLayer(ctx, 'label', x, y), g = jzGrp(B);
        jzAddRect(g, sz[0] + fs * 0.7, fs * 1.36); jzAddFill(g, sc.ink);
        jzXf(B, 'ADBE Rotate Z').setValue(rot); B.moveAfter(T);
        T.parent = B; jzPop(ctx, B, delay, true); jzNoGhost(T);
        return B;
    }
    if (variant === 'radial') {
        var m = Math.max(units.length, 10), R = H * 0.3, fs = H * 0.062;
        if (jzP(ctx, 'center', 'orb') === 'orb') { var O = jzShapeLayer(ctx, 'orb', W / 2, H / 2), go = jzGrp(O); jzAddEllipse(go, R * 1.04, R * 1.04); jzAddFill(go, sc.accent); jzPop(ctx, O, 0, true); O.moveToEnd(); }
        for (i = 0; i < m; i++) { var a = i / m * 360 - 90; label(units[i % units.length], W / 2 + Math.cos(a * Math.PI / 180) * R, H / 2 + Math.sin(a * Math.PI / 180) * R, a, fs, i * 0.025); }
        if (jzP(ctx, 'center', 'orb') === 'word') jzMain(ctx, text, { font: jzFontOf(ctx, 'fontC', 'display'), size: 200, color: sc.fg, x: W / 2, y: H / 2, maxW: R * 1.1, maxH: R * 0.7, maxSize: H * 0.18 });
        return { x0: W / 2 - R, x1: W / 2 + R, y0: H / 2 - R, y1: H / 2 + R, cx: W / 2, cy: H / 2 };
    }
    if (variant === 'rows') {
        var k = units.length, fr = Math.min(H * 0.14, H * 0.7 / (k * 1.5));
        for (i = 0; i < k; i++) label(units[i], W / 2 + (jzR(s, i, 5) * 2 - 1) * W * 0.12, H / 2 + (i - (k - 1) / 2) * fr * 1.55, (jzR(s, i, 6) * 2 - 1) * 4, fr, i * 0.05);
        return { x0: W * 0.3, x1: W * 0.7, y0: H / 2 - k * fr * 0.8, y1: H / 2 + k * fr * 0.8, cx: W / 2, cy: H / 2 };
    }
    var fsc = H * 0.085;
    for (i = 0; i < units.length; i++) label(units[i], W * (0.15 + 0.7 * ((i + 0.5) / units.length)) + (jzR(s, i, 7) * 2 - 1) * W * 0.04, H / 2 + (jzR(s, i, 8) * 2 - 1) * H * 0.25, (jzR(s, i, 9) * 2 - 1) * 22, fsc * (0.8 + jzR(s, i, 10) * 0.5), i * 0.05);
    return { x0: W * 0.15, x1: W * 0.85, y0: H * 0.3, y1: H * 0.7, cx: W / 2, cy: H / 2 };
};

JZ_LAYOUTS.condensed = function (ctx) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, text = c.text.replace(/[\s\u3000]+/g, ''), count = jzP(ctx, 'count', 2), sx = jzP(ctx, 'sx', 0.5), sy = jzP(ctx, 'sy', 1.2);
    var slot = W * 0.92 / count, bb = null, order = [1, 0, 2, 3];
    for (var i = 0; i < count; i++) {
        var L = jzMain(ctx, text, { font: jzFontOf(ctx, 'font', 'display'), size: 200, color: sc.fg, x: W / 2 + (i - (count - 1) / 2) * slot, y: H / 2, track: 0.04, maxW: slot * 0.94 / sx, maxH: H * 0.8 / sy, maxSize: H * 0.62, sx: sx, sy: sy, mi: count > 1 ? order[i] * 2 : 0 });
        bb = jzUnion(bb, jzBB(L));
    }
    return bb;
};

JZ_LAYOUTS.gloss = function (ctx) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, text = c.text, font = jzFontOf(ctx, 'font', 'serif'), right = jzP(ctx, 'side', 'right') === 'right', r;
    if (jzP(ctx, 'bgText', true)) for (r = 0; r < 3; r++) jzNoGhost(jzScrollRow(ctx, text.replace(/[\s\u3000]+/g, ''), { font: font, size: H * 0.3, color: sc.dim, y: H * (0.18 + r * 0.32), speed: r % 2 ? 20 : -20, offset: r * 200 })).moveToEnd();
    var L = jzMain(ctx, text, { font: font, size: 200, color: sc.fg, x: right ? W * 0.4 : W * 0.6, y: H * 0.54, track: 0.03, maxW: W * 0.5, maxH: H * 0.3, maxSize: H * 0.24 });
    var bb = jzBB(L), size = jzFontSize(L);
    var ax = right ? bb.x1 + size * 0.1 : bb.x0 - size * 0.1, ay = bb.y0 + size * 0.2;
    var nx = right ? Math.min(W * 0.9, bb.x1 + W * 0.1) : Math.max(W * 0.1, bb.x0 - W * 0.1), ny = Math.max(H * 0.12, bb.y0 - H * 0.12);
    var S = jzNoGhost(jzShapeLayer(ctx, 'leader', 0, 0)), g = jzGrp(S);
    jzAddPath(g, [[ax, ay], [jzLerp(ax, nx, 0.45), ay], [nx, ny]], false); jzAddStroke(g, sc.sub, 1.3);
    jzAddTrimPaths(g, JZ_FNS + '100*oe((time-' + jzN(c.inDur * 0.4) + ')/0.45)');
    var gd = jzGrp(S); jzAddEllipse(gd, 8, 8, ax, ay); jzAddFill(gd, sc.accent);
    var ns = Math.max(14, H * 0.024), al = right ? 'left' : 'right', body = jzFontOf(ctx, '_', 'body');
    var t1 = jzText(ctx, '\u3010' + text.replace(/[\s\u3000]+/g, '') + '\u3011', { font: font, size: ns * 1.2, color: sc.fg, x: nx, y: ny - ns * 1.2, align: al });
    var note = c.note || jzRomaji(text.replace(/[\s\u3000]+/g, '')) || c.lineText;
    var t2 = jzText(ctx, note, { font: body, size: ns, color: sc.sub, x: nx, y: ny + ns * 0.4, align: al, track: 0.08 });
    var t3 = jzText(ctx, 'No.' + jzPad((c.line || 0) + 1, 2), { font: 'mono', size: ns * 0.8, color: sc.accent, x: nx, y: ny + ns * 2, align: al });
    jzFadeIO(ctx, t1, c.inDur * 0.5, 0.3); jzFadeIO(ctx, t2, c.inDur * 0.6, 0.3); jzFadeIO(ctx, t3, c.inDur * 0.7, 0.3);
    jzNoGhost(t1); jzNoGhost(t2); jzNoGhost(t3);
    return bb;
};

JZ_LAYOUTS.type = function (ctx) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, left = jzP(ctx, 'align', 'left') === 'left';
    var text = jzSplitLines(c.text, 14), x = left ? W * 0.13 : W / 2;
    var L = jzText(ctx, text, { font: jzFontOf(ctx, 'font', 'body'), size: 200, color: sc.fg, x: x, y: H / 2, align: left ? 'left' : 'center', track: 0.06, maxW: W * 0.74, maxH: H * 0.36, maxSize: H * 0.11 });
    jzMotion(ctx, L, { size: jzFontSize(L), mi: 0, enter: c.enter === 'cut' ? 'type' : c.enter });
    var bb = jzBB(L), fs = jzFontSize(L);
    if (jzP(ctx, 'prompt', true)) { var p = jzText(ctx, '>', { font: 'mono', size: fs * 0.8, color: sc.accent, x: (left ? x : bb.x0) - fs * 0.9, y: bb.y0 + fs * 0.55 }); jzFadeIO(ctx, p, 0, 0.05); jzNoGhost(p); }
    var st = jzNoGhost(jzText(ctx, 'LINE ' + jzPad((c.line || 0) + 1, 2), { font: 'mono', size: Math.max(12, H * 0.02), color: sc.sub, x: W * 0.13, y: H * 0.8, align: 'left', opacity: 0.8 }));
    try { st.property('ADBE Text Properties').property('ADBE Text Document').expression = '"LINE ' + jzPad((c.line || 0) + 1, 2) + ' \u2500 " + timeToTimecode(time + ' + jzN(c.start) + ')'; } catch (e) {}
    return bb;
};

JZ_LAYOUTS.diag = function (ctx) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, ang = jzP(ctx, 'ang', 16), bandCol = jzP(ctx, 'band', 'accent') === 'accent' ? sc.accent : sc.ink;
    var txtCol = jzLum(bandCol) > 0.5 ? (jzLum(sc.bg) < 0.5 ? sc.bg : '#111111') : (jzLum(sc.fg) > 0.5 ? sc.fg : '#FFFFFF');
    var L = jzText(ctx, c.text, { font: jzFontOf(ctx, 'font', 'display'), size: 200, color: txtCol, x: W / 2, y: H / 2, track: 0.05, maxW: W * 0.72, maxH: H * 0.24, maxSize: H * 0.2, rot: -ang });
    var size = jzFontSize(L), bh = size * 1.6;
    var B = jzShapeLayer(ctx, 'band', W / 2, H / 2), g = jzGrp(B); jzAddRect(g, W * 2.4, bh); jzAddFill(g, bandCol);
    jzXf(B, 'ADBE Rotate Z').setValue(-ang); B.moveAfter(L);
    jzSetExpr(jzXf(B, 'ADBE Scale'), JZ_FNS + 'var e=oe(time/' + jzN(Math.max(0.1, c.inDur * 0.8)) + ')*(1-ie((time-' + jzN(c.dur - (c.outDur || 0)) + ')/' + jzN(Math.max(0.05, c.outDur || 0.05)) + '));[value[0],value[1]*e]');
    if (jzP(ctx, 'second', true)) {
        var y2 = bh * 0.95, h2 = bh * 0.32, rad = -ang * Math.PI / 180;
        var ox = -Math.sin(rad) * y2, oy = Math.cos(rad) * y2;
        var B2 = jzNoGhost(jzShapeLayer(ctx, 'band 2', W / 2 + ox, H / 2 + oy)), g2 = jzGrp(B2); jzAddRect(g2, W * 2.4, h2); jzAddFill(g2, sc.fg, 90);
        jzXf(B2, 'ADBE Rotate Z').setValue(-ang); B2.moveAfter(B);
        var R2 = jzScrollRow(ctx, c.lineText + '\u3000\uFF0F', { font: jzFontOf(ctx, '_', 'body'), size: h2 * 0.55, color: sc.bg, y: 0, speed: 120, track: 0.1 });
        R2.parent = B2; jzXf(R2, 'ADBE Rotate Z').setValue(0); jzNoGhost(R2);
        jzXf(R2, 'ADBE Position').setValue([0, 0]);
    }
    jzMotion(ctx, L, { size: size, mi: 0 });
    return jzBB(L);
};

JZ_LAYOUTS.circle = function (ctx) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, text = c.text.replace(/[\s\u3000]+/g, ''), variant = jzP(ctx, 'variant', 'disc'), font = jzFontOf(ctx, 'font', 'display');
    var cx = W / 2 + jzP(ctx, 'off', 0) * W, cy = H / 2;
    if (variant === 'eclipse') {
        var L = jzMain(ctx, jzSplitLines(text, 6), { font: font, size: 200, color: sc.fg, x: W / 2, y: H / 2, maxW: W * 0.82, maxH: H * 0.46, maxSize: H * 0.36 });
        var R = H * 0.19, E = jzNoGhost(jzShapeLayer(ctx, 'eclipse', W / 2, H / 2 + H * 0.12)), g = jzGrp(E);
        jzAddEllipse(g, R * 2, R * 2); jzAddStroke(g, sc.fg, 3, 90); jzAddFill(g, '#000000');
        var gl = jzEffect(E, 'ADBE Glo2', 'JZ Corona'); jzEP(gl, 2, 20); jzEP(gl, 3, 40); jzEP(gl, 4, 1.4);
        jzSetExpr(jzXf(E, 'ADBE Position'), '[value[0]+(time/' + jzN(c.dur) + '-0.5)*thisComp.width*0.16,value[1]]');
        return jzBB(L);
    }
    var Rr = H * 0.3, D = jzShapeLayer(ctx, 'circle', cx, cy), gd = jzGrp(D);
    jzAddEllipse(gd, Rr * 2, Rr * 2);
    if (variant === 'disc') { jzAddFill(gd, sc.accent); jzPop(ctx, D, 0, true); }
    else { jzAddStroke(gd, sc.fg, 3); jzAddTrimPaths(gd, JZ_FNS + '100*oe(time/' + jzN(Math.max(0.2, c.inDur * 1.3)) + ')'); }
    var vert = !!jzP(ctx, 'vertical', false);
    var T = jzMain(ctx, vert ? jzVertical(text) : text, { font: font, size: 200, color: variant === 'disc' ? sc.bg : sc.fg, x: cx, y: cy, maxW: vert ? Rr * 1.1 : Rr * 1.45, maxH: vert ? Rr * 1.35 : Rr * 0.9, maxSize: vert ? Rr * 0.9 : Rr * 0.8 });
    return jzBB(T);
};

JZ_LAYOUTS.stack = function (ctx) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, n = jzP(ctx, 'copies', 4), dir = jzP(ctx, 'dir', 1), gap = jzP(ctx, 'gap', 0.92), style = jzP(ctx, 'style', 'fade'), xs = jzP(ctx, 'xs', 0);
    var probe = jzText(ctx, c.text, { font: jzFontOf(ctx, 'font', 'display'), size: 200, color: sc.fg, x: -9999, y: -9999, track: 0.03, maxW: W * 0.8, maxH: H * 0.22, maxSize: H * 0.19 });
    var size = jzFontSize(probe); probe.remove();
    var step = size * gap, y0 = H / 2 - dir * (n - 1) * step / 2, bb = null;
    for (var k = n - 1; k >= 0; k--) {
        var o = { font: jzFontOf(ctx, 'font', 'display'), size: size, color: sc.fg, x: W / 2 + xs * W * k, y: y0 + dir * k * step, track: 0.03, mi: k * 1.2 };
        if (k > 0) { if (style === 'outline') { o.fill = false; o.stroke = Math.max(1.2, size * 0.014); o.strokeColor = sc.fg; o.opacity = 0.85; } else o.opacity = 0.6 * Math.pow(0.58, k - 1); }
        var L = jzMain(ctx, c.text, o); if (k === 0) bb = jzBB(L);
    }
    return bb;
};

JZ_LAYOUTS.pill = function (ctx) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut;
    var grad = jzP(ctx, 'grad', false) && sc.grad, fillA = grad ? sc.grad[0] : sc.accent, tc = jzLum(fillA) > 0.55 ? '#111111' : '#FFFFFF';
    var T = jzText(ctx, c.text, { font: jzFontOf(ctx, 'font', 'display'), size: 200, color: tc, x: W / 2, y: H / 2, track: 0.04, maxW: W * 0.62, maxH: H * 0.2, maxSize: H * 0.17 });
    var size = jzFontSize(T), sz = jzSize(T), w = sz[0] + size * 1.3, h = size * 1.6;
    var P = jzShapeLayer(ctx, 'capsule', W / 2, H / 2), g = jzGrp(P); jzAddRect(g, w, h, h / 2); jzAddFill(g, fillA);
    if (grad) { var gr = jzEffect(P, 'ADBE Ramp', 'JZ Gradient'); jzEP(gr, 1, [W / 2 - w / 2, H / 2]); jzEP(gr, 2, jzHex(sc.grad[0])); jzEP(gr, 3, [W / 2 + w / 2, H / 2]); jzEP(gr, 4, jzHex(sc.grad[1])); }
    P.moveAfter(T);
    jzSetExpr(jzXf(P, 'ADBE Scale'), JZ_FNS + 'var e=oe(time/' + jzN(Math.max(0.1, c.inDur * 0.9)) + ')*(1-ie((time-' + jzN(c.dur - (c.outDur || 0)) + ')/' + jzN(Math.max(0.05, c.outDur || 0.05)) + '));[value[0]*Math.max(' + jzN(h / w) + ',e),value[1]]');
    jzMotion(ctx, T, { size: size, mi: 0 });
    if (jzP(ctx, 'smalls', true)) {
        var labs = [jzRomaji(c.text.replace(/[\s\u3000]+/g, '')) || 'LYRIC', 'No.' + jzPad((c.line || 0) + 1, 2)], fs = Math.max(13, H * 0.022);
        for (var i = 0; i < labs.length; i++) {
            var px = W / 2 + (i === 0 ? -w * 0.3 : w * 0.42), py = H / 2 + (i === 1 ? -h * 0.95 : h * 0.95);
            var t = jzSmall(ctx, labs[i], { size: fs, color: sc.fg, x: px, y: py, track: 0.1 }), ts = jzSize(t);
            var B = jzShapeLayer(ctx, 'tag', px, py), gb = jzGrp(B); jzAddRect(gb, ts[0] + fs * 1.6, fs * 1.7, fs * 0.85); jzAddStroke(gb, sc.fg, 1.3);
            t.parent = B; jzPop(ctx, B, 0.15 + i * 0.06, true); jzNoGhost(B); jzNoGhost(t);
        }
    }
    return jzBB(T);
};

JZ_LAYOUTS.title = function (ctx) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut;
    var L = jzMain(ctx, c.text, { font: jzFontOf(ctx, 'font', 'display'), size: 200, color: sc.fg, x: W / 2, y: H / 2, track: 0.08, maxW: W * 0.7, maxH: H * 0.2, maxSize: H * 0.16 });
    if (c.note) { var a = jzSmall(ctx, c.note, { size: Math.max(16, H * 0.03), color: sc.sub, x: W / 2, y: H / 2 + jzFontSize(L) * 0.95, track: 0.3 }); jzFadeIO(ctx, a, 0.3, 0.4); jzNoGhost(a); }
    return jzBB(L);
};

JZ_LAYOUTS.interlude = function (ctx) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut;
    if (jzP(ctx, 'variant', 'counter') === 'quiet') {       // [\u9593\u594F]: background and decorations only (+ the title on long ones)
        if (jzP(ctx, 'showTitle', false) && jzP(ctx, 'titleText', '')) jzNoGhost(jzSmall(ctx, jzP(ctx, 'titleText', ''), { size: Math.max(12, H * 0.024), color: sc.sub, x: W / 2, y: H * 0.88, track: 0.3 }));
        return { x0: W * 0.3, x1: W * 0.7, y0: H * 0.3, y1: H * 0.7, cx: W / 2, cy: H / 2 };
    }
    var S = jzNoGhost(jzShapeLayer(ctx, 'rings', W / 2, H / 2));
    for (var k = 0; k < 3; k++) { var g = jzGrp(S); jzAddEllipse(g, H * (0.4 + k * 0.2), H * (0.4 + k * 0.2)); jzAddStroke(g, sc.sub, 1.2, 50); }
    jzSetExpr(jzXf(S, 'ADBE Scale'), 'var s=100*(1+0.04*Math.sin(time*2));[s,s]');
    if (jzP(ctx, 'variant', 'counter') === 'counter') {
        var n = jzText(ctx, '0.0', { font: jzFontOf(ctx, '_', 'display'), size: H * 0.36, color: sc.fg, x: W / 2, y: H / 2, opacity: 0.9 });
        try { n.property('ADBE Text Properties').property('ADBE Text Document').expression = 'Math.max(0,' + jzN(c.dur) + '-time).toFixed(1)'; } catch (e) {}
    }
    jzNoGhost(jzSmall(ctx, c.text || '\u2014 interlude \u2014', { size: Math.max(12, H * 0.022), color: sc.sub, x: W / 2, y: H * 0.82, track: 0.4 }));
    return { x0: W * 0.35, x1: W * 0.65, y0: H * 0.3, y1: H * 0.7, cx: W / 2, cy: H / 2 };
};

// ---- register the original layouts (their parameters come from jzParams in 15_plan.jsx)
(function () {
    for (var k in JZ_LAYOUTS) if (JZ_LAYOUTS.hasOwnProperty(k)) {
        jzReg('layout', k, { build: JZ_LAYOUTS[k], special: (k === 'title' || k === 'interlude'),
            plan: (function (kk) { return function (rng, cut, st) { return jzParams(kk, rng, st, cut.text); }; })(k) });
    }
})();

// ================================================================ decor (AE)
var JZ_DECOR = {};
var JZ_DECOR_BACK = { grid: 1, stripes: 1, blobs: 1, bars: 1, shapes: 1, counter: 1 };
// decor the browser draws with ghost off (grid, stripes, counter, rings, dots, chevrons, leaders, waveform, barcode) is kept
// out of the tinted ghosts with jzNoGhost; brackets, slash, sparks, blobs, bars and shapes are ghosted like in the browser
function jzIO(ctx, dur) { // opacity in/out expression for decor layers
    var c = ctx.cut;
    return JZ_FNS + 'value*oc(time/' + jzN(dur || 0.3) + ')*(1-ic((time-' + jzN(c.dur - Math.max(0.12, c.outDur || 0.15)) + ')/' + jzN(Math.max(0.12, c.outDur || 0.15)) + '))';
}

JZ_DECOR.brackets = function (ctx, bb, d) {
    var sc = ctx.sc, pad = 18 + (bb.y1 - bb.y0) * 0.12, x0 = bb.x0 - pad, x1 = bb.x1 + pad, y0 = bb.y0 - pad, y1 = bb.y1 + pad;
    var cx = (x0 + x1) / 2, cy = (y0 + y1) / 2, L = Math.min(x1 - x0, y1 - y0) * 0.16 + 8, col = d.accent ? sc.accent : sc.fg;
    var S = jzShapeLayer(ctx, 'brackets', cx, cy), g = jzGrp(S), hw = (x1 - x0) / 2, hh = (y1 - y0) / 2;
    jzAddPath(g, [[-hw, -hh + L], [-hw, -hh], [-hw + L, -hh]], false);
    jzAddPath(g, [[hw - L, -hh], [hw, -hh], [hw, -hh + L]], false);
    jzAddPath(g, [[-hw, hh - L], [-hw, hh], [-hw + L, hh]], false);
    jzAddPath(g, [[hw - L, hh], [hw, hh], [hw, hh - L]], false);
    jzAddStroke(g, col, 2.2);
    jzSetExpr(jzXf(S, 'ADBE Scale'), JZ_FNS + 'var e=oe(time/0.35)*(1-ic((time-' + jzN(ctx.cut.dur - Math.max(0.12, ctx.cut.outDur || 0.15)) + ')/0.15));[value[0]*e,value[1]*e]');
};
JZ_DECOR.rings = function (ctx, bb, d) {
    var sc = ctx.sc, cx = (bb.x0 + bb.x1) / 2, cy = (bb.y0 + bb.y1) / 2, R0 = Math.max(bb.x1 - bb.x0, bb.y1 - bb.y0) * 0.55 + ctx.H * 0.05;
    var S = jzNoGhost(jzShapeLayer(ctx, 'rings', cx, cy));
    for (var k = 0; k < (d.n || 2); k++) {
        var R = R0 * (1 + k * 0.28 + jzR(d.seed, k, 1) * 0.1), g = jzGrp(S);
        jzAddEllipse(g, R * 2, R * 2); jzAddStroke(g, sc.fg, 1.2, 70);
        jzAddTrimPaths(g, JZ_FNS + '100*oe(time/0.5)*' + jzN(0.55 + 0.45 * jzR(d.seed, k, 3)));
        jzGX(g).property('ADBE Vector Rotation').expression = 'time*' + (k % 2 ? -14 : 10) + '+' + Math.round(jzR(d.seed, k, 2) * 360);
        var a = (jzR(d.seed, k, 2) * 360 + 40) * Math.PI / 180, px = cx + Math.cos(a) * R, py = cy + Math.sin(a) * R;
        var lab = jzText(ctx, 'X' + Math.round(px) + ' Y' + Math.round(py), { font: 'mono', size: Math.max(10, ctx.H * 0.015), color: sc.sub, x: px + 10, y: py - 12, align: 'left' });
        jzSetExpr(jzXf(lab, 'ADBE Opacity'), jzIO(ctx, 0.5)); jzNoGhost(lab);
    }
    jzSetExpr(jzXf(S, 'ADBE Opacity'), jzIO(ctx, 0.3));
};
JZ_DECOR.dots = function (ctx, bb, d) {
    var sc = ctx.sc, cx = (bb.x0 + bb.x1) / 2, cy = (bb.y0 + bb.y1) / 2, R = Math.max(bb.x1 - bb.x0, bb.y1 - bb.y0) * 0.62 + ctx.H * 0.04;
    var S = jzNoGhost(jzShapeLayer(ctx, 'dot ring', cx, cy)), g = jzGrp(S);
    jzAddEllipse(g, 5, 5, R, 0); jzAddFill(g, sc.fg);
    var rp = jzVecs(g).addProperty('ADBE Vector Filter - Repeater');
    rp.property('ADBE Vector Repeater Copies').setValue(36);
    var rt = jzVecs(g).property('ADBE Vector Filter - Repeater').property('ADBE Vector Repeater Transform');
    rt.property('ADBE Vector Repeater Position').setValue([0, 0]);
    rt.property('ADBE Vector Repeater Rotation').setValue(10);
    jzSetExpr(jzXf(S, 'ADBE Rotate Z'), 'time*20');
    jzSetExpr(jzXf(S, 'ADBE Opacity'), jzIO(ctx, 0.3));
};
JZ_DECOR.arrows = function (ctx, bb, d) {
    var sc = ctx.sc, cy = (bb.y0 + bb.y1) / 2, s = Math.max(14, ctx.H * 0.03), gap = s * 0.9;
    for (var side = -1; side <= 1; side += 2) {
        var xEdge = side < 0 ? bb.x0 - s * 1.2 : bb.x1 + s * 1.2, dir = -side;
        var S = jzNoGhost(jzShapeLayer(ctx, 'chevrons', xEdge, cy));
        for (var i = 0; i < 3; i++) {
            var g = jzGrp(S), x = side * i * gap;
            jzAddPath(g, [[x - dir * s * 0.35, -s * 0.5], [x + dir * s * 0.35, 0], [x - dir * s * 0.35, s * 0.5]], false);
            jzAddStroke(g, i === 0 ? sc.accent : sc.fg, Math.max(2, s * 0.14));
            jzGX(g).property('ADBE Vector Group Opacity').expression = 'posterizeTime(12);(Math.floor(time*12)+' + i + ')%3===0?30:100';
        }
        jzSetExpr(jzXf(S, 'ADBE Position'), JZ_FNS + '[value[0]+' + side + '*(1-oe(time/0.4))*thisComp.width*0.2,value[1]]');
        jzSetExpr(jzXf(S, 'ADBE Opacity'), jzIO(ctx, 0.2));
    }
};
JZ_DECOR.slash = function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc;
    for (var k = 0; k < (d.n || 1); k++) {
        var ang = jzLerp(-70, -20, jzR(d.seed, k, 1)) * Math.PI / 180, cx = jzLerp(W * 0.3, W * 0.7, jzR(d.seed, k, 2)), cy = jzLerp(H * 0.3, H * 0.7, jzR(d.seed, k, 3)), L = Math.sqrt(W * W + H * H);
        var S = jzShapeLayer(ctx, 'slash', 0, 0), g = jzGrp(S);
        jzAddPath(g, [[cx - Math.cos(ang) * L / 2, cy - Math.sin(ang) * L / 2], [cx + Math.cos(ang) * L / 2, cy + Math.sin(ang) * L / 2]], false);
        jzAddStroke(g, k ? sc.accent : sc.fg, k ? 2 : 1.4, 90);
        jzAddTrimPaths(g, JZ_FNS + '100*oe((time-' + jzN(k * 0.06) + ')/0.35)', JZ_FNS + '100*ic((time-' + jzN(ctx.cut.dur - Math.max(0.12, ctx.cut.outDur || 0.15)) + ')/0.15)');
    }
};
JZ_DECOR.sparks = function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc;
    for (var k = 0; k < (d.n || 6); k++) {
        var x = jzLerp(W * 0.05, W * 0.95, jzR(d.seed, k, 2)), y = jzLerp(H * 0.08, H * 0.92, jzR(d.seed, k, 3)), r = jzLerp(H * 0.015, H * 0.04, jzR(d.seed, k, 4));
        var S = jzShapeLayer(ctx, 'spark', x, y), g = jzGrp(S), arms = jzR(d.seed, k, 7) < 0.5 ? 3 : 4;
        for (var a = 0; a < arms; a++) { var an = a * Math.PI / arms; jzAddPath(g, [[-Math.cos(an) * r, -Math.sin(an) * r], [Math.cos(an) * r, Math.sin(an) * r]], false); }
        jzAddStroke(g, k % 3 === 0 ? sc.accent : sc.fg, Math.max(1.5, r * 0.14));
        jzSetExpr(jzXf(S, 'ADBE Rotate Z'), 'time*' + Math.round((jzR(d.seed, k, 5) * 2 - 1) * 170));
        jzPop(ctx, S, jzR(d.seed, k, 1) * 0.4, true);
    }
};
JZ_DECOR.leaders = function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, fs = Math.max(11, H * 0.018);
    var labels = [jzRomaji(c.text.replace(/[\s\u3000]+/g, '')) || c.lineText, 'No.' + jzPad((c.line || 0) + 1, 2)];
    var anchors = [[bb.x1, bb.y0], [bb.x0, bb.y1]];
    for (var k = 0; k < 2; k++) {
        var ax = anchors[k][0], ay = anchors[k][1], sgn = k === 1 ? -1 : 1;
        var tx = jzClamp(ax + sgn * W * jzLerp(0.06, 0.14, jzR(d.seed, k, 1)), W * 0.06, W * 0.94), ty = jzClamp(ay + (k === 0 ? -1 : 1) * H * jzLerp(0.08, 0.16, jzR(d.seed, k, 2)), H * 0.08, H * 0.92);
        var S = jzNoGhost(jzShapeLayer(ctx, 'leader', 0, 0)), g = jzGrp(S);
        jzAddPath(g, [[ax, ay], [tx, ty], [tx + sgn * W * 0.05, ty]], false); jzAddStroke(g, sc.sub, 1.2);
        jzAddTrimPaths(g, JZ_FNS + '100*oe((time-0.1)/0.45)');
        var gd = jzGrp(S); jzAddEllipse(gd, 7, 7, ax, ay); jzAddFill(gd, sc.accent);
        var t = jzText(ctx, labels[k], { font: k === 0 ? 'gothic_med' : 'mono', size: fs, color: sc.fg, x: tx + sgn * W * 0.055, y: ty - fs * 0.9, align: k === 1 ? 'right' : 'left', track: 0.06 });
        jzSetExpr(jzXf(t, 'ADBE Opacity'), jzIO(ctx, 0.45)); jzNoGhost(t);
    }
};
JZ_DECOR.waveform = function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, y = H * (d.low ? 0.86 : 0.14), pts = [], n = 60;
    for (var i = 0; i <= n; i++) { var u = i / n, a = H * 0.03 * Math.sin(u * Math.PI) * (0.4 + jzR(d.seed, i)); pts.push([jzLerp(W * 0.18, W * 0.82, u), y + (i % 2 ? a : -a)]); }
    var S = jzNoGhost(jzShapeLayer(ctx, 'waveform', 0, 0)), g = jzGrp(S); jzAddPath(g, pts, false); jzAddStroke(g, sc.fg, 1.4, 90);
    jzSetExpr(jzXf(S, 'ADBE Scale'), 'posterizeTime(12);seedRandom(Math.floor(time*12),true);[100,100*random(0.5,1.2)]');
    jzXf(S, 'ADBE Anchor Point').setValue([W / 2, y]); jzXf(S, 'ADBE Position').setValue([W / 2, y]);
    jzAddTrimPaths(g, JZ_FNS + '100*oe(time/0.4)');
};
JZ_DECOR.barcode = function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, x0 = d.right ? W * 0.84 : W * 0.06, y0 = d.low ? H * 0.84 : H * 0.07, h = H * 0.05;
    var S = jzNoGhost(jzShapeLayer(ctx, 'barcode', x0, y0)), g = jzGrp(S), x = 0;
    for (var i = 0; i < 34; i++) { var w = 1 + Math.floor(jzR(d.seed, i, 1) * 3.2); if (jzR(d.seed, i, 2) < 0.62) jzAddRect(g, w, h, 0, x + w / 2, h / 2); x += w + 1.5; }
    jzAddFill(g, sc.fg, 90);
    var t = jzText(ctx, jzPad(jzHash(d.seed, 5) % 1000000000, 9), { font: 'mono', size: Math.max(9, H * 0.014), color: sc.fg, x: x0, y: y0 + h + 12, align: 'left', track: 0.2 });
    jzSetExpr(jzXf(S, 'ADBE Opacity'), jzIO(ctx, 0.3)); jzSetExpr(jzXf(t, 'ADBE Opacity'), jzIO(ctx, 0.3)); jzNoGhost(t);
};
JZ_DECOR.grid = function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, gs = H / 8;
    var S = jzNoGhost(jzShapeLayer(ctx, 'grid', 0, 0)), g = jzGrp(S), x, y;
    for (x = (W / 2) % gs; x < W; x += gs) jzAddPath(g, [[x, 0], [x, H]], false);
    for (y = (H / 2) % gs; y < H; y += gs) jzAddPath(g, [[0, y], [W, y]], false);
    jzAddStroke(g, sc.sub, 1, 12);
    jzSetExpr(jzXf(S, 'ADBE Opacity'), jzIO(ctx, 0.3));
};
JZ_DECOR.stripes = function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, w = H * 0.04;
    var S = jzNoGhost(jzShapeLayer(ctx, 'stripes', d.corner ? W * 0.85 : W * 0.15, d.corner ? H * 0.15 : H * 0.85)), g = jzGrp(S);
    for (var i = -6; i <= 6; i++) jzAddRect(g, w, H * 0.36, 0, i * w * 2, 0);
    jzAddFill(g, d.accent ? sc.accent : sc.dim, 90);
    jzXf(S, 'ADBE Rotate Z').setValue(-35);
    jzSetExpr(jzXf(S, 'ADBE Scale'), JZ_FNS + '[value[0],value[1]*oe(time/0.3)]');
};
JZ_DECOR.blobs = function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc;
    for (var k = 0; k < (d.n || 2); k++) {
        var cx = jzLerp(W * 0.12, W * 0.88, jzR(d.seed, k, 1)), cy = jzLerp(H * 0.15, H * 0.85, jzR(d.seed, k, 2)), R = jzLerp(H * 0.06, H * 0.16, jzR(d.seed, k, 3));
        var sh = new Shape(), v = [], inT = [], outT = [], m = 12;
        for (var i = 0; i < m; i++) {
            var a = i / m * Math.PI * 2, r = R * (0.72 + 0.5 * jzR(d.seed, k, i, 4)), tx = -Math.sin(a) * r * 0.26, ty = Math.cos(a) * r * 0.26;
            v.push([Math.cos(a) * r, Math.sin(a) * r]); inT.push([-tx, -ty]); outT.push([tx, ty]);
        }
        sh.vertices = v; sh.inTangents = inT; sh.outTangents = outT; sh.closed = true;
        var S = jzShapeLayer(ctx, 'blob', cx, cy), g = jzGrp(S);
        var p = jzVecs(g).addProperty('ADBE Vector Shape - Group'); p.property('ADBE Vector Shape').setValue(sh);
        jzAddFill(g, k % 2 ? sc.accent : (sc.accent2 || sc.accent), 95);
        jzPop(ctx, S, 0, true);
    }
};
JZ_DECOR.bars = function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc;
    for (var k = 0; k < (d.n || 3); k++) {
        var y = H * (jzR(d.seed, k, 11) < 0.5 ? jzLerp(0.1, 0.27, jzR(d.seed, k, 1)) : jzLerp(0.73, 0.9, jzR(d.seed, k, 1))), h = H * jzLerp(0.03, 0.08, jzR(d.seed, k, 2));
        var w = W * jzLerp(0.35, 0.75, jzR(d.seed, k, 4)), fromL = jzR(d.seed, k, 3) < 0.5;
        var S = jzShapeLayer(ctx, 'bar', fromL ? 0 : W, y), g = jzGrp(S);
        jzAddRect(g, w, h, 0, fromL ? w / 2 : -w / 2, 0); jzAddFill(g, k === 0 ? sc.accent : sc.ink, 92);
        jzSetExpr(jzXf(S, 'ADBE Scale'), JZ_FNS + 'var e=oe((time-' + jzN(k * 0.05) + ')/0.3)*(1-ie((time-' + jzN(ctx.cut.dur - Math.max(0.12, ctx.cut.outDur || 0.15)) + ')/0.15));[value[0]*e,value[1]]');
    }
};
JZ_DECOR.shapes = function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, cols = [sc.accent, sc.accent2 || sc.fg, sc.ink, sc.fg];
    for (var k = 0; k < (d.n || 5); k++) {
        var type = ['circle', 'square', 'tri', 'ring', 'ring'][Math.floor(jzR(d.seed, k, 1) * 5)];
        var top = jzR(d.seed, k, 4) < 0.5, x = jzLerp(W * 0.05, W * 0.95, jzR(d.seed, k, 2)), y = top ? jzLerp(H * 0.06, H * 0.24, jzR(d.seed, k, 10)) : jzLerp(H * 0.76, H * 0.94, jzR(d.seed, k, 10));
        var r = jzLerp(H * 0.018, H * 0.06, jzR(d.seed, k, 6)), col = cols[k % 4];
        var S = jzShapeLayer(ctx, 'shape', x, y), g = jzGrp(S);
        if (type === 'circle') { jzAddEllipse(g, r * 2, r * 2); jzAddFill(g, col); }
        else if (type === 'ring') { jzAddEllipse(g, r * 2, r * 2); jzAddStroke(g, col, Math.max(2, r * 0.12)); }
        else if (type === 'square') { jzAddRect(g, r * 2, r * 2); jzAddFill(g, col); }
        else { jzAddStar(g, 3, r, r * 0.5); var st = jzVecs(g).property('ADBE Vector Shape - Star'); try { st.property('ADBE Vector Star Type').setValue(2); } catch (e) {} jzAddFill(g, col); }
        jzSetExpr(jzXf(S, 'ADBE Rotate Z'), 'time*' + Math.round((jzR(d.seed, k, 8) * 2 - 1) * 60) + '+' + Math.round(jzR(d.seed, k, 7) * 360));
        jzPop(ctx, S, jzR(d.seed, k, 9) * 0.3, true);
    }
};
JZ_DECOR.counter = function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut;
    var t = jzNoGhost(jzText(ctx, '00', { font: jzFontOf(ctx, '_', 'display'), size: H * 0.5, color: d.accent ? sc.accent : sc.dim, x: d.right ? W * 0.86 : W * 0.14, y: H * (d.low ? 0.72 : 0.3) }));
    var ex = d.mode === 'count' ? 'Math.floor(linear(time,0,' + jzN(c.dur * 0.8) + ',' + (d.from || 0) + ',' + (d.to || 99) + ')).toString()' : '"' + jzPad((c.index || 0) + 1, 2) + '"';
    try { t.property('ADBE Text Properties').property('ADBE Text Document').expression = ex; } catch (e) {}
    jzSetExpr(jzXf(t, 'ADBE Opacity'), jzIO(ctx, 0.3));
};

function jzDecorate(ctx, bb) {
    var list = ctx.cut.decor || [];
    bb = bb || { x0: ctx.W * 0.35, x1: ctx.W * 0.65, y0: ctx.H * 0.4, y1: ctx.H * 0.6, cx: ctx.W / 2, cy: ctx.H / 2 };
    for (var i = 0; i < list.length; i++) {
        var d = list[i], id = jzFallback('decor', d.id, null), D = id ? JZ_REG.decor[id] : null;
        if (!D) continue;
        var before = ctx.comp.numLayers;
        try { D.build(ctx, bb, d); } catch (e) { jzWarn('decor ' + id + ': ' + e.toString() + (e.line ? ' (line ' + e.line + ')' : '')); }
        if (D.back || jzMeta('decor', id).layer === 'back') { // push newly created layers to the back, keeping their order
            var added = ctx.comp.numLayers - before;
            for (var k = 0; k < added; k++) { try { ctx.comp.layer(1).moveToEnd(); } catch (e2) {} }
        }
    }
}
// ---- register the original decor set
(function () {
    for (var k in JZ_DECOR) if (JZ_DECOR.hasOwnProperty(k)) jzReg('decor', k, { build: JZ_DECOR[k], back: !!JZ_DECOR_BACK[k] });
})();

// ================================================================ core entries for the newer groups + shared helpers
// (text treatment / background graphic / camera / effect events / cut-to-cut transitions)

// ---- expression header for anything placed on the MAIN comp timeline for one event / transition
function jzEvHead(t0, dur, extra) {
    return 'var T0=' + jzN(t0) + ',TD=' + jzN(Math.max(1 / 240, dur)) + (extra || '') + ';\n' + JZ_FNS + 'var p=cl((time-T0)/TD);\n';
}
// adjustment layer on the main comp covering [t0, t0+dur] (+ a little pre-roll)
function jzAdjLayer(comp, name, t0, dur) {
    var L = comp.layers.addSolid([1, 1, 1], name, comp.width, comp.height, 1, comp.duration);
    L.adjustmentLayer = true;
    L.inPoint = Math.max(0, t0); L.outPoint = Math.min(comp.duration, Math.max(t0 + dur, t0 + 1 / 24));
    return L;
}
// shape layer on the main comp covering [t0, t0+dur]; origin at the comp's top-left
function jzEvShape(comp, name, t0, dur) {
    var L = comp.layers.addShape(); L.name = name;
    jzXf(L, 'ADBE Position').setValue([0, 0]);
    L.inPoint = Math.max(0, t0); L.outPoint = Math.min(comp.duration, Math.max(t0 + dur, t0 + 1 / 24));
    return L;
}
function jzEvSolid(comp, name, hex, t0, dur) {
    var L = comp.layers.addSolid(jzHex(hex), name, comp.width, comp.height, 1, comp.duration);
    L.inPoint = Math.max(0, t0); L.outPoint = Math.min(comp.duration, Math.max(t0 + dur, t0 + 1 / 24));
    return L;
}

// ---- text treatment: none
jzReg('treat', 'none', { apply: function (ctx, L, P, o) {} });
// ---- background graphic: none (the scheme colour + radial lift is always there)
jzReg('bg', 'none', { build: function (b, P) {} });
// ---- camera: slow push-in (the default)
jzReg('cam', 'push', { apply: function (cam, P) {
    jzSetExpr(jzXf(cam.nul, 'ADBE Scale'), 'var s=1+0.03*' + jzN(cam.fx.motion) + '*Math.min(1,Math.max(0,time/' + jzN(Math.max(0.3, cam.cut.dur)) + '));[value[0]*s,value[1]*s]');
} });

// ---- effect events handled by the global layers in jzBuild (chroma = ghost offsets, shake / slice / zoom / invert / flash)
jzReg('fx', 'chroma', { builtin: true, build: function (f, ev) {} });
jzReg('fx', 'shake', { builtin: true, build: function (f, ev) {} });
jzReg('fx', 'slice', { builtin: true, build: function (f, ev) {} });
jzReg('fx', 'invert', { builtin: true, build: function (f, ev) {} });
jzReg('fx', 'flash', { builtin: true, build: function (f, ev) {} });
jzReg('fx', 'zoom', { builtin: true, build: function (f, ev) {} });
// block glitch: a few frames of coarse mosaic with the picture knocked sideways
jzReg('fx', 'block', { build: function (f, ev) {
    var L = jzAdjLayer(f.comp, 'JZ FX block', ev.t, ev.dur);
    var mo = jzEffect(L, 'ADBE Mosaic', 'JZ Block'); jzEP(mo, 1, 28); jzEP(mo, 2, 16); jzEP(mo, 3, 1);
    var of = jzEffect(L, 'ADBE Offset', 'JZ Block Shift');
    jzEX(of, 1, 'posterizeTime(24);seedRandom(' + (ev.seed || 3) + '+Math.floor(time*24),true);[value[0]+random(-1,1)*thisComp.width*0.04*' + jzN(ev.amp || 1) + ',value[1]]');
} });
// mosaic
jzReg('fx', 'mosaic', { build: function (f, ev) {
    var L = jzAdjLayer(f.comp, 'JZ FX mosaic', ev.t, ev.dur);
    var mo = jzEffect(L, 'ADBE Mosaic', 'JZ Mosaic'); jzEP(mo, 3, 1);
    jzEX(mo, 1, jzEvHead(ev.t, ev.dur) + 'Math.round(linear(p,0,1,24,90))');
    jzEX(mo, 2, jzEvHead(ev.t, ev.dur) + 'Math.round(linear(p,0,1,24,90)*thisComp.height/thisComp.width)');
} });

// ---- shared by background / decor packs: faint tones
function jzLayC(sc, k) { return jzMixHex(sc.bg, sc.fg, k); }      // faint layer tone (k \u2248 0.05..0.15), = layC in the browser
function jzTintC(sc, k) { return jzMixHex(sc.bg, sc.accent, k); } // faint accent tone, = tintC
// ---- shared by camera packs
function jzKM(cam) { return jzClamp(cam.fx.motion * 1.25, 0, 1.25); }     // KM(env) in the browser
// expression header for camera moves: DUR, KM, cu (= time / DUR clamped), easing incl. ios (in-out sine)
function jzCamHead(cam) { return 'var DUR=' + jzN(Math.max(0.3, cam.cut.dur)) + ',KM=' + jzN(jzKM(cam)) + ',SD=' + ((cam.cut.seed || 0) % 99991) + ';\n' + JZ_FNS + 'function ios(x){x=cl(x);return -(Math.cos(Math.PI*x)-1)/2;}var cu=cl(time/DUR);\n'; }
// ---- shared by transition packs
function jzTAcc(t) { return t.sc.accent; }
function jzBellExpr() { return 'function bell(x){x=cl(x);return Math.sin(Math.PI*x);}'; }

// ================================================================ background graphics part 1 (AE port of the bg entries in src/11p_looks.js)
// bg.build(b, P): b.comp is the cut's WRAPPER comp (full frame, time 0 = cut start, duration = cut + 1 s). Layers you add
// sit above the scheme colour / paper and below the lyric. Keep contrast low; animate with expressions on `time`.

/* ---- sunburst \u2014 \u653E\u5C04: rotating rays, soft disc of the background colour in the middle */
jzReg('bg', 'sunburst', {
    plan: function (rng, st) { return { seed: rng.int(1, 999999999), n: rng.pick([12, 16, 20, 24]), cx: rng.pick([0.5, 0.5, 0.3, 0.7]), cy: rng.pick([0.5, 0.5, 0.62, 1.05]), spd: rng.range(3, 7) * rng.pick([1, -1]), k: rng.range(0.07, 0.1) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, cx = W * (P.cx == null ? 0.5 : P.cx), cy = H * (P.cy == null ? 0.5 : P.cy), n = P.n || 16;
        var R = Math.sqrt(W * W + H * H) * 1.1, w = Math.PI * 2 / n * 0.5, i;
        var S = jzShapeLayer(b, 'bg sunburst', cx, cy), g = jzGrp(S, 'rays');
        for (i = 0; i < n; i++) { var a0 = i / n * Math.PI * 2; jzAddPath(g, [[0, 0], [Math.cos(a0) * R, Math.sin(a0) * R], [Math.cos(a0 + w) * R, Math.sin(a0 + w) * R]], true); }
        jzAddFill(g, jzLayC(sc, P.k || 0.08));
        // grow in over 0.5 s, keep turning
        jzSetExpr(jzXf(S, 'ADBE Scale'), JZ_FNS + 'var e=oc(time/0.5);var s=25+75*e;[s,s]');
        jzSetExpr(jzXf(S, 'ADBE Rotate Z'), JZ_FNS + 'time*' + jzN(P.spd || 4) + '+(1-oc(time/0.5))*25');
        // soft disc in the background colour over the centre
        var r = jzU(b) * 0.55, D = b.comp.layers.addSolid(jzHex(sc.bg), 'bg sunburst centre', W, H, 1, b.comp.duration);
        var m = D.property('ADBE Mask Parade').addProperty('ADBE Mask Atom');
        m.property('ADBE Mask Shape').setValue(jzCircleShape(cx, cy, r * 0.6)); m.property('ADBE Mask Feather').setValue([r * 0.8, r * 0.8]);
        jzXf(D, 'ADBE Opacity').setValue(85);
    }
});

// ================================================================ shared helpers of this pack (prefix bg1_)
// the browser's deterministic hash J.h / J.r (so random placements match the web frame exactly)
function bg1_h(a, b, c, d, e) {
    var h = 0x9e3779b9 ^ (a | 0);
    h = jzImul(h ^ (h >>> 16), 0x85ebca6b);
    h = (h + jzImul((b | 0) + 0x632be5ab, 0xc2b2ae35)) | 0;
    h = jzImul(h ^ (h >>> 13), 0xc2b2ae35);
    h = (h + jzImul((c | 0) + 0x5bd1e995, 0x27d4eb2f)) | 0;
    h = jzImul(h ^ (h >>> 15), 0x165667b1);
    h = (h + jzImul((d | 0) + 0x1b873593, 0x85ebca6b)) | 0;
    h = jzImul(h ^ (h >>> 16), 0x27d4eb2f);
    h = (h + jzImul((e | 0) + 0x68e31da4, 0x9e3779b1)) | 0;
    h ^= h >>> 15; h = jzImul(h, 0x2c1b3c6d); h ^= h >>> 12; h = jzImul(h, 0x297a2d39); h ^= h >>> 15;
    return h >>> 0;
}
function bg1_r(a, b, c, d, e) { return bg1_h(a, b, c, d, e) / 4294967296; }
function bg1_rs(a, b, c, d, e) { return bg1_r(a, b, c, d, e) * 2 - 1; }
function bg1_rr(lo, hi, a, b, c, d, e) { return lo + (hi - lo) * bg1_r(a, b, c, d, e); }
// seconds this background had already been running at the cut start (browser bgT: consecutive cuts with the same bg + seed are one run)
function bg1_run(b) {
    var c = b.cut, cs = (b.plan && b.plan.cuts) || [], s = c.start || 0, P0 = c.bgP || {}, idx = -1, i;
    for (i = 0; i < cs.length; i++) if (cs[i] === c) { idx = i; break; }
    for (i = idx - 1; i >= 0; i--) {
        var p = cs[i];
        if (p.bg === c.bg && (p.bgP || {}).seed === P0.seed && Math.abs(p.end - s) < 0.06) s = p.start; else break;
    }
    return (c.start || 0) - s;
}
// expression header: T = absolute song time (browser env.t), BT = time since this background started (browser bgT)
var BG1_FNS = 'function cl(x){return Math.max(0,Math.min(1,x));}function oc(x){x=cl(x);return 1-Math.pow(1-x,3);}function oe(x){x=cl(x);return x>=1?1:1-Math.pow(2,-10*x);}function wr(v,m){return ((v%m)+m)%m;}';
function bg1_hd(b) { return BG1_FNS + 'var T=time+' + jzN(b.cut.start || 0) + ',BT=time+' + jzN(bg1_run(b)) + ';\n'; }
function bg1_px(b) { return jzU(b) / 1080; }                        // one browser design px in comp px
function bg1_dark(sc) { return jzLum(sc.bg) < 0.45; }
// colour mix a -> b by the expression q, as an expression colour [r,g,b,1]
function bg1_mixX(a, b, q) {
    var A = jzHex(a), B = jzHex(b), o = [], i;
    for (i = 0; i < 3; i++) o.push(jzN(A[i]) + '+' + jzN(B[i] - A[i]) + '*(' + q + ')');
    return '[' + o.join(',') + ',1]';
}
function bg1_solid(b, hex, name, w, h) { return b.comp.layers.addSolid(jzHex(hex), name, Math.max(4, Math.round(w)), Math.max(4, Math.round(h)), 1, b.comp.duration); }
// rectangle mask in layer space; o: { f: [fx, fy] feather, mode: MaskMode.*, inv: true }
function bg1_mask(L, x0, y0, x1, y1, o) {
    o = o || {};
    var m = L.property('ADBE Mask Parade').addProperty('ADBE Mask Atom'), sh = new Shape();
    sh.vertices = [[x1, y0], [x1, y1], [x0, y1], [x0, y0]]; sh.closed = true;
    m.property('ADBE Mask Shape').setValue(sh);
    if (o.f) m.property('ADBE Mask Feather').setValue(o.f);
    if (o.mode) m.maskMode = o.mode;
    if (o.inv) m.inverted = true;
    return m;
}
function bg1_cmask(L, cx, cy, r, o) {
    o = o || {};
    var m = L.property('ADBE Mask Parade').addProperty('ADBE Mask Atom');
    m.property('ADBE Mask Shape').setValue(jzCircleShape(cx, cy, r));
    if (o.f) m.property('ADBE Mask Feather').setValue(o.f);
    if (o.inv) m.inverted = true;
    return m;
}
// shape Repeater (copies n, offset off, step [dx, dy]) at the end of group g
function bg1_rep(g, n, dx, dy, off) {
    var V = jzVecs(g), ix = V.addProperty('ADBE Vector Filter - Repeater').propertyIndex;
    V.property(ix).property('ADBE Vector Repeater Copies').setValue(n);
    if (off) V.property(ix).property('ADBE Vector Repeater Offset').setValue(off);
    V.property(ix).property('ADBE Vector Repeater Transform').property('ADBE Vector Repeater Position').setValue([dx, dy]);
    return V.property(ix);
}
function bg1_seed(rng) { return rng.int(1, 999999999); }
function bg1_sub(g, name) { var c = jzVecs(g).addProperty('ADBE Vector Group'); if (name) c.name = name; return c; }   // group inside a group

/* ---- concentric \u2014 \u540C\u5FC3\u5186: rings drifting outwards from the centre, pulsing line width / tone */
jzReg('bg', 'concentric', {
    plan: function (rng, st) { return { seed: bg1_seed(rng), gap: rng.range(0.07, 0.11), spd: rng.range(0.15, 0.35) * rng.pick([1, 1, -1]), k: rng.range(0.08, 0.12), cy: rng.pick([0.5, 0.5, 0.58]) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, gap = jzU(b) * (P.gap || 0.09), k = P.k || 0.1, i;
        var Rm = Math.sqrt(W * W + H * H) * 0.6, n = Math.ceil(Rm / gap) + 1;
        var S = jzShapeLayer(b, 'bg concentric', W / 2, H * (P.cy || 0.5)), g = jzGrp(S, 'rings');
        var hd = bg1_hd(b) + 'var G=' + jzN(gap) + ',ph=wr(T*' + jzN(P.spd || 0.2) + ',1),Rm=' + jzN(Rm) + '*oc(BT/0.6),pu=0.5+0.5*Math.sin(T*Math.PI);\n';
        for (i = 0; i < n; i++) {   // ring i sits at (phase + i) * gap; rings outside the growing radius collapse to nothing
            var el = jzAddEllipse(g, 10, 10);
            jzSetExpr(el.property('ADBE Vector Ellipse Size'), hd + 'var r=(ph+' + i + ')*G;(r<2||r>=Rm)?[0,0]:[2*r,2*r]');
        }
        var st = jzAddStroke(g, jzLayC(sc, k), gap * 0.19);
        jzSetExpr(st.property('ADBE Vector Stroke Width'), hd + 'G*(0.14+0.1*pu)');
        jzSetExpr(st.property('ADBE Vector Stroke Color'), hd + bg1_mixX(sc.bg, sc.fg, jzN(k) + '*(0.8+0.4*pu)'));
    }
});

/* ---- halftoneFade \u2014 \u7DB2\u70B9\u30B0\u30E9\u30C7: hex dot screen whose dots grow along a direction (the lattice is turned with the direction), slow drift */
jzReg('bg', 'halftoneFade', {
    plan: function (rng, st) { return { seed: bg1_seed(rng), dir: rng.pick([0, 45, 90, 135, 180, 225, 270, 315]), cell: rng.range(0.032, 0.045), k: rng.range(0.12, 0.17) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, cell = jzU(b) * (P.cell || 0.032), rh = cell * 0.866, i, h;
        var ang = (P.dir || 0) * Math.PI / 180, dx = Math.cos(ang), dy = Math.sin(ang);
        var ext = Math.abs(dx) * W / 2 + Math.abs(dy) * H / 2, ext2 = Math.abs(dy) * W / 2 + Math.abs(dx) * H / 2;
        var S = jzShapeLayer(b, 'bg halftoneFade', W / 2, H / 2);
        jzXf(S, 'ADBE Rotate Z').setValue(P.dir || 0);
        var g = jzGrp(S, 'dots'), n0 = Math.ceil((ext + cell) / cell);
        // one dot per column (even row + odd row), the column is repeated downwards; size = smoothstep along the gradient axis
        for (i = -n0; i <= n0; i++) for (h = 0; h < 2; h++) {
            var x = i * cell + h * cell / 2, t = jzClamp((x / ext + 0.8) / 1.8, 0, 1), r = cell * 0.52 * t * t * (3 - 2 * t);
            if (r < cell * 0.06) continue;
            jzAddEllipse(g, r * 2, r * 2, x, h * rh);
        }
        jzAddFill(g, jzLayC(b.sc, P.k || 0.14));
        var ny = Math.ceil((ext2 + 2 * rh) / (2 * rh)) * 2 + 2;
        bg1_rep(g, ny, 0, 2 * rh, -ny / 2);
        jzSetExpr(jzGX(g).property('ADBE Vector Position'), bg1_hd(b) + '[0,wr(T*' + jzN(cell * 0.35) + ',' + jzN(2 * rh) + ')]');
        jzSetExpr(jzXf(S, 'ADBE Opacity'), bg1_hd(b) + '100*oc(BT/0.5)');
    }
});

/* ---- bigStripes \u2014 \u5927\u304D\u306A\u659C\u7DDA: wide diagonal stripes sliding sideways, stripes widen in */
jzReg('bg', 'bigStripes', {
    plan: function (rng, st) { return { seed: bg1_seed(rng), ang: rng.pick([30, 45, -30, -45, 60]), w: rng.range(0.06, 0.11), spd: rng.range(0.03, 0.08) * rng.pick([1, -1]), fill: rng.pick([0.5, 0.5, 0.3]), k: rng.range(0.05, 0.08) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, U = jzU(b), R = Math.sqrt(W * W + H * H) / 2 + 10 * bg1_px(b), per = U * (P.w || 0.08) * 2, fw = per * (P.fill || 0.5);
        var S = jzShapeLayer(b, 'bg bigStripes', W / 2, H / 2);
        jzXf(S, 'ADBE Rotate Z').setValue(P.ang || 45);
        var g = jzGrp(S, 'stripes'), rc = jzAddRect(g, fw, 2 * R);
        var hd = bg1_hd(b) + 'var w=' + jzN(fw) + '*oe(BT/0.5);';
        jzSetExpr(rc.property('ADBE Vector Rect Size'), hd + '[w,' + jzN(2 * R) + ']');
        jzSetExpr(rc.property('ADBE Vector Rect Position'), hd + '[w/2,0]');
        jzAddFill(g, jzLayC(b.sc, P.k || 0.06));
        bg1_rep(g, Math.ceil(2 * R / per) + 3, per, 0, 0);
        jzSetExpr(jzGX(g).property('ADBE Vector Position'), bg1_hd(b) + '[' + jzN(-R - per) + '+wr(T*' + jzN(U * (P.spd || 0.05)) + ',' + jzN(per) + '),0]');
    }
});

/* ---- splitV / splitH / splitDiag \u2014 \u4E8C\u8272: a flat second tone slides in from one side, its edge sways; thin accent line on the edge */
function bg1_splitCol(sc, P) { return P.c === 'tint' ? jzTintC(sc, (P.k || 0.08) * 1.8) : jzLayC(sc, P.k || 0.08); }
function bg1_split(b, P, vert) {
    var W = b.W, H = b.H, px = bg1_px(b), far = (P.side || 1) > 0, L = vert ? W : H, M = vert ? H : W;
    var hd = bg1_hd(b) + 'var e=oe(BT/0.5),x=' + jzN(L) + '*(' + jzN(P.pos || 0.5) + '+0.012*Math.sin(T*' + (vert ? '0.6' : '0.5') + ')),ed=' + (far ? jzN(L) + '+(x-' + jzN(L) + ')*e' : 'x*e') + ';';
    var xy = function (a, c) { return vert ? '[' + a + ',' + c + ']' : '[' + c + ',' + a + ']'; };
    var S = jzShapeLayer(b, 'bg ' + (vert ? 'splitV' : 'splitH'), 0, 0);
    var g2 = jzGrp(S, 'edge'), r2 = jzAddRect(g2, vert ? 2 * px : M, vert ? M : 2 * px);
    jzSetExpr(r2.property('ADBE Vector Rect Position'), hd + xy('ed', jzN(M / 2)));
    jzAddFill(g2, jzTintC(b.sc, 0.45), 60);
    jzSetExpr(jzGX(g2).property('ADBE Vector Group Opacity'), hd + '100*e');
    var g = jzGrp(S, 'panel'), rc = jzAddRect(g, vert ? L + 2 * px : M + 2 * px, vert ? M + 2 * px : L + 2 * px);
    jzSetExpr(rc.property('ADBE Vector Rect Position'), hd + xy('ed' + (far ? '+' : '-') + jzN(L / 2 + px), jzN(M / 2)));
    jzAddFill(g, bg1_splitCol(b.sc, P));
}
jzReg('bg', 'splitV', {
    plan: function (rng, st) { return { seed: bg1_seed(rng), side: rng.pick([1, -1]), pos: rng.range(0.4, 0.6), c: rng.pick(['lay', 'lay', 'tint']), k: rng.range(0.08, 0.12) }; },
    build: function (b, P) { bg1_split(b, P, true); }
});
jzReg('bg', 'splitH', {
    plan: function (rng, st) { return { seed: bg1_seed(rng), side: rng.pick([1, -1]), pos: rng.range(0.42, 0.6), c: rng.pick(['lay', 'lay', 'tint']), k: rng.range(0.08, 0.12) }; },
    build: function (b, P) { bg1_split(b, P, false); }
});
jzReg('bg', 'splitDiag', {
    plan: function (rng, st) { return { seed: bg1_seed(rng), side: rng.pick([1, -1]), pos: rng.range(0.42, 0.58), ang: rng.range(14, 30) * rng.pick([1, -1]), c: rng.pick(['lay', 'lay', 'tint']), k: rng.range(0.08, 0.12) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, px = bg1_px(b), side = (P.side || 1) > 0 ? 1 : -1, tn = Math.tan((P.ang || 20) * Math.PI / 180), far = side * W * 2.2;
        var hd = bg1_hd(b) + 'var e=oe(BT/0.5),cx=' + jzN(W * (P.pos || 0.5)) + '+(1-e)*' + jzN(W * 0.9 * side) + '+' + jzN(W * 0.01) + '*Math.sin(T*0.5);';
        var S = jzShapeLayer(b, 'bg splitDiag', 0, 0);
        var g2 = jzGrp(S, 'edge');
        jzAddPath(g2, [[-tn * H / 2, -px], [tn * H / 2, H + px]], false);
        jzAddStroke(g2, jzTintC(b.sc, 0.45), 2 * px, 60);
        jzSetExpr(jzGX(g2).property('ADBE Vector Position'), hd + '[cx,0]');
        jzSetExpr(jzGX(g2).property('ADBE Vector Group Opacity'), hd + '100*e');
        var g = jzGrp(S, 'panel');
        jzAddPath(g, [[-tn * H / 2, -px], [far, -px], [far, H + px], [tn * H / 2, H + px]], true);
        jzAddFill(g, bg1_splitCol(b.sc, P));
        jzSetExpr(jzGX(g).property('ADBE Vector Position'), hd + '[cx,0]');
    }
});

/* ---- gradientSweep \u2014 \u30B0\u30E9\u30C7: a broad band of accent tint (linear 0 -> 1 -> 0 across the frame) that turns slowly and sways.
   AE: a big rotated solid; two feathered half-plane masks (expansion + feather follow the peak) give the linear ramps */
jzReg('bg', 'gradientSweep', {
    plan: function (rng, st) { return { seed: bg1_seed(rng), spd: rng.range(0.25, 0.5), k: rng.range(0.16, 0.26), c: rng.pick(['accent', 'accent', 'accent2', 'fg']), a0: rng.range(0, 6.28) }; },
    build: function (b, P) {
        // the solid is twice the frame diagonal so the masks' feather never reaches the layer edges inside the frame
        var W = b.W, H = b.H, sc = b.sc, L = Math.sqrt(W * W + H * H) / 2, D = Math.ceil(L * 4) + 8, F = D * 3;
        var tint = jzMixHex(sc.bg, sc[P.c] || sc.accent, P.k || 0.2);
        var S = bg1_solid(b, tint, 'bg gradientSweep', D, D);
        var hd = bg1_hd(b) + 'var L=' + jzN(L) + ',xp=L*(2*(0.5+0.32*Math.sin(T*' + jzN(P.spd || 0.35) + '))-1);';
        // rising side: from -L (0) to the peak (1)
        var m1 = bg1_mask(S, D / 2, -F, F, F);
        jzSetExpr(m1.property('ADBE Mask Offset'), hd + '(L-xp)/2');
        jzSetExpr(m1.property('ADBE Mask Feather'), hd + '[Math.max(0,xp+L),0]');
        var m2 = bg1_mask(S, -F, -F, D / 2, F, { mode: MaskMode.INTERSECT });
        jzSetExpr(m2.property('ADBE Mask Offset'), hd + '(xp+L)/2');
        jzSetExpr(m2.property('ADBE Mask Feather'), hd + '[Math.max(0,L-xp),0]');
        jzSetExpr(jzXf(S, 'ADBE Rotate Z'), bg1_hd(b) + '(' + jzN(P.a0 || 0) + '+T*0.12)*180/Math.PI');
        jzSetExpr(jzXf(S, 'ADBE Opacity'), bg1_hd(b) + '100*oc(BT/0.6)');
    }
});

/* ---- spotlight \u2014 \u30B9\u30DD\u30C3\u30C8\u30E9\u30A4\u30C8: a wandering soft pool of light, darker surroundings, optional beam from the top */
jzReg('bg', 'spotlight', {
    plan: function (rng, st) { return { seed: bg1_seed(rng), beam: rng.chance(0.6), k: rng.range(0.1, 0.16) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, s = P.seed || 1, U = jzU(b), R = U * 0.62, dk = bg1_dark(sc), px = bg1_px(b);
        var lc = dk ? sc.fg : '#FFFFFF', k = P.k || 0.12;
        var hd = bg1_hd(b) + 'var e=oc(BT/0.6),cx=' + jzN(W) + '*(0.5+0.2*Math.sin(T*0.31+' + (s % 7) + ')),cy=' + jzN(H) + '*(0.52+0.08*Math.sin(T*0.23+' + (s % 5) + '));';
        // darker surroundings: black with a soft hole (transparent inside 0.3R, full outside 1.7R)
        var V = bg1_solid(b, '#000000', 'bg spotlight shade', W * 2 + 4 * R, H * 2 + 4 * R), vw = W + 2 * R, vh = H + 2 * R;
        bg1_cmask(V, vw, vh, R, { f: [R * 1.4, R * 1.4], inv: true });
        jzSetExpr(jzXf(V, 'ADBE Position'), hd + '[cx,cy]');
        jzSetExpr(jzXf(V, 'ADBE Opacity'), hd + jzN(dk ? 35 : 14) + '*e');
        // the pool of light
        var G = bg1_solid(b, lc, 'bg spotlight light', R * 2.4, R * 2.4);
        bg1_cmask(G, R * 1.2, R * 1.2, R * 0.6, { f: [R * 0.8, R * 0.8] });
        jzSetExpr(jzXf(G, 'ADBE Position'), hd + '[cx,cy]');
        jzSetExpr(jzXf(G, 'ADBE Opacity'), hd + jzN(k * 100) + '*e');
        if (P.beam) {   // trapezoid from the top edge to the pool, fading out downwards (a feathered mask follows the pool's height)
            var Hb = 1000, S = jzShapeLayer(b, 'bg spotlight beam', 0, 0), g = jzGrp(S, 'beam');
            jzAddPath(g, [[-U * 0.04, 0], [U * 0.04, 0], [R * 0.75, Hb], [-R * 0.75, Hb]], true);
            jzAddFill(g, lc);
            var hb = hd + 'var tx=cx+(' + jzN(W / 2) + '-cx)*0.5,dx=cx-tx,dy=cy+' + jzN(2 * px) + ';';
            jzSetExpr(jzGX(g).property('ADBE Vector Position'), hb + '[tx,' + jzN(-2 * px) + ']');
            jzSetExpr(jzGX(g).property('ADBE Vector Rotation'), hb + 'Math.atan2(-dx,dy)*180/Math.PI');
            jzSetExpr(jzGX(g).property('ADBE Vector Scale'), hb + '[100,Math.sqrt(dx*dx+dy*dy)/' + Hb + '*100]');
            var m = bg1_mask(S, -W, -H * 3, W * 2, 0);
            jzSetExpr(m.property('ADBE Mask Offset'), hd + 'cy/2');
            jzSetExpr(m.property('ADBE Mask Feather'), hd + '[0,Math.max(1,cy)]');
            jzSetExpr(jzXf(S, 'ADBE Opacity'), hd + jzN(k * 70) + '*e');
        }
    }
});

/* ---- tvBars \u2014 \u30C6\u30EC\u30D3\u306E\u5E2F: a few horizontal bars of different heights rolling down the frame (same bars as the browser) */
jzReg('bg', 'tvBars', {
    plan: function (rng, st) { return { seed: bg1_seed(rng), n: rng.int(4, 7), spd: rng.range(0.05, 0.14), k: rng.range(0.06, 0.09) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, s = P.seed || 1, k = P.k || 0.07, n = P.n || 5, px = bg1_px(b), i;
        var S = jzShapeLayer(b, 'bg tvBars', 0, 0);
        for (i = n - 1; i >= 0; i--) {    // later bars on top (first shape group = front)
            var h = H * bg1_rr(0.02, 0.15, s, i, 1), v = H * (P.spd || 0.08) * (0.5 + bg1_r(s, i, 2)), y0 = bg1_r(s, i, 3) * H * 1.4;
            var g = jzGrp(S, 'bar ' + (i + 1));
            if (bg1_r(s, i, 5) < 0.5) {
                var lh = Math.max(1.5 * px, H * 0.002), gl = bg1_sub(g, 'line');
                jzAddRect(gl, W, lh, 0, W / 2, -H * 0.012 + lh / 2); jzAddFill(gl, jzLayC(sc, k * 1.4));
            }
            var gb = bg1_sub(g, 'band');
            jzAddRect(gb, W, h, 0, W / 2, h / 2);
            jzAddFill(gb, i % 3 === 0 ? jzTintC(sc, k * 1.7) : jzLayC(sc, k * (0.6 + 0.8 * bg1_r(s, i, 4))));
            jzSetExpr(jzGX(g).property('ADBE Vector Position'), bg1_hd(b) + '[0,wr(T*' + jzN(v) + '+' + jzN(y0) + ',' + jzN(H * 1.4) + ')-' + jzN(H * 0.2) + ']');
        }
    }
});

/* ---- checker \u2014 \u5E02\u677E: large faint checkerboard (optionally at 45\u00B0) sliding diagonally */
jzReg('bg', 'checker', {
    plan: function (rng, st) { return { seed: bg1_seed(rng), n: rng.int(5, 8), k: rng.range(0.04, 0.065), spd: rng.range(0.1, 0.25), rot: rng.pick([0, 0, 45]) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, cell = jzU(b) / (P.n || 6) * (P.rot ? 1.2 : 1), R = Math.sqrt(W * W + H * H) / 2 + cell * 2;
        var S = jzShapeLayer(b, 'bg checker', W / 2, H / 2);
        if (P.rot) jzXf(S, 'ADBE Rotate Z').setValue(P.rot);
        var g = jzGrp(S, 'checks');
        jzAddRect(g, cell, cell, 0, cell / 2, -cell / 2);      // 2x2 tile: the two odd squares
        jzAddRect(g, cell, cell, 0, -cell / 2, cell / 2);
        jzAddFill(g, jzLayC(b.sc, P.k || 0.05));
        var c = Math.ceil(R / (2 * cell)) * 2 + 4;
        bg1_rep(g, c, 2 * cell, 0, -c / 2);
        bg1_rep(g, c, 0, 2 * cell, -c / 2);
        jzSetExpr(jzGX(g).property('ADBE Vector Position'), bg1_hd(b) + 'var o=wr(T*' + jzN(cell * (P.spd || 0.15)) + ',' + jzN(2 * cell) + ');[o,o]');
        jzSetExpr(jzXf(S, 'ADBE Opacity'), bg1_hd(b) + '100*oc(BT/0.4)');
    }
});

/* ---- bigChar \u2014 \u5DE8\u5927\u6587\u5B57: the line's first character, huge and faint, off to one side, breathing slowly */
jzReg('bg', 'bigChar', {
    plan: function (rng, st) { return { seed: bg1_seed(rng), side: rng.pick([-1, 1]), font: rng.pick(['display', 'serif']), outline: rng.chance(0.3), k: rng.range(0.07, 0.1) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, k = P.k || 0.08, side = P.side || 1, i;
        var cs = jzChars(String(b.cut.lineText || b.cut.text || '').replace(/[\s\u3000]+/g, '')), ch = null;
        for (i = 0; i < cs.length; i++) if (!jzIsPunct(cs[i])) { ch = cs[i]; break; }
        if (!ch) ch = cs[0];
        if (!ch) return;
        var size = Math.max(W, H) * 0.6, o = { font: jzFontsOf(b.st, [P.font || 'display'])[0], size: size, color: jzLayC(sc, k), x: W / 2 + side * W * 0.2, y: H * 0.53, rot: side * 4, name: 'bg bigChar' };
        if (P.outline) { o.fill = false; o.stroke = Math.max(2 * bg1_px(b), size * 0.006); o.strokeColor = jzLayC(sc, k * 2.4); }
        var L = jzText({ comp: b.comp, roles: null, W: W, H: H }, ch, o);
        jzSetExpr(jzXf(L, 'ADBE Scale'), bg1_hd(b) + 'var f=1.06-0.06*oc(BT/0.7)+0.015*Math.sin(T*0.3);[value[0]*f,value[1]*f]');
        jzSetExpr(jzXf(L, 'ADBE Position'), bg1_hd(b) + '[value[0]+Math.sin(T*0.2)*' + jzN(W * 0.01) + ',value[1]]');
        jzSetExpr(jzXf(L, 'ADBE Opacity'), bg1_hd(b) + '100*oc(BT/0.7)');
    }
});

/* ---- speedLines \u2014 \u96C6\u4E2D\u7DDA: manga focus lines around a clear ellipse, re-drawn every frame step (3 random sets cycle) */
jzReg('bg', 'speedLines', {
    plan: function (rng, st) { return { seed: bg1_seed(rng), n: rng.int(90, 140), k: rng.range(0.2, 0.28), clear: rng.range(0.3, 0.36) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, s = P.seed || 1, n = P.n || 100, K = 3, TAU = Math.PI * 2, j, i;
        var rx = W * (P.clear || 0.38), ry = H * (P.clear || 0.38) * 1.05, R = Math.sqrt(W * W + H * H) * 0.6;
        var S = jzShapeLayer(b, 'bg speedLines', W / 2, H / 2);
        for (j = 0; j < K; j++) {
            var g = jzGrp(S, 'lines ' + (j + 1));
            for (i = 0; i < n; i++) {
                var a = (i + bg1_r(s, i, j, 1) * 0.8) / n * TAU, f = 1 + bg1_r(s, i, j, 2) * 0.5, w = TAU / n * (0.15 + 0.45 * bg1_r(s, i, j, 3));
                jzAddPath(g, [[Math.cos(a) * rx * f, Math.sin(a) * ry * f], [Math.cos(a - w / 2) * R, Math.sin(a - w / 2) * R], [Math.cos(a + w / 2) * R, Math.sin(a + w / 2) * R]], true);
            }
            jzAddFill(g, jzLayC(b.sc, P.k || 0.16));
            jzSetExpr(jzGX(g).property('ADBE Vector Group Opacity'), bg1_hd(b) + 'Math.floor(T*12+1e-6)%' + K + '===' + j + '?100:0');
        }
        // the clear area opens from 1.6x to 1x over 0.3 s (the lines' outer ends stay off-frame)
        jzSetExpr(jzXf(S, 'ADBE Scale'), bg1_hd(b) + 'var f=100*(1+(1-oc(BT/0.3))*0.6);[f,f]');
    }
});

/* ---- scanBars \u2014 \u8D70\u67FB\u7DDA\u306E\u5E2F: 2-3 tall soft bands (vertical gradient up to a bright scan line) rolling down slowly */
jzReg('bg', 'scanBars', {
    plan: function (rng, st) { return { seed: bg1_seed(rng), n: rng.int(2, 3), spd: rng.range(0.06, 0.12), k: rng.range(0.05, 0.08) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, s = P.seed || 1, k = P.k || 0.06, px = bg1_px(b), F = H * 4, i;
        for (i = 0; i < (P.n || 2); i++) {
            var h = H * (0.12 + 0.12 * bg1_r(s, i, 1)), v = H * (P.spd || 0.08) * (0.8 + 0.4 * bg1_r(s, i, 2)), y0 = bg1_r(s, i, 3) * H * 1.5, lh = Math.max(1.5 * px, H * 0.0018);
            var S = jzShapeLayer(b, 'bg scanBars ' + (i + 1), 0, 0);
            var gl = jzGrp(S, 'scan line'); jzAddRect(gl, W, lh, 0, W / 2, h * 0.86 + lh / 2); jzAddFill(gl, sc.fg);
            var gb = jzGrp(S, 'band'); jzAddRect(gb, W, h, 0, W / 2, h / 2); jzAddFill(gb, sc.fg, 62.5);
            // alpha 0 at the top -> 1 at 0.85 h -> 0 at the bottom (two feathered half-plane masks)
            bg1_mask(S, -W, h * 0.425, W * 2, F, { f: [0, h * 0.85] });
            bg1_mask(S, -W, -F, W * 2, h * 0.925, { f: [0, h * 0.15], mode: MaskMode.INTERSECT });
            jzXf(S, 'ADBE Opacity').setValue(Math.min(100, k * 160));
            jzSetExpr(jzXf(S, 'ADBE Position'), bg1_hd(b) + '[0,wr(T*' + jzN(v) + '+' + jzN(y0) + ',' + jzN(H * 1.5) + ')-' + jzN(H * 0.3) + ']');
        }
    }
});

/* ---- dotGrid \u2014 \u30C9\u30C3\u30C8\u683C\u5B50: fine dot lattice drifting diagonally (optional plus marks every 4th point), a few accent dots twinkle */
jzReg('bg', 'dotGrid', {
    plan: function (rng, st) { return { seed: bg1_seed(rng), sp: rng.range(0.045, 0.065), k: rng.range(0.22, 0.3), plus: rng.chance(0.4) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, U = jzU(b), sp = U * (P.sp || 0.055), r = Math.max(1.5 * bg1_px(b), U * 0.0036), i, j;
        var per = P.plus ? sp * 4 : sp, S = jzShapeLayer(b, 'bg dotGrid', 0, 0), g = jzGrp(S, 'lattice');
        if (P.plus) {       // 4x4 tile: a plus at the tile origin, dots elsewhere
            jzAddRect(g, r * 6, r, 0, 0, 0); jzAddRect(g, r, r * 6, 0, 0, 0);
            for (j = 0; j < 4; j++) for (i = 0; i < 4; i++) if (i || j) jzAddEllipse(g, r * 2, r * 2, i * sp, j * sp);
        } else jzAddEllipse(g, r * 2, r * 2);
        jzAddFill(g, jzLayC(sc, P.k || 0.2));
        var nx = Math.ceil((W + 2 * per) / per) + 1, ny = Math.ceil((H + 2 * per) / per) + 1;
        bg1_rep(g, nx, per, 0, 0);
        bg1_rep(g, ny, 0, per, 0);
        jzSetExpr(jzGX(g).property('ADBE Vector Position'), bg1_hd(b) + '[wr(T*' + jzN(0.15 * sp) + ',' + jzN(per) + ')-' + jzN(per) + ',wr(T*' + jzN(0.08 * sp) + ',' + jzN(per) + ')-' + jzN(per) + ']');
        // accent twinkles: re-picked every half second on lattice points (about 1.2 % of the dots)
        var cols = Math.ceil(W / sp) + 2, rows = Math.ceil(H / sp) + 2, M = Math.max(2, Math.round(cols * rows * 0.012)), sd = (P.seed || 1) % 9973;
        var ga = jzGrp(S, 'accents');
        for (i = 0; i < M; i++) {
            var gi = bg1_sub(ga, 'twinkle ' + (i + 1));
            jzAddEllipse(gi, r * 4, r * 4);
            jzSetExpr(jzGX(gi).property('ADBE Vector Position'), bg1_hd(b) + 'var tw=Math.floor(T*2),mx=T*0.15,my=T*0.08;seedRandom(' + sd + '+tw*97+' + i + '*7,true);' +
                'var a=Math.floor(random(-1,' + cols + ')),c=Math.floor(random(-1,' + rows + '));[(a+wr(mx,1))*' + jzN(sp) + ',(c+wr(my,1))*' + jzN(sp) + ']');
        }
        jzAddFill(ga, jzTintC(sc, 0.55));
        jzSetExpr(jzXf(S, 'ADBE Opacity'), bg1_hd(b) + '100*oc(BT/0.5)');
    }
});

/* ---- retroGrid \u2014 \u30EC\u30C8\u30ED\u683C\u5B50: synthwave floor (perspective grid rushing towards the viewer), horizon glow, optional striped sun */
jzReg('bg', 'retroGrid', {
    plan: function (rng, st) { return { seed: bg1_seed(rng), hz: rng.range(0.56, 0.64), spd: rng.range(0.4, 0.8), sun: rng.chance(0.4), k: rng.range(0.38, 0.5) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, U = jzU(b), px = bg1_px(b), k = P.k || 0.35, yh = H * (P.hz || 0.6), D = H - yh, col = jzTintC(sc, k), i, j;
        var hd = bg1_hd(b) + 'var e=oc(BT/0.6);';
        if (P.sun) {
            var R = U * 0.2, sy = yh - R * 0.35, SN = jzShapeLayer(b, 'bg retroGrid sun', W / 2, sy);
            var gs = jzGrp(SN, 'stripes');
            for (i = 0; i < 5; i++) { var hh = R * 0.03 * (1 + i * 0.6); jzAddRect(gs, R * 2, hh, 0, 0, R * (0.1 + i * 0.18) + hh / 2); }
            jzAddFill(gs, sc.bg);
            var gc = jzGrp(SN, 'sun'), el = jzAddEllipse(gc, R * 2, R * 2);
            jzSetExpr(el.property('ADBE Vector Ellipse Size'), hd + '[' + jzN(2 * R) + '*e,' + jzN(2 * R) + '*e]');
            jzAddFill(gc, jzTintC(sc, k * 0.45));
            bg1_mask(SN, -W, -H * 2, W, yh - sy);           // only above the horizon
        }
        var S = jzShapeLayer(b, 'bg retroGrid floor', 0, 0), spB = W * 0.18;
        var gh = jzGrp(S, 'rows');
        for (j = 0; j < 26; j++) {      // row j sits at depth z = j + 1 - phase
            var gj = bg1_sub(gh, 'row ' + (j + 1));
            jzAddPath(gj, [[-2 * px, yh], [W + 2 * px, yh]], false);
            jzSetExpr(jzGX(gj).property('ADBE Vector Position'), bg1_hd(b) + 'var z=' + (j + 1) + '-wr(T*' + jzN(P.spd || 0.6) + ',1),y=z>0.2?' + jzN(D * 0.9) + '/z:1e5;(y>' + jzN(D + 2 * px) + '||y<' + jzN(1.5 * px) + ')?[0,' + jzN(H * 3) + ']:[0,y]');
        }
        jzAddStroke(gh, col, Math.max(1.5 * px, U * 0.0025));
        var gv = jzGrp(S, 'rails');
        for (i = -12; i <= 12; i++) jzAddPath(gv, [[W / 2 + i * spB * 0.06, yh], [W / 2 + i * spB * 1.6, H + H * 0.3]], false);
        jzAddStroke(gv, col, Math.max(1.5 * px, U * 0.0025));
        // lines fade in towards the horizon: alpha 0 at the horizon, ~0.55 at 30 %, 1 at the bottom
        bg1_mask(S, -W, yh + D * 0.275, W * 2, H * 3, { f: [0, D * 0.55] });
        jzSetExpr(jzXf(S, 'ADBE Opacity'), hd + '100*e');
        // glow above the horizon + the horizon line
        var GL = bg1_solid(b, col, 'bg retroGrid glow', W, H * 0.1);
        jzXf(GL, 'ADBE Position').setValue([W / 2, yh - H * 0.05]);
        bg1_mask(GL, -W, H * 0.05, W * 2, H, { f: [0, H * 0.1] });
        jzSetExpr(jzXf(GL, 'ADBE Opacity'), hd + '35*e');
        var HL = jzRectLayer(b, 'bg retroGrid horizon', W / 2, yh, W, 2 * px, col);
        jzSetExpr(jzXf(HL, 'ADBE Opacity'), hd + '80*e');
    }
});

/* ---- bokehBg \u2014 \u30DC\u30B1\u7389: large soft out-of-focus discs in the scheme colours drifting up, twinkling (screen blend on dark schemes) */
jzReg('bg', 'bokehBg', {
    plan: function (rng, st) { return { seed: bg1_seed(rng), n: rng.int(12, 20), k: rng.range(0.18, 0.28) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, s = P.seed || 1, U = jzU(b), dk = bg1_dark(sc), i;
        var cols = [sc.accent, sc.accent2 || sc.accent, sc.fg, sc.ghostA || sc.accent, sc.ghostB || sc.fg];
        var S = jzShapeLayer(b, 'bg bokehBg', 0, 0);
        for (i = (P.n || 12) - 1; i >= 0; i--) {
            var r = U * bg1_rr(0.035, 0.13, s, i, 1), vx = bg1_rs(s, i, 2) * U * 0.03, vy = -U * bg1_rr(0.01, 0.04, s, i, 3);
            var a = (P.k || 0.2) * (0.5 + 0.5 * bg1_r(s, i, 6)) * (dk ? 1 : 0.7), c = cols[i % cols.length];
            var g = jzGrp(S, 'bokeh ' + (i + 1));
            jzAddEllipse(g, r * 1.68, r * 1.68); jzAddStroke(g, c, r * 0.16, 35);     // brighter rim
            var gi = bg1_sub(g, 'disc'); jzAddEllipse(gi, r * 1.84, r * 1.84); jzAddFill(gi, c, 70);
            jzSetExpr(jzGX(g).property('ADBE Vector Position'), bg1_hd(b) + '[wr(' + jzN(bg1_r(s, i, 4) * W) + '+T*' + jzN(vx) + ',' + jzN(W + 2 * r) + ')-' + jzN(r) + '+Math.sin(T*0.4+' + i + ')*' + jzN(U * 0.015) +
                ',wr(' + jzN(bg1_r(s, i, 5) * H) + '+T*' + jzN(vy) + ',' + jzN(H + 2 * r) + ')-' + jzN(r) + ']');
            jzSetExpr(jzGX(g).property('ADBE Vector Group Opacity'), bg1_hd(b) + jzN(a * 100) + '*oc(BT/0.8)*(0.75+0.25*Math.sin(T*' + jzN(0.6 + bg1_r(s, i, 7)) + '+' + i + '))');
        }
        var bl = jzEffect(S, 'ADBE Gaussian Blur 2', 'JZ Bokeh Soft'); jzEP(bl, 1, U * 0.006); jzEP(bl, 3, 0);
        if (dk) S.blendingMode = BlendingMode.SCREEN;
    }
});

/* ---- particlesBg \u2014 \u821E\u3044\u4E0A\u304C\u308B\u7C92: many small dots / squares rising and swaying, fading out towards the top */
jzReg('bg', 'particlesBg', {
    plan: function (rng, st) { return { seed: bg1_seed(rng), n: rng.int(60, 100), k: rng.range(0.45, 0.65), sq: rng.chance(0.4) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, s = P.seed || 1, U = jzU(b), px = bg1_px(b), i;
        var area = W * H / (px * px), n = Math.min(140, Math.round((P.n || 60) * Math.sqrt(area / (1920 * 1080))));
        var S = jzShapeLayer(b, 'bg particlesBg', 0, 0), lay = jzLayC(sc, 0.6);
        for (i = 0; i < n; i++) {
            var v = H * bg1_rr(0.04, 0.14, s, i, 1), sz = U * bg1_rr(0.0025, 0.008, s, i, 5), g = jzGrp(S, 'p' + (i + 1));
            if (P.sq) jzAddRect(g, sz * 2, sz * 2); else jzAddEllipse(g, sz * 2, sz * 2);
            jzAddFill(g, bg1_r(s, i, 7) < 0.22 ? sc.accent : lay, (0.55 + 0.45 * bg1_r(s, i, 6)) * 100);
            jzSetExpr(jzGX(g).property('ADBE Vector Position'), bg1_hd(b) + '[' + jzN(bg1_r(s, i, 3) * W) + '+Math.sin(T*' + jzN(bg1_rr(0.4, 1.2, s, i, 4)) + '+' + i + ')*' + jzN(U * 0.02) +
                ',' + jzN(H + 10 * px) + '-wr(T*' + jzN(v) + '+' + jzN(bg1_r(s, i, 2) * H * 1.2) + ',' + jzN(H * 1.2) + ')]');
        }
        bg1_mask(S, -W, H * 0.15, W * 2, H * 3, { f: [0, H * 0.3] });     // fade out towards the top edge
        jzSetExpr(jzXf(S, 'ADBE Opacity'), bg1_hd(b) + jzN((P.k || 0.45) * 100) + '*oc(BT/0.6)');
    }
});

/* ---- ripples \u2014 \u6CE2\u7D0B: rings spreading out every 0.7 s (at the centre or at random spots), a thinner echo ring inside */
jzReg('bg', 'ripples', {
    plan: function (rng, st) { return { seed: bg1_seed(rng), centre: rng.chance(0.4), life: rng.range(1.6, 2.4), k: rng.range(0.24, 0.34) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, s = P.seed || 1, U = jzU(b), life = P.life || 1.8, per = 0.7, t0 = b.cut.start || 0, j, m;
        // ring centres by ring index (same hash as the browser), baked for the rings this cut can show
        var m0 = Math.floor(t0 / per) - 5, m1 = Math.floor((t0 + b.comp.duration) / per) + 1, X = [], Y = [];
        for (m = m0; m <= m1; m++) { X.push(jzN(P.centre ? W / 2 : W * bg1_rr(0.15, 0.85, s, m, 1))); Y.push(jzN(P.centre ? H / 2 : H * bg1_rr(0.2, 0.8, s, m, 2))); }
        var S = jzShapeLayer(b, 'bg ripples', 0, 0);
        for (j = 0; j < 4; j++) {
            var hd = bg1_hd(b) + 'var m=Math.floor(T/' + per + ')-' + j + ',age=T-m*' + per + ',q=cl(age/' + jzN(life) + '),r=' + jzN(U) + '*(0.05+0.75*oc(q)),a=age>' + jzN(life) + '?0:' + jzN((P.k || 0.2) * 100) + '*Math.pow(1-q,1.4);';
            var g = jzGrp(S, 'ring ' + (j + 1));
            var gi = bg1_sub(g, 'echo'), ei = jzAddEllipse(gi, 10, 10);
            jzSetExpr(ei.property('ADBE Vector Ellipse Size'), hd + '[1.6*r,1.6*r]');
            var si = jzAddStroke(gi, sc.fg, U * 0.0015, 50);
            jzSetExpr(si.property('ADBE Vector Stroke Opacity'), hd + 'a*0.5');
            var go = bg1_sub(g, 'ring'), eo = jzAddEllipse(go, 10, 10);
            jzSetExpr(eo.property('ADBE Vector Ellipse Size'), hd + '[2*r,2*r]');
            var so = jzAddStroke(go, sc.fg, U * 0.005, 100);
            jzSetExpr(so.property('ADBE Vector Stroke Opacity'), hd + 'a');
            jzSetExpr(so.property('ADBE Vector Stroke Width'), hd + jzN(U) + '*(0.008*(1-q)+0.002)');
            jzSetExpr(jzGX(g).property('ADBE Vector Position'), hd + 'var X=[' + X.join(',') + '],Y=[' + Y.join(',') + '],i=Math.max(0,Math.min(' + (X.length - 1) + ',m-(' + m0 + ')));[X[i],Y[i]]');
        }
    }
});

/* ---- polka \u2014 \u6C34\u7389: hex polka dots popping in (overshoot), slow diagonal drift */
jzReg('bg', 'polka', {
    plan: function (rng, st) { return { seed: bg1_seed(rng), sp: rng.range(0.11, 0.16), r: rng.range(0.2, 0.3), k: rng.range(0.06, 0.09), acc: rng.chance(0.3) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, sp = jzU(b) * (P.sp || 0.13), r = sp * (P.r || 0.25), rh = sp * 0.866, i;
        var S = jzShapeLayer(b, 'bg polka', 0, 0), g = jzGrp(S, 'dots');
        var sz = bg1_hd(b) + JZ_FNS + 'var d=' + jzN(2 * r) + '*ob(BT/0.45,1.5);[d,d]';
        for (i = 0; i < 2; i++) { var el = jzAddEllipse(g, 2 * r, 2 * r, i * sp / 2, i * rh); jzSetExpr(el.property('ADBE Vector Ellipse Size'), sz); }
        jzAddFill(g, P.acc ? jzTintC(sc, (P.k || 0.07) * 1.8) : jzLayC(sc, P.k || 0.07));
        bg1_rep(g, Math.ceil((W + 3 * sp) / sp) + 1, sp, 0, 0);
        bg1_rep(g, Math.ceil((H + 3 * rh) / (2 * rh)) + 1, 0, 2 * rh, 0);
        jzSetExpr(jzGX(g).property('ADBE Vector Position'), bg1_hd(b) + '[wr(T*' + jzN(sp * 0.25) + ',' + jzN(sp) + ')-' + jzN(2 * sp) + ',wr(T*' + jzN(sp * 0.15) + ',' + jzN(2 * rh) + ')-' + jzN(2 * rh) + ']');
    }
});

/* ---- eqBars \u2014 \u80CC\u666F\u30A4\u30B3\u30E9\u30A4\u30B6\u30FC: a faint spectrum of bars (bottom / mirrored / centred, optionally segmented) with peak caps.
   Levels come from smooth noise + a 2 Hz pulse (the browser follows the audio energy / beats when it has them) */
jzReg('bg', 'eqBars', {
    plan: function (rng, st) { return { seed: bg1_seed(rng), n: rng.pick([24, 32, 40]), mode: rng.pick(['bottom', 'bottom', 'mirror', 'center']), seg: rng.chance(0.4), k: rng.range(0.09, 0.14) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, s = (P.seed || 1) % 997, n = P.n || 32, bw = W / n, bwd = bw * 0.62, Hm = H * 0.32, segH = bw * 0.45, mode = P.mode || 'bottom', i;
        var S = jzShapeLayer(b, 'bg eqBars', 0, 0);
        var lv = function (i) {       // expression: level L (0..1) of bar i, h = its height in px
            return bg1_hd(b) + 'var en=0.45+0.2*noise(T*2+' + jzN(s * 0.37) + '),bt=Math.exp(-wr(T*2,1)*4),nz=0.5+0.5*noise(T*4+' + jzN(i * 1.7 + s * 1.31 + i * 11.3) + '),sh=' + jzN(0.55 + 0.45 * Math.sin(Math.PI * (i + 0.5) / n)) +
                ',L=cl(sh*(0.25+0.75*en)*(0.55+0.45*nz)+bt*0.18*nz)*oc(BT/0.4),h=' + jzN(Hm) + '*L;';
        };
        if (mode !== 'center') {     // peak caps (drawn over the bars)
            var gc = jzGrp(S, 'caps'), ch = Math.max(2 * bg1_px(b), bw * 0.12);
            for (i = 0; i < n; i++) {
                var gci = bg1_sub(gc, 'cap ' + (i + 1)); jzAddRect(gci, bwd, ch);
                jzSetExpr(jzGX(gci).property('ADBE Vector Position'), lv(i) + '[' + jzN(i * bw + bw * 0.5) + ',' + jzN(H - bw * 0.3 + ch / 2) + '-h]');
            }
            jzAddFill(gc, jzTintC(sc, 0.4));
        }
        var gb = jzGrp(S, 'bars');
        for (i = 0; i < n; i++) {
            var xc = i * bw + bw * 0.5, parts = mode === 'center' ? [[H / 2, -1, 0.7], [H / 2, 1, 0.7]] : (mode === 'mirror' ? [[H, -1, 1], [0, 1, 0.7]] : [[H, -1, 1]]), q;
            for (q = 0; q < parts.length; q++) {
                var y0 = parts[q][0], dir = parts[q][1], f = parts[q][2], gi = bg1_sub(gb, 'bar ' + (i + 1) + (q ? 'b' : ''));
                if (P.seg) {      // stacked segments, the Repeater shows as many as fit
                    jzAddRect(gi, bwd, segH * 0.7, 0, 0, dir * segH * 0.35);
                    var rp = bg1_rep(gi, 0, 0, dir * segH, 0);
                    jzSetExpr(rp.property('ADBE Vector Repeater Copies'), lv(i) + 'var hhv=h*' + jzN(f) + ';hhv<' + jzN(segH * 0.7) + '?0:Math.floor((hhv-' + jzN(segH * 0.7) + ')/' + jzN(segH) + ')+1');
                } else {          // one bar growing from its base
                    jzAddRect(gi, bwd, Hm * f, 0, 0, dir * Hm * f / 2);
                    jzSetExpr(jzGX(gi).property('ADBE Vector Scale'), lv(i) + '[100,L*100]');
                }
                jzGX(gi).property('ADBE Vector Position').setValue([xc, y0]);
            }
        }
        jzAddFill(gb, jzLayC(sc, P.k || 0.11));
    }
});

/* ---- borderFrame \u2014 \u592A\u67A0: a thick frame drawn around the picture (two strokes racing round), optional thin inner frame */
jzReg('bg', 'borderFrame', {
    plan: function (rng, st) { return { seed: bg1_seed(rng), th: rng.range(0.012, 0.022), m: rng.range(0.03, 0.05), acc: rng.chance(0.6), dbl: rng.chance(0.4) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, U = jzU(b), m = U * (P.m || 0.04), th = U * (P.th || 0.016), i;
        var cand = P.acc ? [sc.accent, sc.ink, sc.fg] : [sc.ink, sc.fg], col = sc.fg;
        for (i = 0; i < cand.length; i++) if (cand[i] && jzContrast(cand[i], sc.bg) >= 1.6) { col = cand[i]; break; }
        var x0 = m + th / 2, y0 = m + th / 2, x1 = W - m - th / 2, y1 = H - m - th / 2;
        var trim = bg1_hd(b) + 'var x=cl(BT/0.6);100*(x<0.5?4*x*x*x:1-Math.pow(-2*x+2,3)/2)';
        var S = jzShapeLayer(b, 'bg borderFrame', 0, 0);
        if (P.dbl) {
            var m2 = m + th * 2.4, g2 = jzGrp(S, 'inner');
            jzAddPath(g2, [[m2, m2], [W - m2, m2], [W - m2, H - m2], [m2, H - m2], [m2, m2]], false);
            jzAddTrimPaths(g2, trim);
            jzAddStroke(g2, col, Math.max(bg1_px(b), th * 0.18), 45);
        }
        var g = jzGrp(S, 'frame');
        jzAddPath(g, [[x0 - th / 2, y0], [x1, y0], [x1, y1 + th / 2]], false);
        jzAddPath(g, [[x1 + th / 2, y1], [x0, y1], [x0, y0 - th / 2]], false);
        jzAddTrimPaths(g, trim);
        jzAddStroke(g, col, th);
    }
});

/* ---- letterbox \u2014 \u30B7\u30CD\u30B9\u30B3\u5E2F: cinema bars slide in from the top and bottom edges, thin line on their inner edge */
jzReg('bg', 'letterbox', {
    plan: function (rng, st) { return { seed: bg1_seed(rng), k: rng.range(0.085, 0.11), line: rng.chance(0.6) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, px = bg1_px(b), dk = bg1_dark(sc), bh = W >= H ? H * (P.k || 0.1) : H * 0.05, lt = Math.max(1.2 * px, H * 0.0015), i;
        var S = jzShapeLayer(b, 'bg letterbox', 0, 0), hd = bg1_hd(b) + 'var bh=' + jzN(bh) + '*oe(BT/0.55);';
        if (P.line || dk) {
            var gl = jzGrp(S, 'lines');
            for (i = 0; i < 2; i++) {
                var gi = bg1_sub(gl, i ? 'bottom line' : 'top line'); jzAddRect(gi, W, lt);
                jzSetExpr(jzGX(gi).property('ADBE Vector Position'), hd + (i ? '[' + jzN(W / 2) + ',' + jzN(H - lt / 2) + '-bh]' : '[' + jzN(W / 2) + ',bh+' + jzN(lt / 2) + ']'));
            }
            jzAddFill(gl, dk ? jzLayC(sc, 0.3) : jzTintC(sc, 0.6));
            jzSetExpr(jzGX(gl).property('ADBE Vector Group Opacity'), hd + 'bh<0.5?0:100');
        }
        var g = jzGrp(S, 'bars');
        for (i = 0; i < 2; i++) {
            var gb = bg1_sub(g, i ? 'bottom' : 'top');
            jzAddRect(gb, W + 2 * px, bh + px, 0, 0, (i ? -1 : 1) * (bh + px) / 2);
            jzGX(gb).property('ADBE Vector Position').setValue([W / 2, i ? H + px : -px]);
            jzSetExpr(jzGX(gb).property('ADBE Vector Scale'), hd + '[100,100*bh/' + jzN(bh) + ']');
        }
        jzAddFill(g, dk ? jzMixHex(sc.bg, '#000000', 0.85) : jzMixHex(sc.fg, '#000000', 0.3));
    }
});

/* ---- noiseField \u2014 \u30CE\u30A4\u30BA\u306E\u63FA\u3089\u304E: a coarse grid of cells lit where a drifting noise field crosses a threshold (3 tones), stepped flicker.
   AE: Fractal Noise -> Offset (drift + per-step jitter) -> Mosaic (cells) -> Threshold (one layer per tone) -> Tint, blended Lighten / Darken */
jzReg('bg', 'noiseField', {
    plan: function (rng, st) { return { seed: bg1_seed(rng), n: rng.int(14, 22), k: rng.range(0.05, 0.085), th: rng.range(0.56, 0.66) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, s = P.seed || 1, cell = jzU(b) / (P.n || 18), th = P.th || 0.6, k = P.k || 0.07, i;
        var lighter = jzLum(sc.fg) >= jzLum(sc.bg), base = lighter ? [0, 0, 0] : [1, 1, 1], sx = 2;
        var lw = Math.ceil(W / sx), cols = Math.max(1, Math.round(W / cell)), rows = Math.max(1, Math.round(H / cell)), sd = s % 9973;
        var tones = [[0, 0.6], [0.1, 1], [0.2, 1.6]];
        for (i = 0; i < 3; i++) {
            var L = bg1_solid(b, '#808080', 'bg noiseField ' + (i + 1), lw, H);
            jzXf(L, 'ADBE Scale').setValue([sx * 100, 100]);           // noise stretched sideways like the browser's row noise
            jzEffect(L, 'ADBE Fractal Noise', 'JZ Noise');
            var of = jzEffect(L, 'ADBE Offset', 'JZ Drift');
            jzEX(of, 1, bg1_hd(b) + 'var st=Math.floor(T*12+1e-6);seedRandom(' + sd + '+st,true);[value[0]-T*' + jzN(cell * 3 / sx) + '+random(-0.35,0.35)*' + jzN(cell / sx) + ',value[1]+T*' + jzN(cell * 1.7) + '+random(-0.35,0.35)*' + jzN(cell) + ']');
            var mo = jzEffect(L, 'ADBE Mosaic', 'JZ Cells'); jzEP(mo, 1, cols); jzEP(mo, 2, rows); jzEP(mo, 3, 1);
            var tr = jzEffect(L, 'ADBE Threshold2', 'JZ Level');
            jzEX(tr, 1, bg1_hd(b) + '255*Math.min(1,' + jzN(th + tones[i][0]) + '+(1-cl(BT/0.3))*0.4)');
            var tn = jzEffect(L, 'ADBE Tint', 'JZ Tone'); jzEP(tn, 1, base); jzEP(tn, 2, jzHex(jzLayC(sc, k * tones[i][1]))); jzEP(tn, 3, 100);
            L.blendingMode = lighter ? BlendingMode.LIGHTEN : BlendingMode.DARKEN;
        }
        // an occasional accent cell jumping every other step (the browser turns ~1 % of the lit cells)
        var A = jzShapeLayer(b, 'bg noiseField accent', 0, 0), ga = jzGrp(A, 'accent cell');
        jzAddRect(ga, cell - bg1_px(b), cell - bg1_px(b), 0, cell / 2, cell / 2);
        jzAddFill(ga, jzTintC(sc, 0.3));
        var ax = bg1_hd(b) + 'var st=Math.floor(T*6+1e-6);seedRandom(' + (sd + 5000) + '+st*7,true);var px=Math.floor(random(0,' + cols + '))*' + jzN(cell) + ',py=Math.floor(random(0,' + rows + '))*' + jzN(cell) + ',on=random()<0.6;';
        jzSetExpr(jzGX(ga).property('ADBE Vector Position'), ax + '[px,py]');
        jzSetExpr(jzXf(A, 'ADBE Opacity'), ax + '(BT<0.3||!on)?0:100');
    }
});

// ================================================================ background graphics part 2 (AE port of the bg entries in src/11p_bgcamB.js)
// bg.build(b, P): b.comp is the cut's WRAPPER comp (full frame, time 0 = cut start, duration = cut + 1 s). Layers you add sit above the
// scheme colour / paper and below the lyric. Expressions use T = absolute song time (browser env.t) and BT = time since this background
// started (browser bgT, drives the fade-ins). Repeating patterns are ONE shape group + two Repeaters (x / y) that slide by a wrapped offset.

// ================================================================ shared helpers of this pack (prefix bg2_)
// the browser's deterministic hash J.h / J.r (so random placements match the web frame)
function bg2_h(a, b, c, d, e) {
    var h = 0x9e3779b9 ^ (a | 0);
    h = jzImul(h ^ (h >>> 16), 0x85ebca6b);
    h = (h + jzImul((b | 0) + 0x632be5ab, 0xc2b2ae35)) | 0;
    h = jzImul(h ^ (h >>> 13), 0xc2b2ae35);
    h = (h + jzImul((c | 0) + 0x5bd1e995, 0x27d4eb2f)) | 0;
    h = jzImul(h ^ (h >>> 15), 0x165667b1);
    h = (h + jzImul((d | 0) + 0x1b873593, 0x85ebca6b)) | 0;
    h = jzImul(h ^ (h >>> 16), 0x27d4eb2f);
    h = (h + jzImul((e | 0) + 0x68e31da4, 0x9e3779b1)) | 0;
    h ^= h >>> 15; h = jzImul(h, 0x2c1b3c6d); h ^= h >>> 12; h = jzImul(h, 0x297a2d39); h ^= h >>> 15;
    return h >>> 0;
}
function bg2_r(a, b, c, d, e) { return bg2_h(a, b, c, d, e) / 4294967296; }
function bg2_rs(a, b, c, d, e) { return bg2_r(a, b, c, d, e) * 2 - 1; }
function bg2_rr(lo, hi, a, b, c, d, e) { return lo + (hi - lo) * bg2_r(a, b, c, d, e); }
// J.noise1 / fbm1 of the browser (1-D value noise, -1..1) for baked shapes
function bg2_noise1(x, s) { var i = Math.floor(x), f = x - i, u = f * f * (3 - 2 * f), a = bg2_rs(s, i), c = bg2_rs(s, i + 1); return a + (c - a) * u; }
function bg2_fbm1(x, s, oct) { var v = 0, a = 0.5, f = 1, n = 0, i; for (i = 0; i < oct; i++) { v += bg2_noise1(x * f, s + i * 53) * a; n += a; a *= 0.5; f *= 2.1; } return v / n; }
function bg2_smooth(a, b, x) { var t = jzClamp((x - a) / (b - a), 0, 1); return t * t * (3 - 2 * t); }
function bg2_oc(x) { x = jzClamp(x, 0, 1); return 1 - Math.pow(1 - x, 3); }
// seconds this background had already been running at the cut start (browser bgT: consecutive cuts with the same bg + seed are one run)
function bg2_run(b) {
    var c = b.cut, cs = (b.plan && b.plan.cuts) || [], s = c.start || 0, P0 = c.bgP || {}, idx = -1, i;
    for (i = 0; i < cs.length; i++) if (cs[i] === c) { idx = i; break; }
    for (i = idx - 1; i >= 0; i--) {
        var p = cs[i];
        if (p.bg === c.bg && (p.bgP || {}).seed === P0.seed && Math.abs(p.end - s) < 0.06) s = p.start; else break;
    }
    return (c.start || 0) - s;
}
// expression header: T = absolute song time, BT = time since this background started
var BG2_FNS = 'function cl(x){return Math.max(0,Math.min(1,x));}function oc(x){x=cl(x);return 1-Math.pow(1-x,3);}function wr(v,m){return ((v%m)+m)%m;}';
// value noise inside expressions (sin-hash lattice, smoothstep; same statistics as the browser's noise1 / noise2, identical in AE and preview)
var BG2_VN = 'function hs(i,j){var q=Math.sin(i*127.1+j*311.7)*43758.5453;return q-Math.floor(q);}' +
    'function vn1(x,j){var i=Math.floor(x),f=x-i,u=f*f*(3-2*f),a=hs(i,j),c=hs(i+1,j);return (a+(c-a)*u)*2-1;}' +
    'function vn2(x,y){var i=Math.floor(x),j=Math.floor(y),fx=x-i,fy=y-j,u=fx*fx*(3-2*fx),v=fy*fy*(3-2*fy),a=hs(i,j),c=hs(i+1,j),d=hs(i,j+1),e=hs(i+1,j+1);return a+(c-a)*u+(d-a)*v+(a-c-d+e)*u*v;}';
function bg2_hd(b) { return BG2_FNS + 'var T=time+' + jzN(b.cut.start || 0) + ',BT=time+' + jzN(bg2_run(b)) + ';\n'; }
function bg2_px(b) { return jzU(b) / 1080; }                        // one browser design px in comp px
function bg2_dark(sc) { return jzLum(sc.bg) < 0.45; }
// colourful scheme colours that stand apart from the background (browser hues())
function bg2_hues(sc) {
    var c = [sc.accent, sc.accent2, sc.ghostA, sc.ghostB, sc.fg], o = [], i, j;
    for (i = 0; i < c.length; i++) {
        if (!c[i] || jzContrast(c[i], sc.bg) < 1.25) continue;
        var k = String(c[i]).toLowerCase(), dup = false;
        for (j = 0; j < o.length; j++) if (String(o[j]).toLowerCase() === k) dup = true;
        if (!dup) o.push(c[i]);
    }
    return o.length ? o : [sc.fg];
}
function bg2_glowOf(sc) { var L = jzLum(sc.bg), c = [sc.accent, sc.accent2, sc.ghostA, sc.ghostB, sc.fg], i; for (i = 0; i < c.length; i++) if (c[i] && jzLum(c[i]) > L + 0.25) return c[i]; return sc.fg; }
function bg2_lightOn(sc) { return bg2_dark(sc) ? bg2_glowOf(sc) : (jzLum(sc.bg) < 0.78 ? '#FFFFFF' : jzMixHex(sc.accent, '#FFFFFF', 0.2)); }
function bg2_seed(rng) { return rng.int(1, 999999999); }
function bg2_solid(b, hex, name, w, h) { return b.comp.layers.addSolid(jzHex(hex), name, Math.max(4, Math.round(w)), Math.max(4, Math.round(h)), 1, b.comp.duration); }
// rectangle mask in layer space; o: { f: [fx, fy] feather, mode: MaskMode.*, inv: true }
function bg2_mask(L, x0, y0, x1, y1, o) {
    o = o || {};
    var m = L.property('ADBE Mask Parade').addProperty('ADBE Mask Atom'), sh = new Shape();
    sh.vertices = [[x1, y0], [x1, y1], [x0, y1], [x0, y0]]; sh.closed = true;
    m.property('ADBE Mask Shape').setValue(sh);
    if (o.f) m.property('ADBE Mask Feather').setValue(o.f);
    if (o.mode) m.maskMode = o.mode;
    if (o.inv) m.inverted = true;
    return m;
}
function bg2_cmask(L, cx, cy, r, o) {
    o = o || {};
    var m = L.property('ADBE Mask Parade').addProperty('ADBE Mask Atom');
    m.property('ADBE Mask Shape').setValue(jzCircleShape(cx, cy, r));
    if (o.f) m.property('ADBE Mask Feather').setValue(o.f);
    if (o.mode) m.maskMode = o.mode;
    if (o.inv) m.inverted = true;
    return m;
}
// shape Repeater (copies n, offset off, step [dx, dy]) at the end of group g
function bg2_rep(g, n, dx, dy, off) {
    var V = jzVecs(g), ix = V.addProperty('ADBE Vector Filter - Repeater').propertyIndex;
    V.property(ix).property('ADBE Vector Repeater Copies').setValue(n);
    if (off) V.property(ix).property('ADBE Vector Repeater Offset').setValue(off);
    V.property(ix).property('ADBE Vector Repeater Transform').property('ADBE Vector Repeater Position').setValue([dx, dy]);
    return V.property(ix);
}
// a periodic tile (period dx \u00D7 dy) repeated over the frame; the group's position then slides by -(wrapped offset + 2 periods)
function bg2_tile(g, W, H, dx, dy, ex, ey) {
    bg2_rep(g, Math.ceil((W + (ex || 0)) / dx) + 5, dx, 0, 0);
    bg2_rep(g, Math.ceil((H + (ey || 0)) / dy) + 5, 0, dy, 0);
}
function bg2_sub(g, name) { var c = jzVecs(g).addProperty('ADBE Vector Group'); if (name) c.name = name; return c; }   // group inside a group
function bg2_hex(g, x, y, r) { var p = [], m; for (m = 0; m < 6; m++) { var a = (m * 60 - 30) * Math.PI / 180; p.push([x + Math.cos(a) * r, y + Math.sin(a) * r]); } return jzAddPath(g, p, true); }
function bg2_round(st) { try { st.property('ADBE Vector Stroke Line Cap').setValue(2); st.property('ADBE Vector Stroke Line Join').setValue(2); } catch (e) {} return st; }
// dashed stroke (AE starts with an empty Dashes group; the preview model has fixed children)
// (each entry is added AND set before the next one is added: in AE adding to the Dashes group invalidates held references to the others)
function bg2_dashP(D, mn, v) {
    var p = null;
    try { p = D.addProperty(mn); } catch (e) { p = null; }
    if (!p) { try { p = D.property(mn); } catch (e2) { p = null; } }
    if (p) p.setValue(v);
}
function bg2_dash(st, dl, gp) {
    var D = st.property('ADBE Vector Stroke Dashes');
    bg2_dashP(D, 'ADBE Vector Stroke Dash 1', dl);
    bg2_dashP(D, 'ADBE Vector Stroke Gap 1', gp);
}
// L gets the layer directly above it as track matte (and the matte is switched off)
function bg2_matte(L, M, type) { L.trackMatteType = type; M.enabled = false; }

/* ================================================================ GRADIENT */

/* ---- auroraRibbons \u2014 \u30AA\u30FC\u30ED\u30E9: 2-3 curtains of light (bright lower edge, fading upwards) waving sideways, streaked by drifting rays.
   AE: per ribbon one shape layer of columns (ray opacity per column from noise), two feathered masks give the vertical fade,
   Gaussian Blur softens, two sine Wave Warps bend the curtain (the browser's sine + noise baseline) */
jzReg('bg', 'auroraRibbons', {
    plan: function (rng, st) { return { seed: bg2_seed(rng), n: rng.int(2, 3), y: rng.range(0.36, 0.48), k: rng.range(0.2, 0.28), c0: rng.int(0, 3) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, s = P.seed || 1, dk = bg2_dark(sc), px = bg2_px(b), M = 56, cw = W / M, i, r;
        var hu = bg2_hues(sc), lite = [];
        for (i = 0; i < hu.length; i++) if (jzLum(hu[i]) > jzLum(sc.bg) + 0.12) lite.push(hu[i]);
        var lc = dk ? (lite.length ? lite : [bg2_lightOn(sc)]) : [bg2_lightOn(sc), sc.accent];
        var kk = (P.k || 0.24) * (dk ? 1.25 : 0.8), n = P.n || 2, hd = bg2_hd(b) + BG2_VN;
        for (r = 0; r < n; r++) {
            var col = lc[((P.c0 || 0) + r) % lc.length], base = H * ((P.y || 0.42) + r * 0.1 - 0.05), dir = r % 2 ? -1 : 1, ph = (s % 97) * 0.13 + r * 2.1;
            var h = Math.max(W, H) * 0.3 * (1 - 0.1 * r), sr = (s % 89) + r * 17;
            var S = jzShapeLayer(b, 'bg auroraRibbons ' + (r + 1), 0, 0);
            for (i = 0; i < M; i++) {
                var u = (i + 0.5) / M, ed = bg2_smooth(0, 0.18, u) * bg2_smooth(1, 0.82, u);
                var g = jzGrp(S, 'ray ' + (i + 1));
                jzAddRect(g, cw + 2 * px, h * 1.07, 0, (i + 0.5) * cw, base - h + h * 0.535);
                var f = jzAddFill(g, col);
                jzSetExpr(f.property('ADBE Vector Fill Opacity'), hd + 'var n1=vn1(' + jzN(u * 34) + '+T*' + jzN(0.9 * dir) + ',' + (sr + 31) + '),n2=vn1(' + jzN(u * 13) + '-T*0.4,' + (sr + 3) + '),n3=vn1(' + jzN(u * 1.5) + '+T*0.1,' + sr + ');' +
                    'Math.min(100,' + jzN(100 * kk * ed) + '*(0.2+1.1*Math.pow(0.5+0.5*n1,1.4)*(0.7+0.3*n2))*(0.75+0.25*n3))');
            }
            // vertical profile: transparent top -> bright band just above the base line -> quick fade below it
            // (two identical intersecting linear ramps = r^2, the browser strip's convex 0 / 0.22 / 0.85 / 1 profile)
            bg2_mask(S, -W, base - 0.475 * h, 2 * W, base + 3 * h, { f: [0, 0.85 * h] });
            bg2_mask(S, -W, base - 0.475 * h, 2 * W, base + 3 * h, { f: [0, 0.85 * h], mode: MaskMode.INTERSECT });
            bg2_mask(S, -W, base - 3 * h, 2 * W, base + 0.035 * h, { f: [0, 0.08 * h], mode: MaskMode.INTERSECT });
            var bl = jzEffect(S, 'ADBE Gaussian Blur 2', 'JZ Aurora Soft'); jzEP(bl, 1, cw * 1.1); jzEP(bl, 3, 0);
            var wv = [[H * 0.07, W / 0.75, ph, 0.3 * dir], [H * 0.035, W / 1.9, ph * 1.7 + 1, 0.69 * dir]], q;
            for (q = 0; q < 2; q++) {
                var ww = jzEffect(S, 'ADBE Wave Warp', 'JZ Aurora Wave ' + (q + 1));
                jzEP(ww, 1, 1); jzEP(ww, 4, 90); jzEP(ww, 5, 0); jzEP(ww, 6, 1);
                jzEX(ww, 2, jzN(wv[q][0])); jzEX(ww, 3, jzN(wv[q][1]));
                jzEX(ww, 7, hd + '-(' + jzN(wv[q][2]) + '+T*' + jzN(wv[q][3]) + ')*180/Math.PI');
            }
            jzSetExpr(jzXf(S, 'ADBE Opacity'), hd + '100*oc(BT/1.2)');
        }
    }
});

/* ---- meshBlobs \u2014 \u30E1\u30C3\u30B7\u30E5\u30B0\u30E9\u30C7: 3-4 huge soft discs in the scheme's hues wandering on Lissajous paths (solid + feathered circle mask each) */
jzReg('bg', 'meshBlobs', {
    plan: function (rng, st) { return { seed: bg2_seed(rng), n: rng.int(3, 4), k: rng.range(0.2, 0.3) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, s = P.seed || 1, cols = bg2_hues(sc), dk = bg2_dark(sc), R0 = Math.max(W, H) * 0.5, n = P.n || 3, i;
        for (i = 0; i < n; i++) {
            var w1 = bg2_rr(0.16, 0.3, s, i, 1), w2 = bg2_rr(0.14, 0.26, s, i, 2), R = R0 * bg2_rr(0.75, 1.1, s, i, 5);
            var c = jzMixHex(sc.bg, cols[i % cols.length], (P.k || 0.25) * (dk ? 1 : 0.75)), D = Math.ceil(R * 2.1);
            var L = bg2_solid(b, c, 'bg meshBlobs ' + (i + 1), D, D);
            bg2_cmask(L, D / 2, D / 2, R * 0.5, { f: [R, R] });          // alpha 1 at the centre -> 0.5 at R/2 -> 0 at R
            jzSetExpr(jzXf(L, 'ADBE Position'), bg2_hd(b) + '[' + jzN(W) + '*(0.5+0.42*Math.sin(T*' + jzN(w1) + '+' + jzN(bg2_r(s, i, 3) * 2 * Math.PI) + ')),' +
                jzN(H) + '*(0.5+0.4*Math.cos(T*' + jzN(w2) + '+' + jzN(bg2_r(s, i, 4) * 2 * Math.PI) + '))]');
            jzSetExpr(jzXf(L, 'ADBE Scale'), bg2_hd(b) + 'var f=100*(1+0.08*Math.sin(T*0.45+' + jzN(i * 1.7) + '));[f,f]');
            jzSetExpr(jzXf(L, 'ADBE Opacity'), bg2_hd(b) + '95*oc(BT/1)');
        }
    }
});

/* ---- duotoneSweep \u2014 \u4E8C\u8272\u30B9\u30A4\u30FC\u30D7: two tones swept round an (often off-frame) centre, turning slowly, with a faint sheen.
   AE: 4-Color Gradient whose points (A, B, A, B) turn on a circle round the centre (\u2248 the browser's conic gradient);
   the sheen = two narrow nested wedges of light turning 1.6x faster */
jzReg('bg', 'duotoneSweep', {
    plan: function (rng, st) { return { seed: bg2_seed(rng), pos: rng.pick([[0.5, 1.2], [-0.15, 1.1], [1.15, 1.1], [0.5, -0.2], [-0.1, -0.1]]), spd: rng.range(0.08, 0.14) * rng.pick([1, -1]), k: rng.range(0.14, 0.2), a0: rng.range(0, 6.28) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, U = jzU(b), cols = bg2_hues(sc), k = P.k || 0.16, j, q;
        var cA = jzMixHex(sc.bg, cols[0], k), cB = jzMixHex(sc.bg, cols[1] || sc.fg, k);
        var pos = P.pos || [0.5, 1.2], cx = W * pos[0], cy = H * pos[1];
        var dC = Math.sqrt((cx - W / 2) * (cx - W / 2) + (cy - H / 2) * (cy - H / 2)), rho = dC * 0.9 + U * 0.25;
        var hd = bg2_hd(b) + 'var a=' + jzN(P.a0 || 0) + '+T*' + jzN(P.spd || 0.1) + ';';
        var L = bg2_solid(b, cA, 'bg duotoneSweep', W, H);
        var fx = jzEffect(L, 'ADBE 4ColorGradient', 'JZ Duotone');
        for (j = 0; j < 4; j++) {
            jzEX(fx, 1 + 2 * j, hd + 'var q=a+' + jzN(j * Math.PI / 2) + ';[' + jzN(cx) + '+Math.cos(q)*' + jzN(rho) + ',' + jzN(cy) + '+Math.sin(q)*' + jzN(rho) + ']');
            jzEP(fx, 2 + 2 * j, jzHex(j % 2 ? cB : cA));
        }
        jzSetExpr(jzXf(L, 'ADBE Opacity'), hd + '90*oc(BT/0.8)');
        // sheen: bands centred at 0.06 and 0.62 turns after the (1.6x faster) sheen angle; three nested wedges = triangular profile
        var Rw = Math.sqrt(W * W + H * H) * 1.8, S = jzShapeLayer(b, 'bg duotoneSweep sheen', cx, cy), hl = bg2_lightOn(sc);
        for (j = 0; j < 2; j++) {
            var g = jzGrp(S, 'sheen ' + (j + 1));
            for (q = 1; q <= 3; q++) {
                var w = 0.015 * q * 2 * Math.PI, gq = bg2_sub(g, 'band ' + q);
                jzAddPath(gq, [[0, 0], [Math.cos(-w) * Rw, Math.sin(-w) * Rw], [Math.cos(w) * Rw, Math.sin(w) * Rw]], true);
                jzAddFill(gq, hl, 2.5);
            }
            jzSetExpr(jzGX(g).property('ADBE Vector Rotation'), hd + '(a*1.6+1+' + jzN((j ? 0.62 : 0.06) * 2 * Math.PI) + ')*180/Math.PI');
        }
        var bl = jzEffect(S, 'ADBE Gaussian Blur 2', 'JZ Sheen Soft'); jzEP(bl, 1, U * 0.02); jzEP(bl, 3, 0);
        jzSetExpr(jzXf(S, 'ADBE Opacity'), hd + '100*oc(BT/0.8)');
    }
});

/* ---- horizonGlow \u2014 \u60D1\u661F\u306E\u7E01: the limb of a huge planet rising at the bottom, a breathing atmosphere glow, a flare sliding along the rim.
   AE: planet = shape ellipse; glow = two frame-sized solids with feathered ring masks (they move with the planet), limb stroke, flare disc */
jzReg('bg', 'horizonGlow', {
    plan: function (rng, st) { return { seed: bg2_seed(rng), cx: rng.range(0.3, 0.7), R: rng.range(1.3, 2.1), top: rng.range(0.7, 0.8), k: rng.range(0.28, 0.4) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, U = jzU(b), dk = bg2_dark(sc), s = P.seed || 1, px = bg2_px(b), i;
        var R = Math.max(W, H) * (P.R || 1.6), cx0 = W * (P.cx || 0.5), cy0 = H * (P.top || 0.75) + R;
        var gc = dk ? bg2_glowOf(sc) : jzTintC(sc, 0.8), k = (P.k || 0.32) * (dk ? 1 : 0.55);
        var hd = bg2_hd(b) + 'var e=oc(BT/1.1),dx=' + jzN(W * 0.03) + '*Math.sin(T*0.08+' + (s % 9) + '),dy=(1-e)*' + jzN(H * 0.25) + '+' + jzN(H * 0.008) + '*Math.sin(T*0.25),br=0.85+0.15*Math.sin(T*0.7);';
        var cen = hd + '[' + jzN(cx0) + '+dx,' + jzN(cy0) + '+dy]';
        // the planet
        var D = jzShapeLayer(b, 'bg horizonGlow planet', 0, 0), gd = jzGrp(D, 'planet');
        jzAddEllipse(gd, 2 * R, 2 * R);
        jzAddFill(gd, dk ? jzMixHex(sc.bg, '#000000', 0.42) : jzMixHex(sc.bg, sc.fg, 0.05));
        jzSetExpr(jzGX(gd).property('ADBE Vector Position'), cen);
        jzSetExpr(jzXf(D, 'ADBE Opacity'), hd + '100*e');
        // atmosphere: wide soft ring + tight bright ring (solids 1.1W x 1.4H, top-left at (-0.05W, -0.35H) when the planet rests)
        var ox = W * 0.05, oy = H * 0.35, spec = [[1.09, 0.26, k * 32], [1.008, 0.03, k * 100]];
        for (i = 0; i < 2; i++) {
            var G = bg2_solid(b, gc, 'bg horizonGlow atmosphere ' + (i + 1), W * 1.1, H * 1.4);
            bg2_cmask(G, cx0 + ox, cy0 + oy, R * spec[i][0], { f: [R * spec[i][1], R * spec[i][1]] });
            bg2_cmask(G, cx0 + ox, cy0 + oy, R * 0.97, { mode: MaskMode.SUBTRACT });
            jzSetExpr(jzXf(G, 'ADBE Position'), hd + '[' + jzN(W * 0.55 - ox) + '+dx,' + jzN(H * 0.7 - oy) + '+dy]');
            jzSetExpr(jzXf(G, 'ADBE Opacity'), hd + 'Math.min(100,' + jzN(spec[i][2]) + '*br*e)');
        }
        // the limb line
        var Lm = jzShapeLayer(b, 'bg horizonGlow limb', 0, 0), gl = jzGrp(Lm, 'limb');
        jzAddEllipse(gl, 2 * R, 2 * R);
        jzAddStroke(gl, gc, Math.max(px, U * 0.0022), Math.min(80, k * 160));
        jzSetExpr(jzGX(gl).property('ADBE Vector Position'), cen);
        jzSetExpr(jzXf(Lm, 'ADBE Opacity'), hd + '100*e');
        // a flare sliding slowly along the limb
        var fr = U * 0.3, F = bg2_solid(b, dk ? gc : bg2_lightOn(sc), 'bg horizonGlow flare', fr * 2, fr * 2);
        bg2_cmask(F, fr, fr, fr * 0.4, { f: [fr * 0.8, fr * 0.8] });
        jzSetExpr(jzXf(F, 'ADBE Position'), hd + 'var fa=-Math.PI/2+0.22*Math.sin(T*0.12+' + (s % 13) + ');[' + jzN(cx0) + '+dx+Math.cos(fa)*' + jzN(R) + ',' + jzN(cy0) + '+dy+Math.sin(fa)*' + jzN(R) + ']');
        jzSetExpr(jzXf(F, 'ADBE Opacity'), hd + 'Math.min(100,' + jzN(k * 90) + '*br*e)');
    }
});

/* ================================================================ PATTERN */

/* ---- seigaiha \u2014 \u9752\u6D77\u6CE2: overlapping wave scales (concentric arcs + a small centre dot), drifting sideways and bobbing.
   AE: the visible part of every arc is computed once (later rows cover earlier ones), one scale pair per tile, Repeaters */
function bg2_seigaiha(R, rings, lw) {
    var cov = [[R, R / 2], [-R, R / 2], [0, R], [R, 1.5 * R], [-R, 1.5 * R], [0, 2 * R]], D2R = Math.PI / 180, arcs = [], m, j, q;
    var vis = function (x, y) { for (var c = 0; c < cov.length; c++) { var dx = x - cov[c][0], dy = y - cov[c][1]; if (dx * dx + dy * dy < R * R * 0.9999) return false; } return true; };
    for (m = 0; m < rings; m++) {
        var r = R * (1 - m / rings) - lw * 0.6, al = 0;
        if (r <= lw) continue;
        while (al < 180 && vis(r * Math.cos((-90 + al + 0.5) * D2R), r * Math.sin((-90 + al + 0.5) * D2R))) al += 0.5;
        if (al < 1) continue;
        var np = Math.max(3, Math.ceil(al / 3)) * 2 + 1, pts = [];
        for (j = 0; j < np; j++) { var an = (-90 - al + 2 * al * j / (np - 1)) * D2R; pts.push([r * Math.cos(an), r * Math.sin(an)]); }
        arcs.push(pts);
    }
    var rd = R / rings * 0.62, dot = [];
    for (q = 0; q < 36; q++) {
        var a2 = q * 10 * D2R, rho = 0;
        while (rho < rd && vis(Math.cos(a2) * (rho + rd / 16), Math.sin(a2) * (rho + rd / 16))) rho += rd / 16;
        dot.push([Math.cos(a2) * rho, Math.sin(a2) * rho]);
    }
    return { arcs: arcs, dot: dot };
}
function bg2_shift(pts, x, y) { var o = [], i; for (i = 0; i < pts.length; i++) o.push([pts[i][0] + x, pts[i][1] + y]); return o; }
jzReg('bg', 'seigaiha', {
    plan: function (rng, st) { return { seed: bg2_seed(rng), R: rng.range(0.07, 0.1), rings: rng.int(3, 4), k: rng.range(0.075, 0.1), dir: rng.pick([1, -1]), acc: rng.chance(0.3) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, R = jzU(b) * (P.R || 0.08), rings = P.rings || 4, k = P.k || 0.09, lw = R * 0.075, i, c;
        var col = P.acc ? jzTintC(sc, k * 1.6) : jzLayC(sc, k), fc = jzLayC(sc, k * 0.35), geo = bg2_seigaiha(R, rings, lw), C = [[0, 0], [R, R / 2]];
        var S = jzShapeLayer(b, 'bg seigaiha', 0, 0), g = jzGrp(S, 'waves');
        var gs = bg2_sub(g, 'rings');
        for (c = 0; c < 2; c++) for (i = 0; i < geo.arcs.length; i++) jzAddPath(gs, bg2_shift(geo.arcs[i], C[c][0], C[c][1]), false);
        jzAddStroke(gs, col, lw);
        var gd = bg2_sub(g, 'dots');
        for (c = 0; c < 2; c++) jzAddPath(gd, bg2_shift(geo.dot, C[c][0], C[c][1]), true);
        jzAddFill(gd, fc);
        bg2_tile(g, W, H, 2 * R, R, 2 * R, 2 * R);
        var hd = bg2_hd(b) + 'var e=oc(BT/0.7);';
        jzSetExpr(jzGX(g).property('ADBE Vector Position'), hd + '[-wr(T*' + jzN(R * 0.22 * (P.dir || 1)) + ',' + jzN(2 * R) + ')-' + jzN(4 * R) +
            ',-wr(' + jzN(R * 0.5) + '+Math.sin(T*0.5)*' + jzN(R * 0.08) + '-(1-e)*' + jzN(R * 0.5) + ',' + jzN(R) + ')-' + jzN(2 * R) + ']');
        jzSetExpr(jzXf(S, 'ADBE Opacity'), hd + '100*e');
    }
});

/* ---- asanoha \u2014 \u9EBB\u306E\u8449: hemp-leaf lattice (triangles + spokes to their centres) drifting slowly; a diagonal band of light sweeps across
   and draws the lines stronger (AE: a stronger copy of the lattice under a soft moving band matte) */
function bg2_asanoha(g, a, h) {
    var T = [[[0, 0], [a, 0], [a / 2, h]], [[a / 2, h], [1.5 * a, h], [a, 0]], [[a / 2, h], [1.5 * a, h], [a, 2 * h]], [[0, 2 * h], [a, 2 * h], [a / 2, h]]], i;
    for (i = 0; i < 4; i++) {
        var A = T[i][0], B = T[i][1], C = T[i][2], G = [(A[0] + B[0] + C[0]) / 3, (A[1] + B[1] + C[1]) / 3];
        jzAddPath(g, [A, B, C], true);
        jzAddPath(g, [A, G, B], false);
        jzAddPath(g, [G, C], false);
    }
}
jzReg('bg', 'asanoha', {
    plan: function (rng, st) { return { seed: bg2_seed(rng), a: rng.range(0.1, 0.14), k: rng.range(0.09, 0.12), dx: rng.pick([1, -1]), sweep: rng.range(0.07, 0.12) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, U = jzU(b), px = bg2_px(b), a = U * (P.a || 0.12), h = a * Math.sqrt(3) / 2, k = P.k || 0.09, lw = Math.max(0.8 * px, a * 0.018);
        var hd = bg2_hd(b) + 'var e=oc(BT/0.8);';
        var pos = hd + '[-wr(T*' + jzN(a * 0.08 * (P.dx || 1)) + ',' + jzN(a) + ')-' + jzN(2 * a) + ',-wr(T*' + jzN(h * 0.05) + '+(1-e)*' + jzN(h) + ',' + jzN(2 * h) + ')-' + jzN(4 * h) + ']';
        var lat = function (name, c, w) {
            var L = jzShapeLayer(b, name, 0, 0), g = jzGrp(L, 'asanoha');
            bg2_asanoha(g, a, h); bg2_round(jzAddStroke(g, c, w));
            bg2_tile(g, W, H, a, 2 * h, 2 * a, 2 * h);
            jzSetExpr(jzGX(g).property('ADBE Vector Position'), pos);
            jzSetExpr(jzXf(L, 'ADBE Opacity'), hd + '100*e');
            return L;
        };
        lat('bg asanoha', jzLayC(sc, k), lw);
        var S2 = lat('bg asanoha sheen', jzLayC(sc, k * 1.3), lw * 1.15);
        // the band: parallelogram leaning 45\u00B0 (x - y), soft edges; position = the browser's sweep centre (bgT based)
        var bw = U * 0.22, M = jzShapeLayer(b, 'bg asanoha band', 0, 0), gm = jzGrp(M, 'band'), hw = bw * 0.6;
        jzAddPath(gm, [[-hw + 4 * px, -4 * px], [hw + 4 * px, -4 * px], [hw - H - 4 * px, H + 4 * px], [-hw - H - 4 * px, H + 4 * px]], true);
        jzAddFill(gm, '#FFFFFF');
        jzSetExpr(jzGX(gm).property('ADBE Vector Position'), bg2_hd(b) + '[(wr(BT*' + jzN(P.sweep || 0.09) + '+0.4,1.5)-0.25)*' + jzN(W + H) + ',0]');
        var bl = jzEffect(M, 'ADBE Gaussian Blur 2', 'JZ Band Soft'); jzEP(bl, 1, bw * 0.6); jzEP(bl, 3, 0);
        bg2_matte(S2, M, TrackMatteType.ALPHA);
    }
});

/* ---- houndstooth \u2014 \u5343\u9CE5\u683C\u5B50: the classic broken check, sliding diagonally */
jzReg('bg', 'houndstooth', {
    plan: function (rng, st) { return { seed: bg2_seed(rng), c: rng.range(0.04, 0.055), k: rng.range(0.055, 0.075), dx: rng.pick([1, -1]), acc: rng.chance(0.25) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, c = jzU(b) * (P.c || 0.045), k = P.k || 0.065, i;
        var col = P.acc ? jzTintC(sc, k * 1.7) : jzLayC(sc, k);
        var S = jzShapeLayer(b, 'bg houndstooth', 0, 0), g = jzGrp(S, 'tooth'), B = [[c, 0], [0, c]];
        jzAddRect(g, c, c, 0, c / 2, c / 2);
        for (i = 0; i < 2; i++) {
            var bx = B[i][0], by = B[i][1];
            jzAddPath(g, [[bx, by], [bx + c / 2, by], [bx, by + c / 2]], true);
            jzAddPath(g, [[bx + c, by], [bx + c, by + c / 2], [bx + c / 2, by + c], [bx, by + c]], true);
        }
        jzAddFill(g, col);
        bg2_tile(g, W, H, 2 * c, 2 * c, 2 * c, 2 * c);
        var hd = bg2_hd(b);
        jzSetExpr(jzGX(g).property('ADBE Vector Position'), hd + 'var d=T*' + jzN(c * 0.35) + ';[-wr(d*' + (P.dx || 1) + ',' + jzN(2 * c) + ')-' + jzN(4 * c) + ',-wr(d*0.6,' + jzN(2 * c) + ')-' + jzN(4 * c) + ']');
        jzSetExpr(jzXf(S, 'ADBE Opacity'), hd + '100*oc(BT/0.6)');
    }
});

/* ---- herringbone \u2014 \u30D8\u30EA\u30F3\u30DC\u30FC\u30F3: two-tone brick weave at \u00B145\u00B0, travelling along its zig-zag columns, a slow sheen crossing it.
   AE: one brick pair + two Repeaters along the weave's own lattice vectors (u,u) and (2u,-2u) in a rotated shape layer */
jzReg('bg', 'herringbone', {
    plan: function (rng, st) { return { seed: bg2_seed(rng), u: rng.range(0.032, 0.045), k: rng.range(0.07, 0.1), dir: rng.pick([1, -1]), rot: rng.pick([45, 45, -45]) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, U = jzU(b), u = U * (P.u || 0.038), k = P.k || 0.08, rot = P.rot || 45, gp = u * 0.1, dk = bg2_dark(sc);
        var hd = bg2_hd(b);
        var S = jzShapeLayer(b, 'bg herringbone', W / 2, H / 2);
        jzXf(S, 'ADBE Rotate Z').setValue(rot);
        var g = jzGrp(S, 'weave');
        var ga = bg2_sub(g, 'across'); jzAddRect(ga, 2 * u - 2 * gp, u - 2 * gp, 0, u, u / 2); jzAddFill(ga, jzLayC(sc, k));
        var gb = bg2_sub(g, 'along'); jzAddRect(gb, u - 2 * gp, 2 * u - 2 * gp, 0, 2.5 * u, 0); jzAddFill(gb, jzLayC(sc, k * 0.45));
        // copies needed along each (orthogonal) lattice vector to cover the frame once rotated
        var th = rot * Math.PI / 180, V = [[1, 1], [2, -2]], i;
        for (i = 0; i < 2; i++) {
            var L = Math.sqrt(V[i][0] * V[i][0] + V[i][1] * V[i][1]), sx = (Math.cos(th) * V[i][0] - Math.sin(th) * V[i][1]) / L, sy = (Math.sin(th) * V[i][0] + Math.cos(th) * V[i][1]) / L;
            var half = Math.abs(sx) * W / 2 + Math.abs(sy) * H / 2 + 6 * u, n = 2 * Math.ceil(half / (L * u)) + 2;
            bg2_rep(g, n, V[i][0] * u, V[i][1] * u, -n / 2);
        }
        var sp = 'wr(T*' + jzN(u * 0.6 * (P.dir || 1)) + ',' + jzN(Math.SQRT2 * u) + ')';
        jzSetExpr(jzXf(S, 'ADBE Position'), hd + (rot > 0 ? '[' + jzN(W / 2) + ',' + jzN(H / 2) + '-' + sp + ']' : '[' + jzN(W / 2) + '-' + sp + ',' + jzN(H / 2) + ']'));
        jzSetExpr(jzXf(S, 'ADBE Opacity'), hd + '100*oc(BT/0.7)');
        // sheen band (leaning like the browser's x - 0.6y skew), soft sideways
        var bw = U * 0.35, SH = jzShapeLayer(b, 'bg herringbone sheen', 0, 0), gs = jzGrp(SH, 'sheen'), y0 = -H * 0.05, y1 = H * 1.05;
        jzAddPath(gs, [[-bw / 2 - 0.6 * y0, y0], [bw / 2 - 0.6 * y0, y0], [bw / 2 - 0.6 * y1, y1], [-bw / 2 - 0.6 * y1, y1]], true);
        jzAddFill(gs, bg2_lightOn(sc));
        jzSetExpr(jzGX(gs).property('ADBE Vector Position'), hd + '[(wr(T*0.1,1.6)-0.3)*' + jzN(W + H) + ',0]');
        var bl = jzEffect(SH, 'ADBE Gaussian Blur 2', 'JZ Sheen Soft'); jzEP(bl, 1, bw * 0.5); jzEP(bl, 2, 2); jzEP(bl, 3, 0);
        jzSetExpr(jzXf(SH, 'ADBE Opacity'), hd + jzN((dk ? 3.5 : 8) * 1.2) + '*oc(BT/0.7)');
    }
});

/* ---- argyle \u2014 \u30A2\u30FC\u30AC\u30A4\u30EB: a checker of two-tone diamonds drifting vertically + dashed diagonal over-check lines moving on their own */
jzReg('bg', 'argyle', {
    plan: function (rng, st) { return { seed: bg2_seed(rng), dw: rng.range(0.16, 0.22), asp: rng.range(1.3, 1.5), k: rng.range(0.06, 0.085), up: rng.pick([1, -1]) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, px = bg2_px(b), dw = jzU(b) * (P.dw || 0.18), dh = dw * (P.asp || 1.4), k = P.k || 0.07, i;
        var hd = bg2_hd(b) + 'var e=oc(BT/0.6),d=T*' + jzN(dh * 0.1 * (P.up || 1)) + ';';
        var dia = function (g, x, y) { jzAddPath(g, [[x, y - dh / 2], [x + dw / 2, y], [x, y + dh / 2], [x - dw / 2, y]], true); };
        var S = jzShapeLayer(b, 'bg argyle', 0, 0), g = jzGrp(S, 'diamonds');
        var gA = bg2_sub(g, 'A'); dia(gA, 0, 0); dia(gA, dw, dh); jzAddFill(gA, jzLayC(sc, k));
        var gB = bg2_sub(g, 'B'); dia(gB, dw, 0); dia(gB, 0, dh); jzAddFill(gB, jzTintC(sc, k * 1.6));
        bg2_tile(g, W, H, 2 * dw, 2 * dh, 4 * dw, 4 * dh);
        jzSetExpr(jzGX(g).property('ADBE Vector Position'), hd + '[-wr(' + jzN(dw - W / 2) + ',' + jzN(2 * dw) + ')-' + jzN(4 * dw) + ',-wr(d-(1-e)*' + jzN(dh * 0.3) + ',' + jzN(2 * dh) + ')-' + jzN(4 * dh) + ']');
        jzSetExpr(jzXf(S, 'ADBE Opacity'), hd + '100*e');
        // over-check: long dashed diagonals through the diamonds' corners (dash = 1/12 of one diamond edge, as in the browser tile)
        var LL = jzShapeLayer(b, 'bg argyle lines', 0, 0), gl = jzGrp(LL, 'lines'), NT = Math.ceil(H / dh) + 3, nx = Math.ceil(W / dw) + NT + 6;
        for (i = 0; i < 2; i++) {
            var gi = bg2_sub(gl, i ? 'falling left' : 'falling right'), sgn = i ? -1 : 1, x0 = -sgn * dw / 2;
            jzAddPath(gi, [[x0 - sgn * 2 * dw, -2 * dh], [x0 + sgn * NT * dw, NT * dh]], false);
            bg2_rep(gi, nx, dw, 0, i ? -3 : -(NT + 3));
        }
        var Ld = Math.sqrt(dw * dw + dh * dh), st = jzAddStroke(gl, jzLayC(sc, k * 3), Math.max(0.8 * px, dw * 0.012));
        bg2_dash(st, Ld / 12, Ld / 12);
        jzSetExpr(jzGX(gl).property('ADBE Vector Position'), hd + '[-wr(' + jzN(dw / 2 - W / 2) + '-d*0.6,' + jzN(dw) + '),-wr(-d,' + jzN(dh) + ')]');
        jzSetExpr(jzXf(LL, 'ADBE Opacity'), hd + '90*e');
    }
});

/* ---- tartan \u2014 \u30BF\u30FC\u30BF\u30F3: a mirrored sett of stripes crossed with itself (translucent), warp drifting sideways, weft drifting up */
jzReg('bg', 'tartan', {
    plan: function (rng, st) { return { seed: bg2_seed(rng), S: rng.range(0.34, 0.5), w1: rng.range(0.18, 0.28), w2: rng.range(0.08, 0.13), k: rng.range(0.085, 0.115), acc: rng.chance(0.6) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, S0 = jzU(b) * (P.S || 0.42), k = P.k || 0.1, w1 = P.w1 || 0.22, w2 = P.w2 || 0.1, o, q, m;
        var cA = jzLayC(sc, k), cB = P.acc ? jzTintC(sc, k * 1.5) : jzLayC(sc, k * 0.6), cL = jzLayC(sc, k * 1.6), cT = jzTintC(sc, k * 2.4);
        var sett = [[0, w1, cA, 0.6], [w1 + 0.04, 0.012, cL, 0.9], [w1 + 0.1, w2, cB, 0.55], [0.72, 0.008, cT, 0.9], [0.84, 0.02, cL, 0.7]];
        var hd = bg2_hd(b), S = jzShapeLayer(b, 'bg tartan', 0, 0);
        for (o = 0; o < 2; o++) {      // weft (horizontal) first = drawn on top
            var hz = o === 0, g = jzGrp(S, hz ? 'weft' : 'warp'), span = (hz ? W : H) + 4 * S0;
            for (q = 0; q < sett.length; q++) {
                var gi = bg2_sub(g, 'stripe ' + (q + 1)), p0 = sett[q][0], w = sett[q][1], xs = [(p0 + w / 2) * S0, S0 - (p0 + w / 2) * S0];
                for (m = 0; m < 2; m++) {
                    if (hz) jzAddRect(gi, span, w * S0, 0, (hz ? W : H) / 2, xs[m]);
                    else jzAddRect(gi, w * S0, span, 0, xs[m], H / 2);
                }
                jzAddFill(gi, sett[q][2], sett[q][3] * 100);
            }
            var n = Math.ceil((hz ? H : W) / S0) + 4;
            if (hz) { bg2_rep(g, n, 0, S0, 0); jzSetExpr(jzGX(g).property('ADBE Vector Position'), hd + '[0,-wr(T*' + jzN(S0 * 0.025) + '+' + jzN(S0 * 0.37) + ',' + jzN(S0) + ')-' + jzN(S0) + ']'); }
            else { bg2_rep(g, n, S0, 0, 0); jzSetExpr(jzGX(g).property('ADBE Vector Position'), hd + '[-wr(-T*' + jzN(S0 * 0.035) + ',' + jzN(S0) + ')-' + jzN(S0) + ',0]'); }
        }
        jzSetExpr(jzXf(S, 'ADBE Opacity'), hd + '100*oc(BT/0.8)');
    }
});

/* ---- chevron \u2014 \u5C71\u5F62: zig-zag bands flowing up or down, swaying a little sideways */
jzReg('bg', 'chevron', {
    plan: function (rng, st) { return { seed: bg2_seed(rng), p: rng.range(0.13, 0.19), amp: rng.range(0.28, 0.42), k: rng.range(0.055, 0.075), dir: rng.pick([1, -1]), acc: rng.chance(0.3) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, px = bg2_px(b), p = jzU(b) * (P.p || 0.16), D = p * 0.5, A = p * (P.amp || 0.35), k = P.k || 0.065;
        var col = P.acc ? jzTintC(sc, k * 1.7) : jzLayC(sc, k), y0 = A / 2;
        var S = jzShapeLayer(b, 'bg chevron', 0, 0), g = jzGrp(S, 'chevrons');
        jzAddPath(g, [[-px, y0], [p / 2, y0 - A], [p + px, y0], [p + px, y0 + D / 2], [p / 2, y0 - A + D / 2], [-px, y0 + D / 2]], true);
        jzAddFill(g, col);
        bg2_tile(g, W, H, p, D, 2 * p, 2 * D + 2 * A);
        var hd = bg2_hd(b);
        jzSetExpr(jzGX(g).property('ADBE Vector Position'), hd + '[-wr(' + jzN(p / 2 - W / 2) + '+Math.sin(T*0.3)*' + jzN(p * 0.1) + ',' + jzN(p) + ')-' + jzN(2 * p) +
            ',-wr(-T*' + jzN(D * 0.4 * (P.dir || 1)) + '+(1-oc(BT/0.6))*' + jzN(D) + ',' + jzN(D) + ')-' + jzN(2 * D) + ']');
        jzSetExpr(jzXf(S, 'ADBE Opacity'), hd + '100*oc(BT/0.6)');
    }
});

/* ---- isoCubes \u2014 \u7ACB\u65B9\u4F53: isometric cube lattice; the light turns slowly so top / left / right faces trade brightness */
jzReg('bg', 'isoCubes', {
    plan: function (rng, st) { return { seed: bg2_seed(rng), s: rng.range(0.055, 0.075), k: rng.range(0.08, 0.11), spd: rng.range(0.2, 0.35) * rng.pick([1, -1]), l0: rng.range(0, 6.28) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, s = jzU(b) * (P.s || 0.065), r3 = Math.sqrt(3), pw = r3 * s, ph = 3 * s, col = jzLayC(sc, P.k || 0.09), fi, n2;
        var hd = bg2_hd(b) + 'var e=oc(BT/0.6),Lg=' + jzN(P.l0 || 0) + '+T*' + jzN(P.spd || 0.25) + ';';
        var dirs = [-Math.PI / 2, Math.PI * 5 / 6, Math.PI / 6], names = ['top', 'left', 'right'];
        var S = jzShapeLayer(b, 'bg isoCubes', 0, 0), g = jzGrp(S, 'cubes');
        for (fi = 0; fi < 3; fi++) {
            var gi = bg2_sub(g, names[fi]);
            for (n2 = 0; n2 < 2; n2++) {
                var cx = n2 * pw / 2, cy = n2 * 1.5 * s, Tp = [cx, cy - s], UR = [cx + pw / 2, cy - s / 2], LR = [cx + pw / 2, cy + s / 2], Bt = [cx, cy + s], LL = [cx - pw / 2, cy + s / 2], UL = [cx - pw / 2, cy - s / 2], C = [cx, cy];
                jzAddPath(gi, fi === 0 ? [Tp, UR, C, UL] : fi === 1 ? [UL, C, Bt, LL] : [UR, LR, Bt, C], true);
            }
            var f = jzAddFill(gi, col);
            jzSetExpr(f.property('ADBE Vector Fill Opacity'), hd + '100*e*(0.25+0.75*(0.5+0.5*Math.cos(Lg-(' + jzN(dirs[fi]) + '))))');
        }
        bg2_tile(g, W, H, pw, ph, 2 * pw, 2 * ph);
        jzSetExpr(jzGX(g).property('ADBE Vector Position'), hd + '[-wr(T*' + jzN(s * 0.25) + ',' + jzN(pw) + ')-' + jzN(2 * pw) + ',-wr(T*' + jzN(s * 0.25 / r3 * 1.5) + ',' + jzN(ph) + ')-' + jzN(2 * ph) + ']');
    }
});

/* ---- hexGrid \u2014 \u516D\u89D2\u683C\u5B50: honeycomb outline growing out from the centre; rings of light travel outwards through the cells
   (AE: soft rings seen through a honeycomb alpha matte) or random cells sparkle */
jzReg('bg', 'hexGrid', {
    plan: function (rng, st) { return { seed: bg2_seed(rng), s: rng.range(0.05, 0.07), k: rng.range(0.09, 0.12), mode: rng.pick(['ring', 'ring', 'sparkle']) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, U = jzU(b), px = bg2_px(b), s = U * (P.s || 0.06), r3 = Math.sqrt(3), rr = s * 0.9, k = P.k || 0.1, i, j;
        var R = Math.sqrt(W * W + H * H) / 2, cols = Math.ceil(W / (r3 * s)) + 2, rows = Math.ceil(H / (1.5 * s)) + 2, sd = (P.seed || 1) % 9973;
        var hd = bg2_hd(b) + 'var e=oc(BT/0.7);', tint = jzTintC(sc, k * 1.6), sparkle = P.mode === 'sparkle';
        var lattice = function (g, r) { bg2_hex(g, 0, 0, r); bg2_hex(g, r3 * s / 2, 1.5 * s, r); };
        var tileIt = function (g) { bg2_rep(g, cols + 3, r3 * s, 0, 0); bg2_rep(g, Math.ceil(rows / 2) + 3, 0, 3 * s, 0); jzGX(g).property('ADBE Vector Position').setValue([-r3 * s, -3 * s]); };
        // outline grid (+ sparkles), revealed by a growing disc
        var GL = jzShapeLayer(b, 'bg hexGrid', 0, 0);
        if (sparkle) {
            var M = Math.max(3, Math.round(cols * rows * 0.05)), gs = jzGrp(GL, 'sparkles');
            for (i = 0; i < M; i++) {
                var gi = bg2_sub(gs, 'sparkle ' + (i + 1)); bg2_hex(gi, 0, 0, rr * 0.86);
                var fi = jzAddFill(gi, tint);
                var ex = hd + 'var tw=Math.floor(T*3+1e-6),f=T*3-tw;seedRandom(' + sd + '+tw*97+' + (i * 7) + ',true);var ci=Math.floor(random(-1,' + cols + ')),cj=Math.floor(random(-1,' + rows + '));';
                jzSetExpr(jzGX(gi).property('ADBE Vector Position'), ex + '[(ci+(((cj%2)+2)%2)*0.5)*' + jzN(r3 * s) + ',cj*' + jzN(1.5 * s) + ']');
                jzSetExpr(fi.property('ADBE Vector Fill Opacity'), ex + 'var v=1-f*0.6;v>0.75?100:(v>0.45?62.5:31.25)');
            }
        }
        var gg = jzGrp(GL, 'grid'); lattice(gg, rr);
        jzAddStroke(gg, jzLayC(sc, k), Math.max(px, U * 0.0022));
        tileIt(gg);
        var MK = jzShapeLayer(b, 'bg hexGrid reveal', W / 2, H / 2), gk = jzGrp(MK, 'reveal'), ek = jzAddEllipse(gk, 10, 10);
        jzSetExpr(ek.property('ADBE Vector Ellipse Size'), hd + 'var d=2*(' + jzN(1.2 * R) + '*e+' + jzN(s * 0.5) + ');[d,d]');
        jzAddFill(gk, '#FFFFFF');
        bg2_matte(GL, MK, TrackMatteType.ALPHA);
        if (!sparkle) {
            // cos^6 rings (3 lit levels = three nested strokes of the brightest tint) travelling outwards, collapsed beyond the revealed radius
            var RL = jzShapeLayer(b, 'bg hexGrid rings', W / 2, H / 2), lam = R / 3.2;
            for (j = 0; j < 6; j++) {
                var g = jzGrp(RL, 'ring ' + (j + 1)), el = jzAddEllipse(g, 10, 10);
                jzSetExpr(el.property('ADBE Vector Ellipse Size'), hd + 'var r=(' + j + '+wr(T*0.55,1))*' + jzN(lam) + ';r>' + jzN(1.2 * R) + '*e+' + jzN(lam * 0.15) + '?[0,0]:[2*r,2*r]');
                jzAddStroke(g, tint, lam * 0.32, 31.25); jzAddStroke(g, tint, lam * 0.22, 45.45); jzAddStroke(g, tint, lam * 0.14, 100);
            }
            var HM = jzShapeLayer(b, 'bg hexGrid cells', 0, 0), gm = jzGrp(HM, 'cells');
            lattice(gm, rr * 0.86); jzAddFill(gm, '#FFFFFF'); tileIt(gm);
            bg2_matte(RL, HM, TrackMatteType.ALPHA);
        }
    }
});

/* ---- triTess \u2014 \u4E09\u89D2\u30E2\u30B6\u30A4\u30AF: triangle mosaic whose cells light up in 4 tones where a drifting noise field (+ a diagonal sweep) is high.
   AE: one group per triangle, its fill opacity (a tone of the brightest colour) from 2-D value noise computed in an expression */
jzReg('bg', 'triTess', {
    plan: function (rng, st) { return { seed: bg2_seed(rng), a: rng.range(0.1, 0.15), k: rng.range(0.07, 0.1), acc: rng.chance(0.35) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, sd = P.seed || 1, U = jzU(b), a = U * (P.a || 0.12), h = a * Math.sqrt(3) / 2, k = P.k || 0.085, r, i, up;
        var cols = Math.ceil(W / a) + 2, rows = Math.ceil(H / h) + 1, top = P.acc ? 1.45 * 1.4 : 1.45, ms = [0.35, 0.65, 1];
        var ctop = P.acc ? jzTintC(sc, k * top) : jzLayC(sc, k * 1.45), lv = [];
        for (i = 0; i < 3; i++) lv.push(jzN(100 * ms[i] / top));
        var hd = bg2_hd(b) + BG2_VN + 'var e=oc(BT/0.8),sw=(wr(T*0.07,1.6)-0.3)*' + jzN(W + H) + ',ns=' + (sd % 997) + ';';
        var S = jzShapeLayer(b, 'bg triTess', 0, 0);
        for (r = 0; r < rows; r++) for (i = -1; i < cols - 1; i++) for (up = 0; up < 2; up++) {
            var y0 = r * h, y1 = y0 + h, x0 = i * a + (r & 1 ? a / 2 : 0) + (up ? a / 2 : 0);
            var tri = up ? [[x0, y1], [x0 + a, y1], [x0 + a / 2, y0]] : [[x0, y0], [x0 + a, y0], [x0 + a / 2, y1]];
            var gx = x0 + a / 2, gy = up ? y0 + h * 0.66 : y0 + h * 0.33, jr = (bg2_r(sd, r, i, up) - 0.5) * 0.12;
            var g = jzGrp(S, 't' + r + '_' + i + '_' + up);
            jzAddPath(g, tri, true);
            var f = jzAddFill(g, ctop);
            jzSetExpr(f.property('ADBE Vector Fill Opacity'), hd + 'var X=' + jzN(gx / U * 1.6) + '+T*0.12,Y=' + jzN(gy / U * 1.6) + '-T*0.07;' +
                'var v=(vn2(X+ns,Y)*0.5+vn2(X*2.03+17,Y*2.03+ns)*0.25)/0.75+0.35*Math.exp(-Math.pow((' + jzN(gx + gy) + '-sw)/' + jzN(U * 0.35) + ',2));' +
                'v=v*e+' + jzN(jr) + ';v>0.78?100:(v>0.62?' + lv[2] + ':(v>0.46?' + lv[1] + ':(v>0.3?' + lv[0] + ':0)))');
        }
    }
});

/* ---- moire \u2014 \u30E2\u30A2\u30EC: two identical sets of fine concentric rings wandering around the centre -> drifting interference fringes */
jzReg('bg', 'moire', {
    plan: function (rng, st) { return { seed: bg2_seed(rng), gap: rng.range(0.016, 0.022), k: rng.range(0.08, 0.11), amp: rng.range(0.05, 0.09) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, U = jzU(b), gap = U * (P.gap || 0.018), amp = U * (P.amp || 0.07), s = (P.seed || 1) % 17, q, r;
        var M = amp * 2.2, Rm = Math.sqrt((W + 2 * M) * (W + 2 * M) + (H + 2 * M) * (H + 2 * M)) / 2, col = jzLayC(sc, P.k || 0.1), hd = bg2_hd(b);
        var S = jzShapeLayer(b, 'bg moire', 0, 0);
        var cen = ['[' + jzN(W / 2) + '+' + jzN(amp) + '*Math.sin(T*0.33+' + s + '),' + jzN(H / 2) + '+' + jzN(amp * 0.7) + '*Math.cos(T*0.27+' + s + ')]',
            '[' + jzN(W / 2) + '-' + jzN(amp) + '*Math.sin(T*0.29+' + (s + 1) + '),' + jzN(H / 2) + '-' + jzN(amp * 0.7) + '*Math.cos(T*0.37+' + (s + 2) + ')]'];
        for (q = 0; q < 2; q++) {
            var g = jzGrp(S, 'rings ' + (q + 1));
            for (r = gap; r < Rm; r += gap) jzAddEllipse(g, 2 * r, 2 * r);
            jzAddStroke(g, col, gap * 0.42);
            jzSetExpr(jzGX(g).property('ADBE Vector Position'), hd + cen[q]);
        }
        jzSetExpr(jzXf(S, 'ADBE Opacity'), hd + '100*oc(BT/0.8)');
    }
});

/* ---- squareTunnel \u2014 \u56DB\u89D2\u30C8\u30F3\u30CD\u30EB: nested, twisting squares (even-odd fill = alternating bands) flying towards the viewer.
   AE: one square + Repeater (scale r, rotation twist per copy) + even-odd Fill; the copy count toggles by one so the band parity stays put */
jzReg('bg', 'squareTunnel', {
    plan: function (rng, st) { return { seed: bg2_seed(rng), r: rng.range(1.22, 1.32), twist: rng.range(3, 7) * rng.pick([1, -1]), spd: rng.range(0.35, 0.6), k: rng.range(0.05, 0.07) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, U = jzU(b), r = P.r || 1.26, lr = Math.log(r), S0 = U * 0.08, smin = U * 0.035, tw = P.twist || 5;
        var maxS = Math.sqrt(W * W + H * H) * 1.05, L0 = Math.log(smin / S0) / lr, K0 = Math.ceil(Math.log(maxS / smin) / lr) + 2;
        var hd = bg2_hd(b) + 'var z=T*' + jzN(P.spd || 0.45) + '+(1-oc(BT/0.5))*1.5,q=z-(' + jzN(L0) + '),nH=Math.floor(q),f=q-nH;';
        var S = jzShapeLayer(b, 'bg squareTunnel', W / 2, H / 2), g = jzGrp(S, 'squares');
        var rc = jzAddRect(g, smin * 2, smin * 2);
        jzSetExpr(rc.property('ADBE Vector Rect Size'), hd + 'var h=' + jzN(smin) + '*Math.pow(' + jzN(r) + ',f);[2*h,2*h]');
        var rp = bg2_rep(g, K0, 0, 0, 0), rt = rp.property('ADBE Vector Repeater Transform');
        rt.property('ADBE Vector Repeater Scale').setValue([100 * r, 100 * r]);
        rt.property('ADBE Vector Repeater Rotation').setValue(tw);
        jzSetExpr(rp.property('ADBE Vector Repeater Copies'), hd + K0 + '+((((nH+1-' + K0 + ')%2)+2)%2)');
        var fl = jzAddFill(g, jzLayC(sc, P.k || 0.06));
        fl.property('ADBE Vector Fill Rule').setValue(2);
        jzSetExpr(jzGX(g).property('ADBE Vector Rotation'), hd + '(f+(' + jzN(L0) + '))*' + jzN(tw) + '+T*4');
    }
});

/* ---- spiralArms \u2014 \u6E26\u5DFB\u304D: 3-6 logarithmic spiral arms turning slowly, a soft disc of the background colour over the centre */
jzReg('bg', 'spiralArms', {
    plan: function (rng, st) { return { seed: bg2_seed(rng), n: rng.int(3, 6), b: rng.range(0.26, 0.38), spd: rng.range(0.14, 0.24) * rng.pick([1, -1]), k: rng.range(0.055, 0.075), cy: rng.pick([0.5, 0.5, 0.56]) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, U = jzU(b), n = P.n || 4, bb = P.b || 0.32, spd = P.spd || 0.18, kk, i;
        var cx = W / 2, cy = H * (P.cy || 0.5), r0 = U * 0.09, Rm = Math.sqrt(W * W + H * H) * 0.62, thMax = Math.log(Rm / r0) / bb, w = Math.PI / n, steps = 110;
        var hd = bg2_hd(b) + 'var e=oc(BT/0.7);';
        var S = jzShapeLayer(b, 'bg spiralArms', cx, cy), g = jzGrp(S, 'arms');
        for (kk = 0; kk < n; kk++) {
            var base = kk * 2 * Math.PI / n, pts = [], th, rr;
            for (i = 0; i <= steps; i++) { th = thMax * i / steps; rr = r0 * Math.exp(bb * th); pts.push([Math.cos(base + th) * rr, Math.sin(base + th) * rr]); }
            for (i = steps; i >= 0; i--) { th = thMax * i / steps; rr = r0 * Math.exp(bb * th); pts.push([Math.cos(base + th + w) * rr, Math.sin(base + th + w) * rr]); }
            jzAddPath(g, pts, true);
        }
        jzAddFill(g, jzLayC(sc, P.k || 0.065));
        jzSetExpr(jzGX(g).property('ADBE Vector Rotation'), hd + '(T*' + jzN(spd) + '+(1-e)*' + jzN(spd < 0 ? -1.2 : 1.2) + ')*180/Math.PI');
        jzSetExpr(jzGX(g).property('ADBE Vector Scale'), hd + 'var f=100*(0.4+0.6*e);[f,f]');
        var D = U * 0.84, C = bg2_solid(b, sc.bg, 'bg spiralArms centre', D, D);
        bg2_cmask(C, D / 2, D / 2, U * 0.21, { f: [U * 0.42, U * 0.42] });
        jzXf(C, 'ADBE Position').setValue([cx, cy]);
        jzXf(C, 'ADBE Opacity').setValue(80);
    }
});

/* ---- topoLines \u2014 \u7B49\u9AD8\u7DDA: contour lines of a slowly drifting terrain, levels creeping; every 4th line heavier.
   AE: Fractal Noise zoomed by a Transform effect (drift = its position) -> Posterize (terraces) -> Find Edges -> Threshold = white lines,
   used as luma matte for a colour solid; the heavy lines are a second chain with a quarter of the levels */
function bg2_topo(b, name, lv, bExpr, zoom, posExpr, thick) {
    var L = bg2_solid(b, '#808080', name, b.W, b.H), px = bg2_px(b);
    var fn = jzEffect(L, 'ADBE Fractal Noise', 'JZ Topo Field'); jzEP(fn, 2, 4); jzEP(fn, 4, 180); jzEX(fn, 5, bExpr);
    var tr = jzEffect(L, 'ADBE Geometry2', 'JZ Topo Zoom'); jzEP(tr, 3, 1); jzEP(tr, 4, zoom); jzEX(tr, 2, posExpr);
    var po = jzEffect(L, 'ADBE Posterize', 'JZ Topo Levels'); jzEP(po, 1, lv);
    var fe = jzEffect(L, 'ADBE Find Edges', 'JZ Topo Edges'); jzEP(fe, 1, 1);
    var th = jzEffect(L, 'ADBE Threshold2', 'JZ Topo Lines'); jzEP(th, 1, 10);
    if (thick) {
        var bx = jzEffect(L, 'ADBE Box Blur2', 'JZ Topo Thick'); jzEP(bx, 1, Math.max(1, 1.2 * px)); jzEP(bx, 2, 1); jzEP(bx, 4, 0);
        var t2 = jzEffect(L, 'ADBE Threshold2', 'JZ Topo Heavy'); jzEP(t2, 1, 50);
    }
    return L;
}
jzReg('bg', 'topoLines', {
    plan: function (rng, st) { return { seed: bg2_seed(rng), n: rng.int(10, 14), fs: rng.range(1.2, 1.7), k: rng.range(0.11, 0.15), acc: rng.chance(0.3) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, U = jzU(b), sd = P.seed || 1, k = P.k || 0.13, m4 = Math.max(2, Math.round((P.n || 12) / 4)), Lv = 4 * m4 + 1;
        var zoom = 100 * U / ((P.fs || 1.4) * 150), hd = bg2_hd(b);
        var z = hd + 'var z=T*0.06+(1-oc(BT/0.9))*2,c=wr(z,4)-2;';
        var pos = hd + '[' + jzN(W / 2 + (bg2_r(sd, 1) - 0.5) * W * 1.2) + '-BT*' + jzN(U * 0.025) + ',' + jzN(H / 2 + (bg2_r(sd, 2) - 0.5) * H * 1.2) + '+BT*' + jzN(U * 0.018) + ']';
        var fade = hd + '100*oc(BT/0.9)', i;
        var chains = [[Lv, z + '100*c/' + (Lv - 1), false, jzLayC(sc, k)], [m4 + 1, z + '100*(c+0.5)/' + (Lv - 1), true, P.acc ? jzTintC(sc, k * 2.2) : jzLayC(sc, k * 1.6)]];
        for (i = 0; i < 2; i++) {
            var C = bg2_solid(b, chains[i][3], 'bg topoLines ' + (i ? 'major' : 'lines'), W, H);
            jzSetExpr(jzXf(C, 'ADBE Opacity'), fade);
            var Mt = bg2_topo(b, 'bg topoLines ' + (i ? 'major' : 'lines') + ' matte', chains[i][0], chains[i][1], zoom, pos, chains[i][2]);
            bg2_matte(C, Mt, TrackMatteType.LUMA);
        }
    }
});

/* ---- ridgePlot \u2014 \u7A1C\u7DDA\u30B0\u30E9\u30D5: stacked ridge lines (Joy Division), each hiding the ones behind it, peaks rolling sideways.
   AE: the ridge outlines are baked as path keyframes (5 per second, denser while they grow in), front ridge in the top group */
jzReg('bg', 'ridgePlot', {
    plan: function (rng, st) { return { seed: bg2_seed(rng), n: rng.int(18, 26), k: rng.range(0.15, 0.2), amp: rng.range(0.07, 0.1), mode: rng.pick(['center', 'center', 'wide']), spd: rng.range(0.18, 0.3) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, sd = P.seed || 1, U = jzU(b), px = bg2_px(b), n = P.n || 22, spd = P.spd || 0.24, i, j, q;
        var x0 = W * 0.06, x1 = W * 0.94, y0 = H * 0.2, y1 = H * 0.94, gap = (y1 - y0) / (n - 1), m = jzClamp(Math.round((x1 - x0) / (U * 0.018)), 50, 100);
        var amp = H * (P.amp || 0.085) * (W < H ? 0.6 : 1), tx = b.st && b.st.texture, paper = sc.paper || (tx && tx.paper > 0.5);
        var t0 = b.cut.start || 0, run = bg2_run(b), dur = b.comp.duration, ts = [], t;
        for (t = 0; t < 1.2 && t < dur; t += 0.1) ts.push(t);
        var step = Math.max(0.2, dur / 80);
        for (t = 1.2; t < dur; t += step) ts.push(t);
        ts.push(dur);
        var envs = [], xs = [];
        for (j = 0; j <= m; j++) {
            var u = j / m;
            xs.push(x0 + (x1 - x0) * u);
            envs.push(P.mode === 'wide' ? 0.3 + 0.7 * Math.pow(Math.sin(Math.PI * u), 2) : 0.08 + 0.92 * Math.exp(-Math.pow((u - 0.5) / 0.16, 2)));
        }
        var S = jzShapeLayer(b, 'bg ridgePlot', 0, 0), lw = Math.max(px, U * 0.0022), lc = jzLayC(sc, P.k || 0.17);
        for (i = n - 1; i >= 0; i--) {       // front (lowest) ridge = first group = on top
            var base = y0 + i * gap, g = jzGrp(S, 'ridge ' + (i + 1)), jit = [], closedV = [], openV = [];
            for (j = 0; j <= m; j++) jit.push(H * 0.002 * bg2_noise1(j / m * 40 + i, sd));
            for (q = 0; q < ts.length; q++) {
                var T = t0 + ts[q], e = bg2_oc((run + ts[q]) / 1), grow = bg2_oc(e * 1.6 - i / n * 0.6), pts = [];
                for (j = 0; j <= m; j++) {
                    var v = Math.pow(0.5 + 0.5 * bg2_fbm1(j / m * 7 + T * spd + i * 0.41, sd + i * 7, 3), 2.4);
                    pts.push([xs[j], base - amp * envs[j] * v * 2.2 * grow - jit[j]]);
                }
                var so = new Shape(); so.vertices = pts; so.closed = false; openV.push(so);
                var sf = new Shape(); sf.vertices = [[x0, base + px]].concat(pts, [[x1, base + px]]); sf.closed = true; closedV.push(sf);
            }
            // (each path gets its keyframes before the stroke / fill / next group is added: adding invalidates `pL` / `pF` in AE)
            var gL = bg2_sub(g, 'line'), pL = jzAddPath(gL, openV[0].vertices, false);
            pL.property('ADBE Vector Shape').setValuesAtTimes(ts, openV);
            bg2_round(jzAddStroke(gL, lc, lw));
            var gF = bg2_sub(g, 'fill'), pF = jzAddPath(gF, closedV[0].vertices, true);
            pF.property('ADBE Vector Shape').setValuesAtTimes(ts, closedV);
            jzAddFill(gF, sc.bg, paper ? 72 : 90);
        }
    }
});

/* ================================================================ SCENE */

/* ---- starfield \u2014 \u661F\u7A7A: three parallax layers of stars drifting one way and twinkling (big ones with a cross flare), a shooting star now and then.
   AE: per layer one tile (1.1W x 1.1H) of static stars in 5 twinkle groups, slid by a wrapped offset and repeated 2x2 */
jzReg('bg', 'starfield', {
    plan: function (rng, st) { return { seed: bg2_seed(rng), ang: rng.range(-0.3, 0.3) + (rng.chance(0.5) ? Math.PI : 0), spd: rng.range(0.8, 1.3), shoot: rng.chance(0.75) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, sd = P.seed || 1, U = jzU(b), px = bg2_px(b), dk = bg2_dark(sc), l, i, q, m;
        var col = dk ? jzMixHex(sc.bg, sc.fg, 0.92) : (jzLum(sc.bg) < 0.78 ? '#FFFFFF' : jzLayC(sc, 0.5));
        var area = Math.sqrt(W * H) / px / Math.sqrt(1920 * 1080), vx = Math.cos(P.ang || 0), vy = Math.sin(P.ang || 0), spd = P.spd || 1;
        var Wt = W * 1.1, Ht = H * 1.1, NB = 5, am = dk ? 1 : 0.65, hd = bg2_hd(b) + 'var e=oc(BT/0.9);';
        var layers = [[90, 0.0013, 0.006, 0.45], [46, 0.002, 0.018, 0.65], [18, 0.0032, 0.045, 0.85]];
        var S = jzShapeLayer(b, 'bg starfield', 0, 0);
        if (P.shoot) {     // shooting star: every 3.4 s window (85 %), a delayed 0.75 s streak (baked per window)
            var per = 3.4, tc = b.cut.start || 0, m0 = Math.floor(tc / per) - 1, m1 = Math.floor((tc + b.comp.duration) / per) + 1, A = [];
            for (m = m0; m <= m1; m++) {
                var ang = bg2_rr(20, 38, sd, m, 3) * Math.PI / 180 * (bg2_r(sd, m, 4) < 0.5 ? 1 : -1);
                A.push('[' + [jzN(W * bg2_rr(0.15, 0.7, sd, m, 1)), jzN(H * bg2_rr(0.05, 0.35, sd, m, 2)), jzN(Math.cos(ang) * (ang < 0 ? -1 : 1)), jzN(Math.abs(Math.sin(ang))), jzN(bg2_r(sd, m, 8) * 1.5), bg2_r(sd, m, 9) < 0.85 ? 1 : 0].join(',') + ']');
            }
            var mh = hd + 'var MS=[' + A.join(',') + '],id=Math.floor(T/' + per + '),A=MS[Math.max(0,Math.min(' + (A.length - 1) + ',id-(' + m0 + ')))],age=T-id*' + per + '-A[4],q=cl(age/0.75),on=A[5]>0&&age>0&&age<0.75,oq=1-(1-q)*(1-q);';
            var gm = jzGrp(S, 'shooting star'), mw = Math.max(px, U * 0.002) * 1.4, TL = U * 0.16;
            var g1 = bg2_sub(gm, 'head'); jzAddPath(g1, [[0, -mw / 2], [0, mw / 2], [-TL * 0.45, 0]], true); jzAddEllipse(g1, mw, mw); jzAddFill(g1, col, 45);
            var g2 = bg2_sub(gm, 'tail'); jzAddPath(g2, [[0, -mw / 2], [0, mw / 2], [-TL, 0]], true); jzAddFill(g2, col, 55);
            jzSetExpr(jzGX(gm).property('ADBE Vector Position'), mh + '[A[0]+A[2]*' + jzN(U * 0.55) + '*oq,A[1]+A[3]*' + jzN(U * 0.55) + '*oq]');
            jzSetExpr(jzGX(gm).property('ADBE Vector Rotation'), mh + 'Math.atan2(A[3],A[2])*180/Math.PI');
            jzSetExpr(jzGX(gm).property('ADBE Vector Scale'), mh + '[on?100*Math.max(0.01,Math.sin(Math.PI*q)):0,100]');
            jzSetExpr(jzGX(gm).property('ADBE Vector Group Opacity'), mh + 'on?Math.min(100,145*e*(1-q*0.6)):0');
        }
        for (l = 2; l >= 0; l--) {       // near (big, fast) layer on top
            var cnt = Math.round(layers[l][0] * area), v = U * layers[l][2] * spd, aB = layers[l][3], g = jzGrp(S, 'stars ' + (l + 1)), stars = [];
            for (i = 0; i < cnt; i++) stars.push([bg2_r(sd, l, i, 1) * Wt, bg2_r(sd, l, i, 2) * Ht, U * layers[l][1] * (0.6 + 0.8 * bg2_r(sd, l, i, 3))]);
            // every sub-group is filled completely before the next one is added (adding a group invalidates the held siblings in AE)
            if (l === 2) {
                var fl = bg2_sub(g, 'flares');
                for (i = 0; i < cnt; i++) if (bg2_r(sd, l, i, 5) < 0.5) { var L5 = stars[i][2] * 5.5, thn = Math.max(0.6 * px, stars[i][2] * 0.25); jzAddRect(fl, 2 * L5, thn, 0, stars[i][0], stars[i][1]); jzAddRect(fl, thn, 2 * L5, 0, stars[i][0], stars[i][1]); }
                var ff = jzAddFill(fl, col); jzSetExpr(ff.property('ADBE Vector Fill Opacity'), hd + jzN(50 * aB * am) + '*e*(0.45+0.55*(0.5+0.5*Math.sin(T*1.7+2)))');
            }
            for (q = 0; q < NB; q++) {
                var gq = bg2_sub(g, 'twinkle ' + (q + 1));
                for (i = q; i < cnt; i += NB) {
                    var x = stars[i][0], y = stars[i][1], r = stars[i][2];
                    if (l < 2) jzAddRect(gq, 2 * r, 2 * r, 0, x, y);
                    else jzAddEllipse(gq, 2 * r, 2 * r, x, y);
                }
                var f = jzAddFill(gq, col);
                jzSetExpr(f.property('ADBE Vector Fill Opacity'), hd + jzN(100 * aB * am) + '*e*(0.45+0.55*(0.5+0.5*Math.sin(T*' + jzN(1.1 + 2.6 * (q + 0.5) / NB) + '+' + jzN(q * 1.7 + l) + ')))');
            }
            bg2_rep(g, 2, Wt, 0, 0); bg2_rep(g, 2, 0, Ht, 0);
            jzSetExpr(jzGX(g).property('ADBE Vector Position'), hd + '[wr(T*' + jzN(v * vx) + ',' + jzN(Wt) + ')-' + jzN(Wt + W * 0.05) + ',wr(T*' + jzN(v * vy) + ',' + jzN(Ht) + ')-' + jzN(Ht + H * 0.05) + ']');
        }
    }
});

// ================================================================ background graphics part 3 (AE port of scenes / textures in src/11p_bgcamB.js)
// bg.build(b, P): b.comp is the cut's WRAPPER comp (full frame, time 0 = cut start, duration = cut + 1 s). Layers added here sit
// above the scheme colour / paper and below the lyric. Low contrast; everything animated with expressions on `time`.

// ================================================================ shared helpers of this pack (prefix bg3_)
var BG3_TAU = Math.PI * 2;
// the browser's deterministic hash J.h / J.r / J.rs / J.rr and smooth 1D noise J.noise1 (so baked shapes match the web frame)
function bg3_h(a, b, c, d, e) {
    var h = 0x9e3779b9 ^ (a | 0);
    h = jzImul(h ^ (h >>> 16), 0x85ebca6b);
    h = (h + jzImul((b | 0) + 0x632be5ab, 0xc2b2ae35)) | 0;
    h = jzImul(h ^ (h >>> 13), 0xc2b2ae35);
    h = (h + jzImul((c | 0) + 0x5bd1e995, 0x27d4eb2f)) | 0;
    h = jzImul(h ^ (h >>> 15), 0x165667b1);
    h = (h + jzImul((d | 0) + 0x1b873593, 0x85ebca6b)) | 0;
    h = jzImul(h ^ (h >>> 16), 0x27d4eb2f);
    h = (h + jzImul((e | 0) + 0x68e31da4, 0x9e3779b1)) | 0;
    h ^= h >>> 15; h = jzImul(h, 0x2c1b3c6d); h ^= h >>> 12; h = jzImul(h, 0x297a2d39); h ^= h >>> 15;
    return h >>> 0;
}
function bg3_r(a, b, c, d, e) { return bg3_h(a, b, c, d, e) / 4294967296; }
function bg3_rs(a, b, c, d, e) { return bg3_r(a, b, c, d, e) * 2 - 1; }
function bg3_rr(lo, hi, a, b, c, d, e) { return lo + (hi - lo) * bg3_r(a, b, c, d, e); }
function bg3_n1(x, s) { var i = Math.floor(x), f = x - i, u = f * f * (3 - 2 * f), a = bg3_rs(s, i); return a + (bg3_rs(s, i + 1) - a) * u; }
function bg3_sgn(x) { return x > 0 ? 1 : (x < 0 ? -1 : 0); }
// seconds this background had already been running at the cut start (browser bgT: consecutive cuts with the same bg + seed are one run)
function bg3_run(b) {
    var c = b.cut, cs = (b.plan && b.plan.cuts) || [], s = c.start || 0, P0 = c.bgP || {}, idx = -1, i;
    for (i = 0; i < cs.length; i++) if (cs[i] === c) { idx = i; break; }
    for (i = idx - 1; i >= 0; i--) {
        var p = cs[i];
        if (p.bg === c.bg && (p.bgP || {}).seed === P0.seed && Math.abs(p.end - s) < 0.06) s = p.start; else break;
    }
    return (c.start || 0) - s;
}
// expression helpers: cl oc oe oq iq wr, hs(i, s) hash -1..1, n1(x, s) smooth noise -1..1 (stand-in for J.noise1)
var BG3_FNS = 'function cl(x){return Math.max(0,Math.min(1,x));}function oc(x){x=cl(x);return 1-Math.pow(1-x,3);}function oe(x){x=cl(x);return x>=1?1:1-Math.pow(2,-10*x);}' +
    'function oq(x){x=cl(x);return 1-(1-x)*(1-x);}function iq(x){x=cl(x);return x*x;}function wr(v,m){return ((v%m)+m)%m;}' +
    'function hs(i,s){var x=Math.sin(i*127.1+s*311.7)*43758.5453;return 2*(x-Math.floor(x))-1;}function n1(x,s){var i=Math.floor(x),f=x-i,u=f*f*(3-2*f),a=hs(i,s);return a+(hs(i+1,s)-a)*u;}';
// header: T = absolute song time (browser env.t), BT = time since this background started (browser bgT)
function bg3_hd(b) { return BG3_FNS + 'var T=time+' + jzN(b.cut.start || 0) + ',BT=time+' + jzN(bg3_run(b)) + ';\n'; }
function bg3_px(b) { return jzU(b) / 1080; }                                   // one browser design px in comp px
function bg3_area(b) { var p = bg3_px(b); return Math.sqrt(b.W * b.H / (p * p) / (1920 * 1080)); }
function bg3_seed(rng) { return rng.int(1, 999999999); }
function bg3_dark(sc) { return jzLum(sc.bg) < 0.45; }
function bg3_glow(sc) { var L = jzLum(sc.bg), c = [sc.accent, sc.accent2, sc.ghostA, sc.ghostB, sc.fg], i; for (i = 0; i < c.length; i++) if (c[i] && jzLum(c[i]) > L + 0.25) return c[i]; return sc.fg; }
function bg3_lightOn(sc) { return bg3_dark(sc) ? bg3_glow(sc) : (jzLum(sc.bg) < 0.78 ? '#FFFFFF' : jzMixHex(sc.accent, '#FFFFFF', 0.2)); }
function bg3_hues(sc) {
    var c = [sc.accent, sc.accent2, sc.ghostA, sc.ghostB, sc.fg], out = [], seen = {}, i;
    for (i = 0; i < c.length; i++) {
        if (!c[i] || jzContrast(c[i], sc.bg) < 1.25) continue;
        var kk = String(c[i]).toLowerCase(); if (seen[kk]) continue;
        seen[kk] = 1; out.push(c[i]);
    }
    return out.length ? out : [sc.fg];
}
function bg3_col(hex) { var c = jzHex(hex); return '[' + jzN(c[0]) + ',' + jzN(c[1]) + ',' + jzN(c[2]) + ',1]'; }
function bg3_solid(b, hex, name, w, h) { return b.comp.layers.addSolid(jzHex(hex), name, Math.max(4, Math.round(w)), Math.max(4, Math.round(h)), 1, b.comp.duration); }
// rectangle / circle masks in layer space; o: { f: [fx, fy] feather, mode: MaskMode.*, inv: true }
function bg3_mask(L, x0, y0, x1, y1, o) {
    o = o || {};
    var m = L.property('ADBE Mask Parade').addProperty('ADBE Mask Atom'), sh = new Shape();
    sh.vertices = [[x1, y0], [x1, y1], [x0, y1], [x0, y0]]; sh.closed = true;
    m.property('ADBE Mask Shape').setValue(sh);
    if (o.f) m.property('ADBE Mask Feather').setValue(o.f);
    if (o.mode) m.maskMode = o.mode;
    if (o.inv) m.inverted = true;
    return m;
}
function bg3_cmask(L, cx, cy, r, o) {
    o = o || {};
    var m = L.property('ADBE Mask Parade').addProperty('ADBE Mask Atom');
    m.property('ADBE Mask Shape').setValue(jzCircleShape(cx, cy, r));
    if (o.f) m.property('ADBE Mask Feather').setValue(o.f);
    if (o.mode) m.maskMode = o.mode;
    if (o.inv) m.inverted = true;
    return m;
}
// shape Repeater (copies n, offset off, step [dx, dy]) at the end of group g
function bg3_rep(g, n, dx, dy, off) {
    var V = jzVecs(g), ix = V.addProperty('ADBE Vector Filter - Repeater').propertyIndex;
    V.property(ix).property('ADBE Vector Repeater Copies').setValue(n);
    if (off) V.property(ix).property('ADBE Vector Repeater Offset').setValue(off);
    V.property(ix).property('ADBE Vector Repeater Transform').property('ADBE Vector Repeater Position').setValue([dx, dy]);
    return V.property(ix);
}
function bg3_sub(g, name) { var c = jzVecs(g).addProperty('ADBE Vector Group'); if (name) c.name = name; return c; }
// capsule (rounded ends) with its top-left at (x, y) \u2014 the browser's cap()
function bg3_cap(g, x, y, w, h) { return jzAddRect(g, w, h, h / 2, x + w / 2, y + h / 2); }
// path with optional Catmull-Rom tangents (smooth curves through the points)
function bg3_path(g, pts, closed, smooth) {
    var p = jzVecs(g).addProperty('ADBE Vector Shape - Group'), sh = new Shape(), n = pts.length, ins = [], outs = [], i;
    sh.vertices = pts; sh.closed = !!closed;
    if (smooth && n > 2) {
        for (i = 0; i < n; i++) {
            var a = pts[i > 0 ? i - 1 : (closed ? n - 1 : 0)], c = pts[i < n - 1 ? i + 1 : (closed ? 0 : n - 1)];
            var tx = (c[0] - a[0]) / 6, ty = (c[1] - a[1]) / 6;
            ins.push([-tx, -ty]); outs.push([tx, ty]);
        }
        sh.inTangents = ins; sh.outTangents = outs;
    }
    p.property('ADBE Vector Shape').setValue(sh);
    return p;
}
function bg3_roundJoin(st) { try { st.property('ADBE Vector Stroke Line Join').setValue(2); st.property('ADBE Vector Stroke Line Cap').setValue(2); } catch (e) {} return st; }
// VHS speckle strip: grey solid -> Noise (new grain every frame) -> Mosaic cells (cw x ch px, none when cw = 0) -> Threshold -> Tint; screen (dark) / multiply (light)
function bg3_vhsNoise(b, col, dk, name, w, h, cw, ch, th) {
    var L = bg3_solid(b, '#808080', name, w, h);
    var nz = jzEffect(L, 'ADBE Noise', 'JZ Grain'); jzEP(nz, 1, 100); jzEP(nz, 2, 0); jzEP(nz, 3, 1);
    if (cw) { var mo = jzEffect(L, 'ADBE Mosaic', 'JZ Cells'); jzEP(mo, 1, Math.max(1, Math.round(w / cw))); jzEP(mo, 2, Math.max(1, Math.round(h / ch))); jzEP(mo, 3, 1); }
    var tr = jzEffect(L, 'ADBE Threshold2', 'JZ Speckle'); jzEP(tr, 1, th || 200);
    var tn = jzEffect(L, 'ADBE Tint', 'JZ Tone'); jzEP(tn, 1, dk ? [0, 0, 0] : [1, 1, 1]); jzEP(tn, 2, jzHex(col)); jzEP(tn, 3, 100);
    L.blendingMode = dk ? BlendingMode.SCREEN : BlendingMode.MULTIPLY;
    return L;
}
// the browser's 2D value noise / fbm (hash2 -> noise2 -> fbm2); lattice hashes cached in C per build
function bg3_hash2(x, y, s) {
    var h = (jzImul(x | 0, 374761393) + jzImul(y | 0, 668265263) + jzImul(s | 0, 144665)) | 0;
    h = jzImul(h ^ (h >>> 13), 1274126177); h ^= h >>> 16; return (h >>> 0) / 4294967296;
}
function bg3_hc(C, x, y, s) { var key = x + ',' + y + ',' + s, v = C[key]; if (v === undefined) { v = bg3_hash2(x, y, s); C[key] = v; } return v; }
function bg3_nz2(C, x, y, s) {
    var ix = Math.floor(x), iy = Math.floor(y), fx = x - ix, fy = y - iy, u = fx * fx * (3 - 2 * fx), v = fy * fy * (3 - 2 * fy);
    var a = bg3_hc(C, ix, iy, s), b = bg3_hc(C, ix + 1, iy, s), c = bg3_hc(C, ix, iy + 1, s), d = bg3_hc(C, ix + 1, iy + 1, s);
    return a + (b - a) * u + (c - a) * v + (a - b - c + d) * u * v;
}
function bg3_fbm2(C, x, y, s, oct) { var v = 0, a = 0.5, f = 1, n = 0, i; for (i = 0; i < oct; i++) { v += bg3_nz2(C, x * f, y * f, s + i * 101) * a; n += a; a *= 0.5; f *= 2.03; } return v / n; }
// iso-line of a sampled field F ((G+1) x (G+1), row-major) at level lv -> [{ pts (grid units), closed }] (marching squares + chaining)
function bg3_iso(F, G, lv) {
    var adj = {}, pos = {}, used = {}, out = [], keys = [], i, j, z, pass, id, R = G + 1;
    var link = function (p, q) { (adj[p] = adj[p] || []).push(q); (adj[q] = adj[q] || []).push(p); };
    var pH = function (i, j) { var id = 'h' + i + '_' + j; if (!pos[id]) { var f0 = F[j * R + i], f1 = F[j * R + i + 1]; pos[id] = [i + (lv - f0) / (f1 - f0), j]; } return id; };
    var pV = function (i, j) { var id = 'v' + i + '_' + j; if (!pos[id]) { var f0 = F[j * R + i], f1 = F[(j + 1) * R + i]; pos[id] = [i, j + (lv - f0) / (f1 - f0)]; } return id; };
    for (j = 0; j < G; j++) for (i = 0; i < G; i++) {
        var a = F[j * R + i] > lv, b = F[j * R + i + 1] > lv, c = F[(j + 1) * R + i + 1] > lv, d = F[(j + 1) * R + i] > lv;
        var cs = (a ? 8 : 0) + (b ? 4 : 0) + (c ? 2 : 0) + (d ? 1 : 0);
        if (cs === 0 || cs === 15) continue;
        var e = [];               // crossed edges in order top, right, bottom, left
        if (a !== b) e.push(pH(i, j));
        if (b !== c) e.push(pV(i + 1, j));
        if (d !== c) e.push(pH(i, j + 1));
        if (a !== d) e.push(pV(i, j));
        if (e.length === 2) link(e[0], e[1]);
        else if (e.length === 4) {
            var cv = (F[j * R + i] + F[j * R + i + 1] + F[(j + 1) * R + i + 1] + F[(j + 1) * R + i]) / 4 > lv;
            if ((cs === 10 && cv) || (cs === 5 && !cv)) { link(e[0], e[1]); link(e[2], e[3]); } else { link(e[3], e[0]); link(e[1], e[2]); }
        }
    }
    for (id in adj) if (adj.hasOwnProperty(id)) keys.push(id);
    for (pass = 0; pass < 2; pass++) for (z = 0; z < keys.length; z++) {
        id = keys[z];
        if (used[id] || (pass === 0 && adj[id].length !== 1)) continue;
        var chain = [], cur = id, prev = null;
        while (cur && !used[cur]) {
            used[cur] = 1; chain.push(pos[cur]);
            var nx = null, nb = adj[cur], q;
            for (q = 0; q < nb.length; q++) if (nb[q] !== prev && !used[nb[q]]) { nx = nb[q]; break; }
            prev = cur; cur = nx;
        }
        if (chain.length >= 3) out.push({ pts: chain, closed: pass === 1 });
    }
    return out;
}

/* ---- nightMoon \u2014 \u6708\u591C: moon (full with craters, or crescent) with a soft halo, sparse twinkling stars, thin cloud streaks drifting across */
jzReg('bg', 'nightMoon', {
    plan: function (rng, st) { return { seed: bg3_seed(rng), side: rng.pick([1, -1]), R: rng.range(0.11, 0.15), phase: rng.pick(['full', 'crescent', 'crescent']), k: rng.range(0.18, 0.26) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, sd = P.seed || 1, U = jzU(b), dk = bg3_dark(sc), side = (P.side || 1) > 0 ? 1 : -1, port = H > W, i;
        var R = U * (P.R || 0.13), mx = W * (0.5 + side * (port ? 0.2 : 0.3)), my = H * (port ? 0.2 : 0.27), gl = bg3_glow(sc);
        var moonC = dk ? jzMixHex(sc.bg, gl === sc.fg ? sc.fg : jzMixHex(sc.fg, gl, 0.3), P.k || 0.22) : jzMixHex(sc.bg, '#FFFFFF', 0.55);
        var haloC = dk ? moonC : jzMixHex(sc.bg, '#FFFFFF', 0.7);
        var hd = bg3_hd(b) + 'var e=oc(BT/1.2),my=' + jzN(my) + '+(1-e)*' + jzN(H * 0.06) + ';';
        // stars (upper 62 %, none near the moon)
        var nS = Math.round(28 * bg3_area(b)), S = jzShapeLayer(b, 'bg nightMoon stars', 0, 0), stc = dk ? jzLayC(sc, 0.55) : jzLayC(sc, 0.2);
        for (i = 0; i < nS; i++) {
            var x = bg3_r(sd, i, 1) * W, y = bg3_r(sd, i, 2) * H * 0.62, r = U * bg3_rr(0.001, 0.0024, sd, i, 3);
            if (Math.sqrt((x - mx) * (x - mx) + (y - my) * (y - my)) < R * 2.2) continue;
            var g = jzGrp(S, 'star ' + (i + 1)); jzAddRect(g, r * 2, r * 2, 0, x, y); jzAddFill(g, stc);
            jzSetExpr(jzGX(g).property('ADBE Vector Group Opacity'), hd + '100*e*(0.2+0.4*(0.5+0.5*Math.sin(T*' + jzN(bg3_rr(0.8, 2.4, sd, i, 4)) + '+' + i + ')))');
        }
        // halo: a steep inner glow + a long faint tail (two feathered circles), breathing
        var HL = bg3_solid(b, haloC, 'bg nightMoon halo', R * 7, R * 7), hc = R * 3.5;
        bg3_cmask(HL, hc, hc, R * 1.2, { f: [R, R] });
        var m2 = bg3_cmask(HL, hc, hc, R * 2.1, { f: [R * 2.6, R * 2.6] }); m2.property('ADBE Mask Opacity').setValue(30);
        jzSetExpr(jzXf(HL, 'ADBE Position'), hd + '[' + jzN(mx) + ',my]');
        jzSetExpr(jzXf(HL, 'ADBE Opacity'), hd + jzN(dk ? 35 : 50) + '*e*(0.9+0.1*Math.sin(T*0.7))');
        // the moon: disc (+ craters) or a crescent cut by a subtracted circle
        var M = jzShapeLayer(b, 'bg nightMoon moon', mx, my);
        if (P.phase !== 'crescent') {
            var gc = jzGrp(M, 'craters');
            for (i = 0; i < 6; i++) { var a = bg3_r(sd, i, 11) * BG3_TAU, d = R * Math.sqrt(bg3_r(sd, i, 12)) * 0.7, cr = R * bg3_rr(0.08, 0.2, sd, i, 13); jzAddEllipse(gc, cr * 2, cr * 2, Math.cos(a) * d, Math.sin(a) * d); }
            jzAddFill(gc, jzMixHex(moonC, sc.bg, 0.22));
        }
        var gd = jzGrp(M, 'disc'); jzAddEllipse(gd, R * 2, R * 2); jzAddFill(gd, moonC);
        if (P.phase === 'crescent') bg3_cmask(M, side * R * 0.42, -R * 0.22, R * 0.9, { mode: MaskMode.SUBTRACT });
        jzSetExpr(jzXf(M, 'ADBE Position'), hd + '[' + jzN(mx) + ',my]');
        jzSetExpr(jzXf(M, 'ADBE Opacity'), hd + '100*e');
        // thin cloud streaks (3 capsules each) drifting across the moon
        var C = jzShapeLayer(b, 'bg nightMoon clouds', 0, 0), cc = dk ? jzLayC(sc, 0.07) : jzLayC(sc, 0.05);
        for (i = 0; i < 3; i++) {
            var w = U * bg3_rr(0.4, 0.7, sd, i, 21), h = U * bg3_rr(0.02, 0.03, sd, i, 22), L = W + w * 2, gs = jzGrp(C, 'streak ' + (i + 1));
            bg3_cap(gs, 0, 0, w, h); bg3_cap(gs, w * bg3_rr(0.15, 0.4, sd, i, 26), -h * 0.75, w * 0.45, h * 0.9); bg3_cap(gs, w * bg3_rr(0.35, 0.6, sd, i, 27), h * 0.7, w * 0.5, h * 0.8);
            jzAddFill(gs, cc);
            jzSetExpr(jzGX(gs).property('ADBE Vector Position'), hd + '[wr(' + jzN((i + bg3_r(sd, i, 23) * 0.5) / 3 * L) + '+T*' + jzN(U * bg3_rr(0.025, 0.045, sd, i, 24)) + ',' + jzN(L) + ')-' + jzN(w) +
                ',my+' + jzN(R * (i - 1) * 0.8 + R * bg3_rr(-0.2, 0.3, sd, i, 25)) + ']');
        }
        jzSetExpr(jzXf(C, 'ADBE Opacity'), hd + '70*e');
    }
});

/* ---- skyline \u2014 \u8857\u4E26\u307F: two rows of building silhouettes scrolling at different speeds (parallax) with lit windows, horizon glow; rises in.
   AE: only the buildings this cut can show are baked (windows as lit at the cut's middle), the city groups slide with an expression */
jzReg('bg', 'skyline', {
    plan: function (rng, st) { return { seed: bg3_seed(rng), k: rng.range(0.08, 0.11), dir: rng.pick([1, -1]), win: rng.range(0.22, 0.34) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, sd = P.seed || 1, U = jzU(b), px = bg3_px(b), dk = bg3_dark(sc), k = P.k || 0.09, dir = P.dir || 1, l;
        var Lp = Math.max(W, H) * 1.5, T0 = b.cut.start || 0, T1 = T0 + b.comp.duration, Tm = (T0 + T1) / 2;
        var hd = bg3_hd(b) + 'var e=oc(BT/0.9);';
        // horizon glow behind the city (0 at mid height -> faint at the bottom)
        var G = bg3_solid(b, dk ? bg3_glow(sc) : sc.accent, 'bg skyline glow', W, H);
        bg3_mask(G, -W, H * 0.75, W * 2, H * 3, { f: [0, H * 0.5] });
        jzSetExpr(jzXf(G, 'ADBE Opacity'), hd + jzN(dk ? 10 : 8) + '*e');
        var winLit = dk ? jzMixHex(sc.bg, bg3_glow(sc), 0.4) : jzMixHex(jzLayC(sc, k * 1.1), '#FFFFFF', 0.55);
        var LY = [[0.6, 0.12, 0.34, 0.012], [1.1, 0.06, 0.2, 0.03]], cw = U * 0.022, ch = U * 0.03;
        for (l = 0; l < 2; l++) {
            var km = LY[l][0], h0 = LY[l][1], h1 = LY[l][2], sp = U * LY[l][3] * dir, O0 = T0 * sp, oMin = Math.min(T0 * sp, T1 * sp), oMax = Math.max(T0 * sp, T1 * sp);
            // rects are collected first ([w, h, x, y]), then each sub-group is built in one go (adding a sibling group in AE
            // invalidates the script's references to the other one)
            var S = jzShapeLayer(b, 'bg skyline ' + (l ? 'front' : 'back'), 0, 0), RB = [], RW = [], z;
            var x = 0, bi = 0;
            while (x < Lp && bi < 80) {
                var w = U * bg3_rr(0.05, 0.12, sd, l, bi, 1), gapW = U * bg3_rr(0, 0.012, sd, l, bi, 2);
                var h = H * bg3_rr(h0, h1, sd, l, bi, 3) * (W < H ? 0.75 : 1), tier = bg3_r(sd, l, bi, 4) < 0.35, ant = bg3_r(sd, l, bi, 5) < 0.25;
                var k0 = Math.floor((oMin - x - w) / Lp), k1 = Math.ceil((oMax + W - x) / Lp), kk;
                for (kk = k0; kk <= k1; kk++) {
                    // screen x of this copy = x + kk*Lp - t*sp; skip copies that never reach the frame during the cut
                    if (x + kk * Lp - oMax > W || x + kk * Lp - oMin + w < 0) continue;
                    var X0 = x + kk * Lp - O0, top = H - h;
                    RB.push([w, h + 2 * px, X0 + w / 2, top + h / 2 + px]);
                    if (tier) RB.push([w * 0.6, h * 0.12 + px, X0 + w * 0.5, top - h * 0.06 + px / 2]);
                    if (ant) { var aw = Math.max(1.5 * px, U * 0.002); RB.push([aw, h * 0.2, X0 + w * 0.5 - px + aw / 2, top - h * (tier ? 0.32 : 0.2) + h * 0.1]); }
                    var nx = Math.floor((w - cw * 0.4) / cw), ny = Math.floor((h - ch) / ch), wx, wy;
                    for (wy = 0; wy < ny && wy < 30; wy++) for (wx = 0; wx < nx; wx++) {
                        var ph = Math.floor(Tm * 0.25 + bg3_r(sd, l, bi, wx, wy) * 7);
                        if (bg3_r(sd + ph, l * 97 + bi, wx, wy) > (P.win || 0.28)) continue;
                        RW.push([cw * 0.4, ch * 0.45, X0 + (w - nx * cw) / 2 + wx * cw + cw * 0.5, top + ch * 0.7 + wy * ch + ch * 0.225]);
                    }
                }
                x += w + gapW; bi++;
            }
            var gC = jzGrp(S, 'city'), gw = bg3_sub(gC, 'windows');       // windows first = drawn over the buildings
            for (z = 0; z < RW.length; z++) jzAddRect(gw, RW[z][0], RW[z][1], 0, RW[z][2], RW[z][3]);
            jzAddFill(gw, winLit, l ? 80 : 50);
            var gb = bg3_sub(gC, 'buildings');
            for (z = 0; z < RB.length; z++) jzAddRect(gb, RB[z][0], RB[z][1], 0, RB[z][2], RB[z][3]);
            jzAddFill(gb, jzLayC(sc, k * km));
            jzSetExpr(jzGX(gC).property('ADBE Vector Position'), hd + '[' + jzN(O0) + '-T*' + jzN(sp) + ',0]');
            jzSetExpr(jzXf(S, 'ADBE Position'), hd + '[0,(1-e)*' + jzN(H * 0.25) + ']');
        }
    }
});

/* ---- sunsetSun \u2014 \u5915\u65E5: sun sinking slowly behind the horizon (halo, sky glow), shimmering reflection strokes on the water, faint swell lines.
   AE: sun disc + halo = one solid with two masks, clipped at the horizon by a track matte */
jzReg('bg', 'sunsetSun', {
    plan: function (rng, st) { return { seed: bg3_seed(rng), hz: rng.range(0.7, 0.76), R: rng.range(0.12, 0.16), cx: rng.pick([rng.range(0.22, 0.34), rng.range(0.66, 0.78)]), k: rng.range(0.28, 0.36) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, sd = P.seed || 1, U = jzU(b), px = bg3_px(b), dk = bg3_dark(sc), k = P.k || 0.35, s9 = sd % 997, j;
        var hz = H * (P.hz || 0.72) * (W < H ? 0.97 : 1), R = U * (P.R || 0.14), pc = P.cx || 0.3, cx = W * (W < H ? 0.5 + (pc - 0.5) * 0.6 : pc);
        var base = dk ? bg3_glow(sc) : sc.accent, sunC = jzMixHex(sc.bg, base, k * (dk ? 1 : 0.7));
        var hd = bg3_hd(b) + 'var e=oc(BT/1.2),cy=' + jzN(hz - R * 0.5) + '+' + jzN(R * 0.35) + '*cl(BT/14)+(1-e)*' + jzN(R * 0.9) + ';';
        // sky glow: 0 half a frame above the horizon -> faint at the horizon, nothing below
        var SK = bg3_solid(b, base, 'bg sunsetSun sky', W, H);
        bg3_mask(SK, -W, hz - H * 0.25, W * 2, H * 3, { f: [0, H * 0.5] });
        bg3_mask(SK, -W, -H * 3, W * 2, hz, { mode: MaskMode.INTERSECT });
        jzSetExpr(jzXf(SK, 'ADBE Opacity'), hd + jzN(dk ? 12 : 10) + '*e');
        // sun + halo, matted to the sky above the horizon
        var c0 = R * 3.2, SN = bg3_solid(b, sunC, 'bg sunsetSun sun', c0 * 2, c0 * 2);
        bg3_cmask(SN, c0, c0, R);
        var mh = bg3_cmask(SN, c0, c0, R * 2, { f: [R * 2, R * 2] }); mh.property('ADBE Mask Opacity').setValue(45);
        jzSetExpr(jzXf(SN, 'ADBE Position'), hd + '[' + jzN(cx) + ',cy]');
        jzSetExpr(jzXf(SN, 'ADBE Opacity'), hd + '100*e');
        var MT = jzShapeLayer(b, 'bg sunsetSun horizon matte', 0, 0), gm = jzGrp(MT, 'above horizon');
        jzAddRect(gm, W * 3, hz + H, 0, W / 2, (hz - H) / 2); jzAddFill(gm, '#FFFFFF');
        SN.trackMatteType = TrackMatteType.ALPHA;
        // horizon line
        var HLn = jzRectLayer(b, 'bg sunsetSun horizon', W / 2, hz, W, Math.max(px, U * 0.0016), jzLayC(sc, 0.14));
        jzSetExpr(jzXf(HLn, 'ADBE Opacity'), hd + '100*e');
        // reflection strokes under the sun (split in two, widths / offsets shimmering)
        var WT = jzShapeLayer(b, 'bg sunsetSun water', cx, 0);
        for (j = 0; j < 18; j++) {
            var y = hz + U * 0.012 * Math.pow(j + 1, 1.3); if (y > H) break;
            var q = j / 18, hh = Math.max(1.2 * px, U * 0.0035 * (1 + j * 0.1));
            var wx = hd + 'var wv=' + jzN(R * (1.25 - q * 0.6)) + '*(0.55+0.45*n1(T*1.3+' + jzN(j * 1.9) + ',' + s9 + ')),xo=' + jzN(R * 0.18) + '*n1(T*0.9+' + jzN(j * 2.7) + ',' + (s9 + 3) +
                '),sp=0.2+0.15*n1(T*1.7+' + jzN(j * 3.3) + ',' + (s9 + 9) + '),lw=Math.max(0,wv*(1-sp));';
            // each rect is fully set up before the next item is added to the group (earlier references become invalid in AE)
            var g = jzGrp(WT, 'glint ' + (j + 1)), r1 = jzAddRect(g, 10, hh);
            jzSetExpr(r1.property('ADBE Vector Rect Size'), wx + '[lw,' + jzN(hh) + ']');
            jzSetExpr(r1.property('ADBE Vector Rect Position'), wx + '[xo-wv+lw/2,' + jzN(y + hh / 2) + ']');
            var r2 = jzAddRect(g, 10, hh);
            jzSetExpr(r2.property('ADBE Vector Rect Size'), wx + '[lw,' + jzN(hh) + ']');
            jzSetExpr(r2.property('ADBE Vector Rect Position'), wx + '[xo+wv*sp+lw/2,' + jzN(y + hh / 2) + ']');
            jzAddFill(g, sunC);
            jzSetExpr(jzGX(g).property('ADBE Vector Group Opacity'), hd + '100*e*' + jzN(0.75 * (1 - q)) + '*(0.6+0.4*n1(T*2+' + j + ',' + (s9 + 5) + '))');
        }
        // faint swell lines drifting left / right
        var SW = jzShapeLayer(b, 'bg sunsetSun swell', 0, 0), gs = jzGrp(SW, 'swell'), th = Math.max(px, U * 0.0018);
        for (j = 0; j < 7; j++) {
            var y2 = hz + (H - hz) * (0.12 + j * 0.13), wl = U * bg3_rr(0.15, 0.4, sd, j, 32), gj = bg3_sub(gs, 'line ' + (j + 1));
            jzAddRect(gj, wl, th, 0, wl / 2, th / 2);
            jzSetExpr(jzGX(gj).property('ADBE Vector Position'), hd + '[wr(T*' + jzN(U * 0.03 * (j % 2 ? 1 : -1)) + '+' + jzN(bg3_r(sd, j, 31) * W) + ',' + jzN(W * 1.4) + ')-' + jzN(W * 0.2) + ',' + jzN(y2) + ']');
        }
        jzAddFill(gs, jzLayC(sc, 0.08));
        jzSetExpr(jzXf(SW, 'ADBE Opacity'), hd + '80*e');
    }
});

/* ---- oceanWaves \u2014 \u6D77\u306E\u6CE2: stacked wave lines, closer (lower) ones bigger / slower / darker, travelling sideways.
   AE: one straight stroke per line bent by two Wave Warps (main swell + a smaller counter-running chop), phases driven by time */
jzReg('bg', 'oceanWaves', {
    plan: function (rng, st) { return { seed: bg3_seed(rng), n: rng.int(9, 13), hz: rng.range(0.48, 0.58), k: rng.range(0.12, 0.17), dir: rng.pick([1, -1]) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, sd = P.seed || 1, U = jzU(b), px = bg3_px(b), n = P.n || 11, hz = P.hz || 0.52, dir = P.dir || 1, i;
        for (i = 0; i < n; i++) {
            var q = (i + 1) / n, y0 = H * (hz + (1 - hz) * Math.pow(q, 1.55)), A = U * (0.004 + 0.028 * Math.pow(q, 1.4)), lam = W * (0.07 + 0.3 * q);
            var w = 0.9 + 0.5 * bg3_r(sd, i, 1), ph = bg3_r(sd, i, 2) * BG3_TAU;
            // main swell: one baked sine path (period lam) sliding sideways by its phase speed, wrapped every period
            var S = jzShapeLayer(b, 'bg oceanWaves ' + (i + 1), 0, y0), g = jzGrp(S, 'wave'), pts = [], x, st = lam / 12;
            for (x = -2 * lam; x <= W + lam + st; x += st) pts.push([x, A * Math.sin(x / lam * BG3_TAU + ph)]);
            bg3_path(g, pts, false, true);
            bg3_roundJoin(jzAddStroke(g, jzLayC(sc, (P.k || 0.14) * (0.45 + 0.75 * q)), Math.max(px, U * (0.0014 + 0.0035 * q))));
            jzSetExpr(jzGX(g).property('ADBE Vector Position'), bg3_hd(b) + '[wr(T*' + jzN(w * dir * lam / BG3_TAU) + ',' + jzN(lam) + '),0]');
            // counter-running chop on top of it (Wave Warp, phase driven by time)
            var w2 = jzEffect(S, 'ADBE Wave Warp', 'JZ Chop');
            jzEP(w2, 1, 1); jzEP(w2, 2, A * 0.35); jzEP(w2, 3, lam / 2.3); jzEP(w2, 4, 90); jzEP(w2, 5, 0); jzEP(w2, 6, 1);
            jzEX(w2, 7, bg3_hd(b) + '-(T*' + jzN(w * 1.3 * dir) + '+' + jzN(2 * ph) + ')*180/Math.PI');
            jzSetExpr(jzXf(S, 'ADBE Position'), bg3_hd(b) + 'var e=oc(BT/0.9);[0,' + jzN(y0) + '+(1-e)*' + jzN(H * 0.15 * q) + ']');
            jzSetExpr(jzXf(S, 'ADBE Opacity'), bg3_hd(b) + '100*oc(BT/0.9)');
        }
    }
});

/* ---- rainWindow \u2014 \u96E8\u306E\u7A93: slanted rain streaks falling fast, mist at the bottom, drops on the glass that sit, grow and run down with a trail.
   AE: streaks in 10 speed classes, each a static set sliding along the rain direction (Repeater copy = wrap); one group per drop */
jzReg('bg', 'rainWindow', {
    plan: function (rng, st) { return { seed: bg3_seed(rng), ang: rng.range(4, 13) * rng.pick([1, -1]), n: rng.int(100, 140), k: rng.range(0.15, 0.2), drops: rng.int(16, 24) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, sd = P.seed || 1, U = jzU(b), px = bg3_px(b), k = P.k || 0.15, dk = bg3_dark(sc), s9 = sd % 9973, i, gI;
        var n = Math.round((P.n || 70) * bg3_area(b)), tn = Math.tan((P.ang || 8) * Math.PI / 180), span = W + H * Math.abs(tn);
        var hd = bg3_hd(b) + 'var e=oc(BT/0.6);';
        // streaks are sorted into their speed class first, then each group is built completely before the next one is added
        var NG = 10, Lm = H * 0.09, Pg = H + Lm, S = jzShapeLayer(b, 'bg rainWindow rain', 0, 0), segs = [], z;
        for (gI = 0; gI < NG; gI++) segs.push([]);
        for (i = 0; i < n; i++) {
            var gi = Math.min(NG - 1, Math.floor(bg3_r(sd, i, 1) * NG)), L = H * bg3_rr(0.03, 0.09, sd, i, 2);
            var y = bg3_r(sd, i, 4) * Pg - Lm, x = bg3_r(sd, i, 3) * span - (tn > 0 ? H * tn : 0) + y * tn;
            segs[gi].push([[x, y], [x + L * tn, y + L]]);
        }
        for (gI = 0; gI < NG; gI++) {
            var vg = H * (1.3 + 0.8 * (gI + 0.5) / NG), gR = jzGrp(S, 'rain ' + (gI + 1));
            for (z = 0; z < segs[gI].length; z++) jzAddPath(gR, segs[gI][z], false);
            bg3_roundJoin(jzAddStroke(gR, jzLayC(sc, k), Math.max(px, U * 0.0016)));
            bg3_rep(gR, 2, Pg * tn, Pg, -1);
            jzSetExpr(jzGX(gR).property('ADBE Vector Position'), hd + 'var d=wr(T*' + jzN(vg) + '+' + jzN(bg3_r(sd, gI, 9) * Pg) + ',' + jzN(Pg) + ');[d*' + jzN(tn) + ',d]');
        }
        jzSetExpr(jzXf(S, 'ADBE Opacity'), hd + '85*e');
        // mist rising from the bottom
        var F = bg3_solid(b, sc.fg, 'bg rainWindow mist', W, H);
        bg3_mask(F, -W, H * 0.81, W * 2, H * 3, { f: [0, H * 0.38] });
        jzSetExpr(jzXf(F, 'ADBE Opacity'), hd + '5*e');
        // drops on the glass: sit + grow (60 % of their cycle), then slide down (accelerating) leaving a trail; new spot every cycle
        var dc = jzLayC(sc, k * 1.5), hc = dk ? jzLayC(sc, k * 3.5) : jzMixHex(sc.bg, '#FFFFFF', 0.7), D = jzShapeLayer(b, 'bg rainWindow drops', 0, 0);
        for (i = 0; i < (P.drops || 12); i++) {
            var per = bg3_rr(3.5, 6.5, sd, i, 11), r = U * bg3_rr(0.008, 0.019, sd, i, 15);
            var dh = hd + 'var per=' + jzN(per) + ',tt=T+' + jzN(bg3_r(sd, i, 12) * per) + ',u=wr(tt,per)/per,cy=Math.floor(tt/per);seedRandom(' + s9 + '+' + (i * 7919) + '+cy*131,true);' +
                'var x0=random(0.03,0.97)*' + jzN(W) + ',y0=random(0.04,0.7)*' + jzN(H) + ',r=' + jzN(r) + ',x=x0,y=y0,q=0,sl=u>=0.6,a=e*cl(u/0.08);' +
                'if(!sl){r*=0.65+0.35*u/0.6;}else{q=(u-0.6)/0.4;y=y0+iq(q)*' + jzN(H * 1.15) + ';x=x0+' + jzN(U * 0.006) + '*Math.sin(q*18+' + i + ');}var L=Math.max(0,y-' + jzN(r * 0.8) + '-y0);';
            var g = jzGrp(D, 'drop ' + (i + 1));
            // every shape is configured before its fill is added (AE invalidates earlier references in the same group)
            var gh = bg3_sub(g, 'glint'), eh = jzAddEllipse(gh, 10, 10);
            jzSetExpr(eh.property('ADBE Vector Ellipse Size'), dh + '[r*0.56,r*0.56]');
            jzSetExpr(eh.property('ADBE Vector Ellipse Position'), dh + '[-r*0.3,-r*0.35]');
            jzAddFill(gh, hc);
            jzSetExpr(jzGX(gh).property('ADBE Vector Group Opacity'), dh + '70*a');
            var gb = bg3_sub(g, 'body'), eb = jzAddEllipse(gb, 10, 10);
            jzSetExpr(eb.property('ADBE Vector Ellipse Size'), dh + '[r*1.8,r*2]');
            jzAddFill(gb, dc);
            jzSetExpr(jzGX(gb).property('ADBE Vector Group Opacity'), dh + '90*a');
            var gt = bg3_sub(g, 'trail'), rt = jzAddRect(gt, 10, 10, r * 0.22);
            jzSetExpr(rt.property('ADBE Vector Rect Size'), dh + '[' + jzN(r * 0.45) + ',L]');
            jzSetExpr(rt.property('ADBE Vector Rect Position'), dh + '[(x0-x)/2,-' + jzN(r * 0.8) + '-L/2]');
            jzAddFill(gt, dc);
            jzSetExpr(jzGX(gt).property('ADBE Vector Group Opacity'), dh + 'sl?55*e*(1-q*0.5):0');
            jzSetExpr(jzGX(g).property('ADBE Vector Position'), dh + '[x,y]');
        }
    }
});

/* ---- snowLayers \u2014 \u96EA: three depths of soft snowflakes (small / slow far, big / fast near) falling with wind and sway */
jzReg('bg', 'snowLayers', {
    plan: function (rng, st) { return { seed: bg3_seed(rng), wind: rng.range(-0.45, 0.45), dens: rng.range(0.85, 1.2) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, sd = P.seed || 1, U = jzU(b), l, i;
        var col = jzLum(sc.bg) < 0.88 ? jzMixHex(sc.bg, '#FFFFFF', 0.92) : jzLayC(sc, 0.35), area = bg3_area(b) * (P.dens || 1);
        var LY = [[70, 0.0045, 0.05, 0.012, 0.5], [36, 0.009, 0.09, 0.02, 0.6], [11, 0.02, 0.16, 0.035, 0.32]];
        for (l = 0; l < 3; l++) {
            var n = Math.round(LY[l][0] * area), vy = H * LY[l][2], vx = vy * (P.wind || 0), sz = U * LY[l][1], S = jzShapeLayer(b, 'bg snowLayers ' + (l + 1), 0, 0);
            for (i = 0; i < n; i++) {
                var s = sz * (0.7 + 0.6 * bg3_r(sd, l, i, 1)), f = bg3_rr(0.5, 1.3, sd, l, i, 2), g = jzGrp(S, 'flake ' + (i + 1));
                jzAddEllipse(g, s * 1.3, s * 1.3); jzAddFill(g, col, LY[l][4] * (0.6 + 0.4 * bg3_r(sd, l, i, 6)) * 100);
                jzSetExpr(jzGX(g).property('ADBE Vector Position'), bg3_hd(b) + '[wr(' + jzN(bg3_r(sd, l, i, 5) * W * 1.1) + '+T*' + jzN(vx) + '+Math.sin(T*' + jzN(f) + '+' + i + ')*' + jzN(U * LY[l][3]) + ',' + jzN(W * 1.1) + ')-' + jzN(W * 0.05) +
                    ',wr(' + jzN(bg3_r(sd, l, i, 3) * H * 1.2) + '+T*' + jzN(vy * (0.8 + 0.4 * bg3_r(sd, l, i, 4))) + ',' + jzN(H * 1.2) + ')-' + jzN(H * 0.1) + ']');
            }
            var bl = jzEffect(S, 'ADBE Gaussian Blur 2', 'JZ Soft Flakes'); jzEP(bl, 1, sz * 0.5); jzEP(bl, 3, 0);
            jzSetExpr(jzXf(S, 'ADBE Opacity'), bg3_hd(b) + '100*oc(BT/1)');
        }
    }
});

/* ---- fireworks \u2014 \u82B1\u706B: a shell every ~0.65 s: launch trail, flash, two rings of streaks spreading out and falling with gravity, crackle.
   AE: 6 shell slots (a slot shows every 6th shell); rings = one streak + head repeated round the burst centre */
jzReg('bg', 'fireworks', {
    plan: function (rng, st) { return { seed: bg3_seed(rng), per: rng.range(0.55, 0.8), k: rng.range(0.42, 0.55) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, sd = P.seed || 1, U = jzU(b), px = bg3_px(b), dk = bg3_dark(sc), cols = bg3_hues(sc), s9 = sd % 9973, j, q;
        var per = P.per || 0.65, NS = 6, lw = Math.max(px, U * 0.0024), hr = U * 0.0028, KK = (P.k || 0.48) * (dk ? 1 : 0.6), CA = [], HA = [];
        for (j = 0; j < cols.length; j++) { var c = dk ? cols[j] : jzMixHex(cols[j], sc.bg, 0.2); CA.push(bg3_col(c)); HA.push(bg3_col(dk ? jzMixHex(c, '#FFFFFF', 0.5) : c)); }
        var slot = function (j) {
            return bg3_hd(b) + 'var e=oc(BT/0.3),K=' + jzN(KK) + '*e,per=' + jzN(per) + ',idx=Math.floor(T/per),b=idx-wr(idx-' + j + ',' + NS + ');' +
                'seedRandom(' + s9 + '+b*101,true);var jt=random(),ox=random(0.1,0.9)*' + jzN(W) + ',oy=random(0.1,0.48)*' + jzN(H) + ',ci=Math.floor(random()*' + CA.length + ')%' + CA.length +
                ',n0=28+Math.floor(random()*14),V=random(0.7,1.05)*' + jzN(U) + ';var age=T-(b*per+jt*per*0.5),vis=age>=0&&age<=2.45,a=age-0.45,aa=Math.max(0,a),fd=a>0?Math.pow(Math.max(0,1-a/2),1.6):0;' +
                'function dd(tt,sp){return sp*(1-Math.exp(-2.6*tt))/2.6;}var C=[' + CA.join(',') + '],HC=[' + HA.join(',') + '];';
        };
        var S = jzShapeLayer(b, 'bg fireworks', 0, 0), FL = jzShapeLayer(b, 'bg fireworks flash', 0, 0);
        for (j = 0; j < NS; j++) {
            var hd = slot(j), gs = jzGrp(S, 'shell ' + (j + 1)), gB = bg3_sub(gs, 'burst');
            for (q = 0; q < 2; q++) {
                var f = q ? 0.55 * 0.925 : 0.925, gr = bg3_sub(gB, q ? 'inner ring' : 'outer ring');
                // each item is configured before the next one is added to its group (AE invalidates the earlier references)
                var ghd = bg3_sub(gr, 'head'), rh = jzAddRect(ghd, hr * 2, hr * 2);
                jzSetExpr(rh.property('ADBE Vector Rect Position'), hd + '[dd(aa,V*' + jzN(f) + '),0]');
                var fh = jzAddFill(ghd, '#FFFFFF');
                jzSetExpr(fh.property('ADBE Vector Fill Color'), hd + 'HC[ci]');
                jzSetExpr(jzGX(ghd).property('ADBE Vector Group Opacity'), hd + 'var st=Math.floor(T*12+1e-6);seedRandom(' + (s9 + 7) + '+b*31+st*' + (q + 3) + ',true);(a>0.9&&random()<0.35)?0:100');
                var gk = bg3_sub(gr, 'streak'), rk = jzAddRect(gk, 10, lw);
                var dx = 'var d1=dd(aa,V*' + jzN(f) + '),d0=dd(Math.max(0,a-0.16),V*' + jzN(f) + ');';
                jzSetExpr(rk.property('ADBE Vector Rect Size'), hd + dx + '[Math.max(0,d1-d0),' + jzN(lw) + ']');
                jzSetExpr(rk.property('ADBE Vector Rect Position'), hd + dx + '[(d0+d1)/2,0]');
                var fk = jzAddFill(gk, '#FFFFFF');
                jzSetExpr(fk.property('ADBE Vector Fill Color'), hd + 'C[ci]');
                var rp = bg3_rep(gr, 30, 0, 0, 0);
                jzSetExpr(rp.property('ADBE Vector Repeater Copies'), hd + 'n0');
                jzSetExpr(rp.property('ADBE Vector Repeater Transform').property('ADBE Vector Repeater Rotation'), hd + '360/n0');
                if (q) jzSetExpr(jzGX(gr).property('ADBE Vector Rotation'), hd + '180/n0');
            }
            jzSetExpr(jzGX(gB).property('ADBE Vector Position'), hd + '[ox,oy+' + jzN(U * 0.055) + '*aa*aa]');
            jzSetExpr(jzGX(gB).property('ADBE Vector Group Opacity'), hd + '(vis&&a>=0)?100*K*fd:0');
            // launch trail rising to the burst point
            var gL = bg3_sub(gs, 'launch'), rl = jzAddRect(gL, lw, 10);
            var lx = 'var y=' + jzN(H * 1.02) + '+(oy-' + jzN(H * 1.02) + ')*oq(age/0.45),y2=' + jzN(H * 1.02) + '+(oy-' + jzN(H * 1.02) + ')*oq(Math.max(0,age-0.12)/0.45);';
            jzSetExpr(rl.property('ADBE Vector Rect Size'), hd + lx + '[' + jzN(lw) + ',Math.max(0,y2-y)]');
            jzSetExpr(rl.property('ADBE Vector Rect Position'), hd + lx + '[ox+Math.sin(age*30)*' + jzN(U * 0.001) + ',(y+y2)/2]');
            var fl = jzAddFill(gL, '#FFFFFF');
            jzSetExpr(fl.property('ADBE Vector Fill Color'), hd + 'C[ci]');
            jzSetExpr(jzGX(gL).property('ADBE Vector Group Opacity'), hd + '(age>=0&&age<0.45)?60*K:0');
            // flash at the burst point
            var gF = jzGrp(FL, 'flash ' + (j + 1)), ef = jzAddEllipse(gF, U * 0.13, U * 0.13);
            jzSetExpr(ef.property('ADBE Vector Ellipse Position'), hd + '[ox,oy]');
            var ff = jzAddFill(gF, '#FFFFFF');
            jzSetExpr(ff.property('ADBE Vector Fill Color'), hd + 'C[ci]');
            jzSetExpr(jzGX(gF).property('ADBE Vector Group Opacity'), hd + '(a>=0&&a<0.25)?60*K*(1-a/0.25):0');
        }
        var bl = jzEffect(FL, 'ADBE Gaussian Blur 2', 'JZ Flash Soft'); jzEP(bl, 1, U * 0.05); jzEP(bl, 3, 0);
        if (dk) { S.blendingMode = BlendingMode.SCREEN; FL.blendingMode = BlendingMode.SCREEN; }
    }
});

/* ---- cloudLayers \u2014 \u96F2: three depths of flat puffy clouds drifting sideways at parallax speeds, breathing slightly */
jzReg('bg', 'cloudLayers', {
    plan: function (rng, st) { return { seed: bg3_seed(rng), dir: rng.pick([1, -1]), k: rng.range(0.06, 0.09) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, sd = P.seed || 1, U = jzU(b), k = P.k || 0.075, dir = P.dir || 1, l, c, p;
        var LY = [[0.1, 0.3, 0.55, 0.012, 0.55], [0.3, 0.55, 0.8, 0.026, 0.8], [0.72, 0.98, 1.15, 0.05, 1.1]];
        for (l = 0; l < 3; l++) {
            var cw0 = U * 0.5 * LY[l][2], Lp = W + cw0 * 2.4, S = jzShapeLayer(b, 'bg cloudLayers ' + (l + 1), 0, 0), gA = jzGrp(S, 'clouds');
            for (c = 0; c < 4; c++) {
                var cw = cw0 * bg3_rr(0.75, 1.2, sd, l, c, 1), y = H * bg3_rr(LY[l][0], LY[l][1], sd, l, c, 3), np = 5 + (bg3_h(sd, l, c) % 3), rmin = 1e9;
                var g = bg3_sub(gA, 'cloud ' + (c + 1));
                for (p = 0; p < np; p++) {
                    var f = (p + 0.5) / np, pr = cw / np * (0.75 + 1.05 * Math.sin(Math.PI * f)) * (0.85 + 0.3 * bg3_r(sd, l, c, p, 4));
                    jzAddEllipse(g, pr * 2, pr * 2, cw * f + bg3_rs(sd, l, c, p, 5) * cw * 0.03, -pr); rmin = Math.min(rmin, pr);
                }
                jzAddRect(g, cw * (1 - 1 / np), rmin, 0, cw * 0.5, -rmin / 2);
                jzSetExpr(jzGX(g).property('ADBE Vector Position'), bg3_hd(b) + 'var e=oc(BT/1);[wr(' + jzN((c + bg3_r(sd, l, c, 2) * 0.6) / 4 * Lp) + '+T*' + jzN(U * LY[l][3] * dir) + ',' + jzN(Lp) + ')-' + jzN(cw * 1.2) +
                    ',' + jzN(y) + '+(1-e)*' + jzN(H * 0.05 * (l + 1)) + ']');
                jzSetExpr(jzGX(g).property('ADBE Vector Scale'), bg3_hd(b) + 'var s=100+2*Math.sin(T*0.6+' + c + ');[s,s]');
            }
            jzAddFill(gA, jzLayC(sc, k * LY[l][4]));
        }
    }
});

/* ---- mountains \u2014 \u5C71\u4E26\u307F: 3-4 ridged mountain ranges (same noise as the browser), far ones paler, scrolling at parallax, mist in the valleys */
jzReg('bg', 'mountains', {
    plan: function (rng, st) { return { seed: bg3_seed(rng), n: rng.int(3, 4), k: rng.range(0.07, 0.1), dir: rng.pick([1, -1]), mist: rng.chance(0.7) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, sd = P.seed || 1, U = jzU(b), px = bg3_px(b), n = P.n || 3, k = P.k || 0.085, dir = P.dir || 1, port = H > W, l, o;
        var m = jzClamp(Math.round(W / (U * 0.01)), 60, 220), dxs = W / m, T0 = b.cut.start || 0, T1 = T0 + b.comp.duration;
        for (l = 0; l < n; l++) {
            var q = n > 1 ? l / (n - 1) : 1, base = H * ((port ? 0.68 : 0.64) + 0.14 * q), A = H * (port ? 0.17 : 0.3) * (1 - 0.35 * q);
            var s2 = U * (0.42 + 0.2 * (1 - q)), sp = 0.012 * (l + 1) * dir, o0 = T0 * sp + l * 7.3, o1 = T1 * sp + l * 7.3, O = o0 * s2;
            var xa = Math.min(o0, o1) * s2 - 2 * px - dxs, xb = W + Math.max(o0, o1) * s2 + 2 * px + dxs, pts = [[xa - O, H + 2 * px]], x, xl = xa;
            for (x = xa; x <= xb + dxs * 0.5; x += dxs) {
                var X = x / s2, v = 0, amp = 1, f = 1, nrm = 0;
                for (o = 0; o < 4; o++) { v += (1 - Math.abs(bg3_n1(X * f, sd + l * 31 + o * 7))) * amp; nrm += amp; amp *= 0.48; f *= 2.2; }
                v = jzClamp((v / nrm - 0.4) * 1.8, 0, 1);
                pts.push([x - O, base - A * v * v]); xl = x;
            }
            pts.push([xl - O, H + 2 * px]);
            var hd = bg3_hd(b) + 'var e=oc(BT/1.1),rise=(1-e)*' + jzN(H * 0.3 * (1 - q * 0.4)) + ';';
            var S = jzShapeLayer(b, 'bg mountains ' + (l + 1), 0, 0), g = jzGrp(S, 'range');
            jzAddPath(g, pts, true); jzAddFill(g, jzLayC(sc, k * (0.45 + 0.75 * q)));
            jzSetExpr(jzGX(g).property('ADBE Vector Position'), '[-time*' + jzN(sp * s2) + ',0]');
            jzSetExpr(jzXf(S, 'ADBE Position'), hd + '[0,rise]');
            if (P.mist !== false && l < n - 1) {      // valley mist: background colour fading in below the ridge
                var y0 = base - A * 0.25, y1 = base + H * 0.06, MS = bg3_solid(b, sc.bg, 'bg mountains mist ' + (l + 1), W, H);
                bg3_mask(MS, -W, (y0 + y1) / 2, W * 2, H * 3, { f: [0, y1 - y0] });
                jzXf(MS, 'ADBE Opacity').setValue(55);
                jzSetExpr(jzXf(MS, 'ADBE Position'), hd + '[value[0],value[1]+rise]');
            }
        }
    }
});

/* ---- filmStrip \u2014 \u30D5\u30A3\u30EB\u30E0: film strips along two edges sliding in, perforations + frame lines running, flickering scratches and dust */
jzReg('bg', 'filmStrip', {
    plan: function (rng, st) { return { seed: bg3_seed(rng), dir: rng.pick([1, -1]), spd: rng.range(0.6, 1.2), scratch: rng.chance(0.7) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, sd = P.seed || 1, U = jzU(b), px = bg3_px(b), dk = bg3_dark(sc), vert = H > W, s9 = sd % 9973, side, i;
        var bw = U * 0.085, L = vert ? H : W, M = vert ? W : H, lw = Math.max(px, U * 0.002);
        var band = dk ? jzLayC(sc, 0.05) : jzLayC(sc, 0.13), hole = dk ? jzLayC(sc, 0.17) : jzMixHex(sc.bg, '#FFFFFF', 0.55), edge = dk ? jzLayC(sc, 0.12) : jzLayC(sc, 0.22);
        var p = bw * 0.62, hw = bw * 0.3, hh = bw * 0.38;
        var hd = bg3_hd(b) + 'var e=oe(BT/0.55),sl=(1-e)*' + jzN(bw * 1.3) + ',off=wr(T*' + jzN(p * (P.spd || 0.9) * (P.dir || 1)) + ',' + jzN(p) + ');';
        var xy = function (a, c) { return vert ? [c, a] : [a, c]; };                                    // a: along the strip, c: across
        var xs = function (a, c) { return vert ? '[' + c + ',' + a + ']' : '[' + a + ',' + c + ']'; };
        var S = jzShapeLayer(b, 'bg filmStrip', 0, 0);
        for (side = 0; side < 2; side++) {
            var g = jzGrp(S, side ? 'far strip' : 'near strip');
            var gd = bg3_sub(g, 'frame lines'), dz = xy(lw, bw * 0.2), dc = xy(lw / 2, bw * (side ? 0.72 : 0.08) + bw * 0.1), s4 = xy(4 * p, 0);
            jzAddRect(gd, dz[0], dz[1], 0, dc[0], dc[1]); jzAddFill(gd, edge);
            bg3_rep(gd, Math.ceil(L / (4 * p)) + 3, s4[0], s4[1], 0);
            jzSetExpr(jzGX(gd).property('ADBE Vector Position'), hd + 'var a=wr(off,' + jzN(4 * p) + ')-' + jzN(4 * p) + ';' + xs('a', '0'));
            var gh = bg3_sub(g, 'perforations'), hz = xy(hw, hh), hc = xy(p / 2, bw / 2 + (side ? 0.12 : -0.12) * bw), s1 = xy(p, 0);
            jzAddRect(gh, hz[0], hz[1], bw * 0.06, hc[0], hc[1]); jzAddFill(gh, hole);
            bg3_rep(gh, Math.ceil(L / p) + 3, s1[0], s1[1], 0);
            jzSetExpr(jzGX(gh).property('ADBE Vector Position'), hd + 'var a=off-' + jzN(p) + ';' + xs('a', '0'));
            var ge = bg3_sub(g, 'edge'), ez = xy(L, lw), ec = xy(L / 2, side ? lw / 2 : bw - lw / 2);
            jzAddRect(ge, ez[0], ez[1], 0, ec[0], ec[1]); jzAddFill(ge, edge);
            var gb = bg3_sub(g, 'band'), bz = xy(L + 4 * px, bw), bc = xy(L / 2, bw / 2);
            jzAddRect(gb, bz[0], bz[1], 0, bc[0], bc[1]); jzAddFill(gb, band);
            jzSetExpr(jzGX(g).property('ADBE Vector Position'), hd + xs('0', side ? jzN(M - bw) + '+sl' : '-sl'));
        }
        if (P.scratch !== false) {        // scratches + dust re-drawn on a 12 fps clock
            var X = jzShapeLayer(b, 'bg filmStrip scratches', 0, 0), scol = dk ? jzLayC(sc, 0.35) : jzLayC(sc, 0.3);
            for (i = 0; i < 3; i++) {
                // the rect is set up before the fill is added (AE invalidates the rect reference once a sibling is added)
                var gs = jzGrp(X, 'scratch ' + (i + 1)), rs = jzAddRect(gs, 2, 2);
                var ex = hd + 'var st=Math.floor(T*12+1e-6);seedRandom(' + s9 + '+Math.floor(st/4)*37+' + i + ',true);var x=random()*' + jzN(L) + ';seedRandom(' + (s9 + 500) + '+st*37+' + i + ',true);' +
                    'var on=random()<=0.55;x+=random(-1,1)*' + jzN(U * 0.004) + ';var w=random(' + jzN(Math.max(px, U * 0.0008)) + ',' + jzN(Math.max(px, U * 0.002)) + '),al=random(0.1,0.22);';
                jzSetExpr(rs.property('ADBE Vector Rect Size'), ex + xs('w', jzN(M + 4 * px)));
                jzAddFill(gs, scol);
                jzSetExpr(jzGX(gs).property('ADBE Vector Position'), ex + xs('x', jzN(M / 2)));
                jzSetExpr(jzGX(gs).property('ADBE Vector Group Opacity'), ex + 'on?100*e*al:0');
            }
            for (i = 0; i < 6; i++) {
                var gu = jzGrp(X, 'dust ' + (i + 1)), ru = jzAddRect(gu, 2, 2);
                var du = hd + 'var st=Math.floor(T*12+1e-6);seedRandom(' + (s9 + 900) + '+st*53+' + i + ',true);var on=random()<=0.5,r=random(' + jzN(U * 0.001) + ',' + jzN(U * 0.003) + '),x=random()*' + jzN(W) + ',y=random()*' + jzN(H) + ';';
                jzSetExpr(ru.property('ADBE Vector Rect Size'), du + '[2*r,1.4*r]');
                jzAddFill(gu, scol);
                jzSetExpr(jzGX(gu).property('ADBE Vector Position'), du + '[x+r,y+0.7*r]');
                jzSetExpr(jzGX(gu).property('ADBE Vector Group Opacity'), du + 'on?25*e:0');
            }
        }
    }
});

/* ---- vhsBand \u2014 VHS\u30CE\u30A4\u30BA: a rolling band of tape noise with a soft light smear and tracking lines, torn head-switching strip at the bottom.
   AE: noise = Noise -> Mosaic -> Threshold -> Tint on grey solids (new grain every frame), screen on dark / multiply on light schemes */
jzReg('bg', 'vhsBand', {
    plan: function (rng, st) { return { seed: bg3_seed(rng), h: rng.range(0.07, 0.12), spd: rng.range(0.08, 0.16) * rng.pick([1, -1]), k: rng.range(0.22, 0.32) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, sd = P.seed || 1, U = jzU(b), px = bg3_px(b), dk = bg3_dark(sc), s9 = sd % 9973, i;
        var col = dk ? sc.fg : jzMixHex(sc.fg, sc.bg, 0.2), kk = (P.k || 0.26) * (dk ? 1 : 0.75), bh = H * (P.h || 0.09), cx = 9 * px, cy = 2 * px;
        var hd = bg3_hd(b) + 'var e=oc(BT/0.3),st=Math.floor(T*12+1e-6),y=wr(T*' + jzN(H * (P.spd || 0.12)) + '+' + jzN(bg3_r(sd, 1) * H) + ',' + jzN(H * 1.3) + ')-' + jzN(H * 0.15) + ';';
        // rolling noise band (jumps sideways a little every step)
        var NB = bg3_vhsNoise(b, col, dk, 'bg vhsBand noise', W, bh, cx, cy);
        jzSetExpr(jzXf(NB, 'ADBE Position'), hd + 'seedRandom(' + s9 + '+st*17,true);[' + jzN(W / 2) + '+random(-1,1)*' + jzN(U * 0.01) + ',y+' + jzN(bh / 2) + ']');
        jzSetExpr(jzXf(NB, 'ADBE Opacity'), hd + jzN(kk * 100) + '*e');
        // soft light smear around the band
        var LG = bg3_solid(b, bg3_lightOn(sc), 'bg vhsBand smear', W, bh * 2);
        bg3_mask(LG, -W, bh * 0.4, W * 2, bh * 1.2, { f: [0, bh * 0.8] });
        jzSetExpr(jzXf(LG, 'ADBE Position'), hd + '[' + jzN(W / 2) + ',y+' + jzN(0.4 * bh) + ']');
        jzSetExpr(jzXf(LG, 'ADBE Opacity'), hd + jzN(dk ? 5 : 10) + '*e');
        // tracking lines inside the band
        var TL = jzShapeLayer(b, 'bg vhsBand tracking', 0, 0), th = Math.max(px, U * 0.0016);
        for (i = 0; i < 3; i++) {
            // the rect is set up before the fill is added (AE invalidates the rect reference once a sibling is added)
            var g = jzGrp(TL, 'line ' + (i + 1)), rl = jzAddRect(g, 10, th);
            var lx = hd + 'seedRandom(' + (s9 + 300) + '+Math.floor(st/2)*29+' + i + ',true);var ly=y+' + jzN(bh) + '*random();seedRandom(' + (s9 + 600) + '+st*29+' + i + ',true);var lx=random()*' + jzN(W * 0.6) + ',lw=random(0.2,0.6)*' + jzN(W) + ';';
            jzSetExpr(rl.property('ADBE Vector Rect Size'), lx + '[lw,' + jzN(th) + ']');
            jzAddFill(g, jzLayC(sc, dk ? 0.3 : 0.2));
            jzSetExpr(jzGX(g).property('ADBE Vector Position'), lx + '[lx+lw/2,ly+' + jzN(th / 2) + ']');
        }
        jzSetExpr(jzXf(TL, 'ADBE Opacity'), hd + '50*e');
        // head-switching strip: 4 rows of noise, knocked sideways (more at the top rows)
        var hb = H * 0.028;
        for (i = 0; i < 4; i++) {
            var HS = bg3_vhsNoise(b, col, dk, 'bg vhsBand head ' + (i + 1), W, hb / 4 + px, 0, 0, 185);
            jzSetExpr(jzXf(HS, 'ADBE Position'), hd + 'seedRandom(' + (s9 + 800) + '+st*13+' + i + ',true);[' + jzN(W / 2 + U * 0.02 * (4 - i) / 4) + '+random(-1,1)*' + jzN(U * 0.03) + ',' + jzN(H - hb + hb * i / 4 + hb / 8 + px / 2) + ']');
            jzSetExpr(jzXf(HS, 'ADBE Opacity'), hd + jzN(kk * 90) + '*e');
        }
    }
});

/* ---- tornPaper \u2014 \u7834\u308C\u7D19: two sheets of paper with torn edges slide in over the top & bottom (or the diagonal corners / the sides), soft shadow + light fibrous rim */
jzReg('bg', 'tornPaper', {
    plan: function (rng, st) { return { seed: bg3_seed(rng), v: rng.pick(['tb', 'tb', 'diag', 'side']), k: rng.range(0.05, 0.08) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, sd = P.seed || 1, U = jzU(b), px = bg3_px(b), dk = bg3_dark(sc), k = P.k || 0.065, v = P.v || 'tb', i, j;
        if (v === 'side' && H > W) v = 'tb';
        // [A, B, n] = edge from A to B, the sheet covers the side of the normal n
        var SS = v === 'diag' ? [[[-W * 0.05, H * 0.58], [W * 0.5, H * 1.05], [-0.7, 0.7]], [[W * 0.55, -H * 0.05], [W * 1.05, H * 0.48], [0.7, -0.7]]]
            : v === 'side' ? [[[W * 0.13, -H * 0.05], [W * 0.1, H * 1.05], [-1, 0]], [[W * 0.9, -H * 0.05], [W * 0.87, H * 1.05], [1, 0]]]
                : [[[-W * 0.05, H * 0.83], [W * 1.05, H * 0.79], [0, 1]], [[-W * 0.05, H * 0.13], [W * 1.05, H * 0.17], [0, -1]]];
        var tones = dk ? [jzLayC(sc, k), jzMixHex(sc.bg, sc.accent, k * 1.6)] : [jzMixHex(sc.bg, '#FFFFFF', 0.5), jzLayC(sc, k)], far = Math.sqrt(W * W + H * H), sd2 = U * 0.008;
        for (i = 0; i < 2; i++) {
            var A = SS[i][0], B = SS[i][1], nr = SS[i][2], dx = B[0] - A[0], dy = B[1] - A[1], len = Math.sqrt(dx * dx + dy * dy), N = Math.min(260, Math.ceil(len / (U * 0.009)));
            var tx = dx / len, ty = dy / len, pts = [], rim = [];
            for (j = 0; j <= N; j++) {
                var u = j / N, o = U * (0.018 * bg3_n1(u * 9, sd + i * 17) + 0.007 * bg3_n1(u * 45, sd + i * 5) + 0.0035 * bg3_rs(sd, i, j));
                pts.push([A[0] + dx * u + nr[0] * o, A[1] + dy * u + nr[1] * o]);
                rim.push(j ? [pts[j][0] - nr[0] * U * 0.002, pts[j][1] - nr[1] * U * 0.002] : [pts[0][0], pts[0][1]]);
            }
            var poly = pts.concat([[B[0] + nr[0] * far + tx * far * 0.2, B[1] + nr[1] * far + ty * far * 0.2], [A[0] + nr[0] * far - tx * far * 0.2, A[1] + nr[1] * far - ty * far * 0.2]]);
            var L = jzShapeLayer(b, 'bg tornPaper ' + (i + 1), 0, 0);
            var gr = jzGrp(L, 'torn rim'); jzAddPath(gr, rim, false);
            bg3_roundJoin(jzAddStroke(gr, dk ? jzLayC(sc, k * 2.4) : jzMixHex(tones[i % 2], '#FFFFFF', 0.75), Math.max(px, U * 0.004)));
            var gs = jzGrp(L, 'sheet'); jzAddPath(gs, poly, true); jzAddFill(gs, tones[i % 2]);
            var gd = jzGrp(L, 'shadow'); jzAddPath(gd, poly, true); jzAddFill(gd, '#000000', dk ? 35 : 10);
            jzGX(gd).property('ADBE Vector Position').setValue([-nr[0] * sd2 * 0.5, -nr[1] * sd2 * 0.5 + sd2 * 0.6]);
            jzSetExpr(jzXf(L, 'ADBE Position'), bg3_hd(b) + 'var ip=oc((BT-' + jzN(i * 0.12) + ')/0.6),pu=(1-ip)*' + jzN(U * 0.35) + '+Math.sin(T*0.5+' + (i * 2) + ')*' + jzN(U * 0.004) + ';[' + jzN(nr[0]) + '*pu,' + jzN(nr[1]) + '*pu]');
        }
    }
});

/* ---- godRays \u2014 \u5149\u8292: shafts of light fanning down from a source above the frame, each beam widening / brightening with slow noise, dust motes */
jzReg('bg', 'godRays', {
    plan: function (rng, st) { return { seed: bg3_seed(rng), x: rng.pick([rng.range(0.12, 0.35), rng.range(0.65, 0.88), 0.5]), n: rng.int(7, 11), spread: rng.range(45, 75), k: rng.range(0.11, 0.16), dust: rng.chance(0.75) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, sd = P.seed || 1, U = jzU(b), dk = bg3_dark(sc), s9 = sd % 997, n = P.n || 9, i, q;
        var rc = dk ? bg3_lightOn(sc) : (jzLum(sc.bg) < 0.8 ? '#FFFFFF' : jzMixHex(sc.accent, sc.bg, 0.3)), kk = (P.k || 0.13) * (dk ? 1.35 : 1.4);
        var L = Math.sqrt(W * W + H * H) * 1.25, sy = -H * 0.12, xr = P.x == null ? 0.5 : P.x, w0 = 3 * Math.PI / 180, FW = [1.8, 1, 0.45], OP = [16, 30, 55];
        var hd = bg3_hd(b) + 'var e=oc(BT/1),sx=' + jzN(W * xr) + '+' + jzN(W * 0.04) + '*Math.sin(T*0.1+' + (sd % 7) + '),base=Math.atan2(' + jzN(H * 0.55 - sy) + ',' + jzN(W * 0.5) + '-sx);';
        var S = jzShapeLayer(b, 'bg godRays', W * xr, sy);
        for (i = 0; i < n; i++) {
            var g = jzGrp(S, 'beam ' + (i + 1));
            for (q = 0; q < 3; q++) {     // nested wedges -> soft falloff across the beam
                var gq = bg3_sub(g, 'wedge ' + (q + 1)), ww = w0 * FW[q] / 2;
                jzAddPath(gq, [[0, 0], [Math.cos(-ww) * L, Math.sin(-ww) * L], [Math.cos(ww) * L, Math.sin(ww) * L]], true);
                jzAddFill(gq, rc, OP[q]);
            }
            var bh = hd + 'var a=base+(' + jzN((P.spread || 60) * (i / (n - 1) - 0.5) + bg3_rs(sd, i, 1) * 4) + '+3*n1(T*0.15+' + jzN(i * 1.3) + ',' + s9 + '))*Math.PI/180,w=' +
                jzN((1.2 + 3.6 * bg3_r(sd, i, 2)) * Math.PI / 180) + '*(0.75+0.25*n1(T*0.4+' + i + ',' + (s9 + 7) + ')),lv=0.5+0.5*n1(T*0.35+' + jzN(i * 2.1) + ',' + (s9 + 3) + ');';
            jzSetExpr(jzGX(g).property('ADBE Vector Rotation'), bh + 'a*180/Math.PI');
            jzSetExpr(jzGX(g).property('ADBE Vector Scale'), bh + '[100,100*w/' + jzN(w0) + ']');
            jzSetExpr(jzGX(g).property('ADBE Vector Group Opacity'), bh + '100*(0.35+0.65*lv)');
        }
        // light fading with the distance from the source (feathered circle round the layer origin = the source)
        bg3_cmask(S, 0, 0, L * 0.3, { f: [L * 0.6, L * 0.6] });
        var bl = jzEffect(S, 'ADBE Gaussian Blur 2', 'JZ Rays Soft'); jzEP(bl, 1, U * 0.012); jzEP(bl, 3, 0);
        jzSetExpr(jzXf(S, 'ADBE Position'), hd + '[sx,' + jzN(sy) + ']');
        jzSetExpr(jzXf(S, 'ADBE Opacity'), hd + jzN(Math.min(100, kk * 130)) + '*e');
        if (P.dust !== false) {        // dust motes drifting through the light
            var D = jzShapeLayer(b, 'bg godRays dust', 0, 0);
            for (i = 0; i < 26; i++) {
                var r = U * bg3_rr(0.0012, 0.003, sd, i, 15), gm = jzGrp(D, 'mote ' + (i + 1)); jzAddRect(gm, r * 2, r * 2); jzAddFill(gm, rc);
                var mh = hd + 'var x=wr(' + jzN(bg3_r(sd, i, 11) * W) + '+T*' + jzN(U * bg3_rs(sd, i, 12) * 0.02) + '+Math.sin(T*0.5+' + i + ')*' + jzN(U * 0.01) + ',' + jzN(W) + '),y=wr(' + jzN(bg3_r(sd, i, 13) * H) + '+T*' + jzN(U * bg3_rr(-0.02, 0.01, sd, i, 14)) + ',' + jzN(H) + ');';
                jzSetExpr(jzGX(gm).property('ADBE Vector Position'), mh + '[x,y]');
                jzSetExpr(jzGX(gm).property('ADBE Vector Group Opacity'), mh + 'var dx=x-sx,dy=y+' + jzN(-sy) + ',nr=cl(1-Math.sqrt(dx*dx+dy*dy)/' + jzN(L) + ');100*e*(0.15+0.35*nr)*(0.5+0.5*Math.sin(T*' + jzN(bg3_rr(0.8, 2, sd, i, 16)) + '+' + i + '))');
            }
        }
    }
});

/* ---- vignettePulse \u2014 \u8272\u306E\u5468\u8FBA\u5149: two colour glows in opposite corners (or one coloured vignette) pulsing gently */
jzReg('bg', 'vignettePulse', {
    plan: function (rng, st) { return { seed: bg3_seed(rng), k: rng.range(0.3, 0.42), two: rng.chance(0.6), rate: rng.range(0.35, 0.55) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, U = jzU(b), dk = bg3_dark(sc), cols = bg3_hues(sc), R = Math.sqrt(W * W + H * H) / 2, i;
        var cA = jzMixHex(sc.bg, cols[0], 0.7), cB = jzMixHex(sc.bg, cols[1] || cols[0], 0.7);
        var hd = bg3_hd(b) + 'var e=oc(BT/0.8),pu=0.75+0.25*Math.sin(T*' + jzN(BG3_TAU * (P.rate || 0.45)) + '),kk=' + jzN((P.k || 0.35) * (dk ? 1 : 0.8)) + '*e*pu;';
        if (P.two !== false) {
            var CS = [[0, 0, cA, 0], [W, H, cB, 1.7]], r0 = R * 1.25;
            for (i = 0; i < 2; i++) {
                var V = bg3_solid(b, CS[i][2], 'bg vignettePulse ' + (i ? 'B' : 'A'), r0 * 2.2, r0 * 2.2);
                bg3_cmask(V, r0 * 1.1, r0 * 1.1, r0 * 0.45, { f: [r0 * 0.9, r0 * 0.9] });
                jzXf(V, 'ADBE Position').setValue([CS[i][0], CS[i][1]]);
                jzSetExpr(jzXf(V, 'ADBE Scale'), hd + 'var s=100*(1+0.064*Math.sin(T*0.6+' + CS[i][3] + '));[s,s]');
                jzSetExpr(jzXf(V, 'ADBE Opacity'), hd + '100*Math.min(1,kk)');
            }
        } else {
            var ri = U * 0.32 * 0.99, ro = R * 1.05, c = R * 1.2, VS = bg3_solid(b, cA, 'bg vignettePulse', c * 2, c * 2);
            var m = bg3_cmask(VS, c, c, (ri + ro) / 2, { f: [ro - ri, ro - ri], inv: true });
            jzSetExpr(m.property('ADBE Mask Offset'), hd + jzN(U * 0.32) + '*(1.08-0.12*pu)-' + jzN(ri));
            jzXf(VS, 'ADBE Position').setValue([W / 2, H / 2]);
            jzSetExpr(jzXf(VS, 'ADBE Opacity'), hd + '100*Math.min(1,kk*1.1)');
        }
    }
});

/* ---- kaleidoscope \u2014 \u4E07\u83EF\u93E1: small shapes drifting outwards, mirrored n-fold (each piece + its mirror image), slowly turning; two polygon rings */
jzReg('bg', 'kaleidoscope', {
    plan: function (rng, st) { return { seed: bg3_seed(rng), n: rng.pick([6, 8, 8, 10]), m: rng.int(6, 9), k: rng.range(0.075, 0.1), spd: rng.range(0.05, 0.1) * rng.pick([1, -1]) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, sd = P.seed || 1, U = jzU(b), px = bg3_px(b), n = P.n || 8, m = P.m || 7, k = P.k || 0.085, j, q, z;
        var R = Math.sqrt(W * W + H * H) * 0.58, seg = Math.PI / n, r0 = U * 0.13;
        var hd = bg3_hd(b) + 'var e=oc(BT/0.8),rot=T*' + jzN(P.spd || 0.07) + ';';
        var S = jzShapeLayer(b, 'bg kaleidoscope', W / 2, H / 2);
        // faint polygon rings anchoring the symmetry (drawn on top), turning against each other
        var gr = jzGrp(S, 'rings'), RG = [[U * 0.46, -1], [U * 0.82, 1]];
        for (q = 0; q < 2; q++) {
            var gq = bg3_sub(gr, 'ring ' + (q + 1)), pts = [];
            for (z = 0; z < 2 * n; z++) pts.push([Math.cos(z * seg) * RG[q][0], Math.sin(z * seg) * RG[q][0]]);
            jzAddPath(gq, pts, true);
            jzSetExpr(jzGX(gq).property('ADBE Vector Rotation'), hd + (RG[q][1] > 0 ? '-' : '') + 'rot*1.5*180/Math.PI');
        }
        jzAddStroke(gr, jzLayC(sc, k * 0.9), Math.max(px, U * 0.002));
        jzSetExpr(jzGX(gr).property('ADBE Vector Group Opacity'), hd + '100*e');
        // pieces: unit shapes (100 px) scaled by expression; the mirror copy is flipped; Repeater makes the n-fold symmetry
        var SH = [[[100, 0], [-50, 80], [-50, -30]], [[100, 0], [0, 42], [-70, 0], [0, -42]]];
        for (j = 0; j < m; j++) {
            var type = bg3_h(sd, j) % 3, v = 0.6 + 0.8 * bg3_r(sd, j, 2), line = bg3_r(sd, j, 5) < 0.35;
            var jh = hd + 'var r=' + jzN(r0) + '+wr(' + jzN(bg3_r(sd, j, 1) * (R - r0)) + '+T*' + jzN(U * 0.05 * v) + ',' + jzN(R - r0) + '),fd=cl((r-' + jzN(r0) + ')/' + jzN(U * 0.15) + ')*cl((' + jzN(R) + '-r)/' + jzN(U * 0.2) + ')*e,' +
                'phi=' + jzN(seg) + '*(0.15+0.7*(0.5+0.5*Math.sin(T*' + jzN(0.35 * v) + '+' + jzN(j * 1.9) + '))),s=r*' + jzN(0.09 + 0.1 * bg3_r(sd, j, 3)) + '*fd,own=T*' + jzN(bg3_rs(sd, j, 4) * 1.2) + '+' + j + ';';
            var g = jzGrp(S, 'piece ' + (j + 1));
            for (q = 0; q < 2; q++) {
                var gi = bg3_sub(g, q ? 'mirror' : 'shape'), ng = q ? '-' : '';
                if (type === 2) jzAddEllipse(gi, 110, 110); else jzAddPath(gi, SH[type], true);
                jzSetExpr(jzGX(gi).property('ADBE Vector Position'), jh + '[r*Math.cos(phi),' + ng + 'r*Math.sin(phi)]');
                jzSetExpr(jzGX(gi).property('ADBE Vector Rotation'), jh + ng + '(phi+own)*180/Math.PI');
                jzSetExpr(jzGX(gi).property('ADBE Vector Scale'), jh + '[s,' + ng + 's]');
            }
            if (line) jzAddStroke(g, jzTintC(sc, k * 2.4), Math.max(px, U * 0.0028)); else jzAddFill(g, jzLayC(sc, k));
            var rp = bg3_rep(g, n, 0, 0, 0);
            rp.property('ADBE Vector Repeater Transform').property('ADBE Vector Repeater Rotation').setValue(360 / n);
            jzSetExpr(jzGX(g).property('ADBE Vector Rotation'), jh + 'rot*180/Math.PI');
            jzSetExpr(jzGX(g).property('ADBE Vector Group Opacity'), jh + 'fd>0.02?100:0');
        }
    }
});

/* ---- marble \u2014 \u5927\u7406\u77F3: thin marble veins (turbulent sine iso-lines of the browser's fbm field) with a faint glow, optional accent veins,
   soft cloudiness; the whole texture drifts slowly. AE: the veins are traced once (marching squares) into smooth stroked paths */
jzReg('bg', 'marble', {
    plan: function (rng, st) { return { seed: bg3_seed(rng), ang: rng.range(0, 3.14), freq: rng.range(1.4, 2.2), turb: rng.range(7, 10), k: rng.range(0.17, 0.22), acc: rng.chance(0.5), dir: rng.pick([1, -1]) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, sd = P.seed || 1, U = jzU(b), px = bg3_px(b), dk = bg3_dark(sc), M = U * 0.12, s = sd % 13, C = {}, G = 64, i, j, z;
        var c2 = jzContrast(sc.accent, sc.bg) > 1.3 ? sc.accent : (sc.accent2 || sc.fg);
        var w = W + 2 * M, h = H + 2 * M, D = Math.max(w, h), sD = D / G;
        var a = P.ang || 0.7, ca = Math.cos(a), sa = Math.sin(a), cb = Math.cos(a + 1.1), sb = Math.sin(a + 1.1), fq = (P.freq || 3) * BG3_TAU, tb = P.turb || 5;
        var F1 = [], F2 = [];
        for (j = 0; j <= G; j++) for (i = 0; i <= G; i++) {
            var u = i / G, v = j / G, X = u * 3, Y = v * 3;
            F1.push((u * ca + v * sa) * fq + bg3_fbm2(C, X, Y, sd, 4) * tb);
            if (P.acc) F2.push((u * cb + v * sb) * fq * 1.6 + bg3_fbm2(C, X * 1.4 + 5, Y * 1.4, sd + 9, 3) * tb * 1.3);
        }
        var hd = bg3_hd(b) + 'var e=oc(BT/1),ox=' + jzN(M) + '+' + jzN(M * 0.9) + '*Math.sin(T*' + jzN(0.06 * (P.dir || 1)) + '+' + s + '),oy=' + jzN(M) + '+' + jzN(M * 0.9) + '*Math.cos(T*0.045+' + (s * 2) + ');';
        var pos = hd + '[' + jzN((w - D) / 2) + '-ox,' + jzN((h - D) / 2) + '-oy]';
        // faint cloudiness (big soft noise, fg tone)
        var CL = bg3_solid(b, '#808080', 'bg marble cloud', D / 4, D / 4);
        var fn = jzEffect(CL, 'ADBE Fractal Noise', 'JZ Cloud'); jzEP(fn, 4, 140); jzEP(fn, 5, -20);
        var tn = jzEffect(CL, 'ADBE Tint', 'JZ Cloud Tone'); jzEP(tn, 1, jzHex(sc.bg)); jzEP(tn, 2, jzHex(sc.fg)); jzEP(tn, 3, 100);
        jzXf(CL, 'ADBE Scale').setValue([400, 400]);
        jzSetExpr(jzXf(CL, 'ADBE Position'), hd + '[' + jzN(w / 2) + '-ox,' + jzN(h / 2) + '-oy]');
        jzSetExpr(jzXf(CL, 'ADBE Opacity'), hd + jzN((P.k || 0.2) * 100 * 0.3 * (dk ? 1 : 0.8)) + '*e');
        // the veins: phase = k*PI iso-lines -> smooth paths (texture px), one layer drifting like the browser's plate
        var S = jzShapeLayer(b, 'bg marble', 0, 0), sets = [[F1, sc.fg, 'veins']];
        if (P.acc) sets.push([F2, c2, 'accent veins']);
        for (z = 0; z < sets.length; z++) {        // main veins first (= on top of the accent veins)
            var F = sets[z][0], lo = 1e9, hi = -1e9, g = jzGrp(S, sets[z][2]), lv;
            for (i = 0; i < F.length; i++) { if (F[i] < lo) lo = F[i]; if (F[i] > hi) hi = F[i]; }
            for (lv = Math.ceil(lo / Math.PI); lv * Math.PI <= hi; lv++) {
                var ch = bg3_iso(F, G, lv * Math.PI), c;
                for (c = 0; c < ch.length; c++) {
                    var pp = [], q;
                    for (q = 0; q < ch[c].pts.length; q++) pp.push([ch[c].pts[q][0] * sD, ch[c].pts[q][1] * sD]);
                    bg3_path(g, pp, ch[c].closed, true);
                }
            }
            if (z === 0) {
                bg3_roundJoin(jzAddStroke(g, sets[z][1], Math.max(px, U * 0.009), 100));
                bg3_roundJoin(jzAddStroke(g, sets[z][1], U * 0.03, 10));
            } else bg3_roundJoin(jzAddStroke(g, sets[z][1], Math.max(px, U * 0.005), 50));
        }
        jzSetExpr(jzXf(S, 'ADBE Position'), pos);
        jzSetExpr(jzXf(S, 'ADBE Opacity'), hd + jzN((P.k || 0.2) * 100 * (dk ? 1 : 0.8)) + '*e');
        // vein brightness varies along the veins (browser: x (0.45 + 0.55 * noise)) -> luma matte of soft noise drifting with the texture
        var MN = bg3_solid(b, '#808080', 'bg marble vein matte', D / 4, D / 4);
        var fm = jzEffect(MN, 'ADBE Fractal Noise', 'JZ Vein Level'); jzEP(fm, 4, 70); jzEP(fm, 5, 25);
        jzXf(MN, 'ADBE Scale').setValue([400, 400]);
        jzSetExpr(jzXf(MN, 'ADBE Position'), hd + '[' + jzN(w / 2) + '-ox,' + jzN(h / 2) + '-oy]');
        S.trackMatteType = TrackMatteType.LUMA;
    }
});

/* ---- paperCut \u2014 \u5207\u308A\u7D75: 3-4 layered paper frames with wavy superellipse openings (back = smallest), growing in, gently swaying, drop shadows.
   AE: full-frame solids with a subtracted mask (the opening, baked at the cut), Drop Shadow; grow = layer scale about the centre */
jzReg('bg', 'paperCut', {
    plan: function (rng, st) { return { seed: bg3_seed(rng), L: rng.int(3, 4), lobes: rng.int(5, 9), k: rng.range(0.06, 0.09), acc: rng.chance(0.5), p: rng.range(2.4, 3.2) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, sd = P.seed || 1, U = jzU(b), dk = bg3_dark(sc), NL = P.L || 3, k = P.k || 0.075, pe = 2 / (P.p || 2.8), lobes = P.lobes || 6, N = 144, i, j;
        var Tm = (b.cut.start || 0) + b.comp.duration * 0.4, SW = W * 1.4, SH = H * 1.4, sh = U * 0.009;
        for (i = 0; i < NL; i++) {
            var d = NL > 1 ? (NL - 1 - i) / (NL - 1) : 0, rx = W * (0.41 + 0.17 * (1 - d)), ry = H * (0.38 + 0.17 * (1 - d)), ph = bg3_r(sd, i, 1) * BG3_TAU, sw = Tm * 0.25 * (i % 2 ? 1 : -1), pts = [];
            for (j = 0; j < N; j++) {
                var th = j / N * BG3_TAU, c = Math.cos(th), s = Math.sin(th);
                var wv = 1 + 0.035 * Math.sin((lobes + i) * th + ph + sw) + 0.014 * Math.sin((lobes * 2 + 3) * th - ph * 1.3 - sw * 0.7);
                pts.push([SW / 2 + rx * bg3_sgn(c) * Math.pow(Math.abs(c), pe) * wv, SH / 2 + ry * bg3_sgn(s) * Math.pow(Math.abs(s), pe) * wv]);
            }
            var hue = P.acc && i % 2 ? sc.accent : sc.fg;
            var col = dk ? jzMixHex(sc.bg, hue, k * (0.5 + 0.7 * (i + 1) / NL)) : jzMixHex(sc.bg, i % 2 ? hue : '#FFFFFF', (i % 2 ? k : 0.35) * (0.6 + 0.5 * (i + 1) / NL));
            var L = bg3_solid(b, col, 'bg paperCut ' + (i + 1), SW, SH);
            var mk = L.property('ADBE Mask Parade').addProperty('ADBE Mask Atom'), shp = new Shape(); shp.vertices = pts; shp.closed = true;
            mk.property('ADBE Mask Shape').setValue(shp); mk.maskMode = MaskMode.SUBTRACT;
            var ds = jzEffect(L, 'ADBE Drop Shadow', 'JZ Paper Shadow');
            jzEP(ds, 1, [0, 0, 0]); jzEP(ds, 2, Math.round(255 * (dk ? 0.32 : 0.12))); jzEP(ds, 3, 158.2); jzEP(ds, 4, sh * Math.sqrt(1.16)); jzEP(ds, 5, 0);
            var hi = bg3_hd(b) + 'var ip=oc((BT-' + jzN(i * 0.1) + ')/0.8),gr=1+(1-ip)*0.7;';
            jzSetExpr(jzXf(L, 'ADBE Scale'), hi + '[100*gr,100*gr]');
            jzSetExpr(jzXf(L, 'ADBE Position'), hi + '[' + jzN(W / 2) + '+Math.sin(T*0.35+' + i + ')*' + jzN(U * 0.006 * (i + 1)) + ',' + jzN(H / 2) + '+Math.cos(T*0.3+' + i + ')*' + jzN(U * 0.004 * (i + 1)) + ']');
        }
    }
});

// ================================================================ camera moves (AE port of the cam entries in src/11p_looks.js + src/11p_bgcamB.js)
// cam.apply(cam, P): cam.nul is a null at the comp centre (anchor = position = centre) that carries the lyric content and its
// colour ghosts. Put expressions on its Position / Scale / Rotation relative to `value`; time 0 = cut start, cam.cut.dur = length.
// For blur (focus pulls) add an effect to every layer in cam.content. The browser's { x, y, s, rot } map to px / % / degrees.

/* ---- dutch \u2014 \u30C0\u30C3\u30C1: the frame tilts slowly (ease in-out) and pushes in a little */
jzReg('cam', 'dutch', {
    plan: function (rng, st) { return { dir: rng.pick([1, -1]), a: rng.range(2.5, 4.5) }; },
    apply: function (cam, P) {
        var H0 = jzCamHead(cam);
        jzSetExpr(jzXf(cam.nul, 'ADBE Rotate Z'), H0 + 'value+' + (P.dir || 1) + '*Math.min(5,' + jzN(P.a || 3.5) + '*KM)*ios(time/Math.max(0.3,DUR*0.8))');
        jzSetExpr(jzXf(cam.nul, 'ADBE Scale'), H0 + 'var s=1+0.03*KM*cu;[value[0]*s,value[1]*s]');
    }
});

// ---------------------------------------------------------------- shared helpers of this pack (cm_)
// browser design px (absolute px constants such as blur radii) \u2192 comp px. Design sizes: min side 1080, 1440 for 1:1 / 4:5.
function cm_px(cam) {
    var W = cam.W || 1920, H = cam.H || 1080, r = W / H;
    return Math.min(W, H) / ((Math.abs(r - 1) < 0.02 || Math.abs(r - 0.8) < 0.02) ? 1440 : 1080);
}
// the browser's `P.k || d`, as an expression literal (parenthesised so negatives are safe)
function cm_p(P, k, d) { return (P && P[k]) ? P[k] : d; }
function cm_n(v) { return '(' + jzN(v) + ')'; }
function cm_dur(cam) { return Math.max(0.01, (cam.cut && cam.cut.dur) || 3); }
// expression code defining CAM(t) \u2192 [x, y, s*sx, s*sy, rot, skx, blur] = the browser's get() at cut time t. Inside the entry's
// snippets: t, cu (= t / DUR clamped), KM, DUR, W, H (comp px), sd (seed), vn(x, seed) (-1..1 smooth value noise \u2014 J.noise1),
// rs(k) (-1..1 per 1/24 s step \u2014 J.rs(seed, env.step, k)) and the JZ_FNS easings + ios.
function cm_fn(cam, o) {
    return jzCamHead(cam) + 'var W=' + jzN(cam.W || 1920) + ',H=' + jzN(cam.H || 1080) + ',sd=SD%997;' +
        'function vn(x,s){var i=Math.floor(x),f=x-i,u=f*f*(3-2*f),a=hh(i*1.731+s*7.117)*2-1,b=hh(i*1.731+1.731+s*7.117)*2-1;return a+(b-a)*u;}\n' +
        'function CAM(t){t=Math.max(0,t);var cu=cl(t/DUR),ST=Math.floor(t*24+0.000001);function rs(k){return hh(ST*1.618+k*9.73+sd*3.17)*2-1;}\n' +
        (o.pre || '') + '\nvar S=(' + (o.s || '1') + ');return [' + (o.x || '0') + ',' + (o.y || '0') + ',S*(' + (o.sx || '1') + '),S*(' + (o.sy || '1') + '),' +
        (o.rot || '0') + ',' + (o.skx || '0') + ',' + (o.blur || '0') + '];}\n';
}
// o = { pre: shared code, x, y (px), s, sx, sy, rot (deg), skx (deg), blur (design px) } \u2014 expression snippets in the units of the
// browser's get(). x / y / s / sx / sy / rot go on the camera null; skx = Transform effect Skew and blur = Gaussian Blur on every
// content layer (the browser blurs the whole content layer once, from the main pass, only above 0.4 px).
// The browser evaluates the camera per pass at that pass's lagged time, so fast moves split the colour ghosts. The ghosts here share
// the null, so each ghost (startTime = its lag) gets a Transform effect = N(t)^-1 \u00B7 N(t - lag) (translation / rotation / scale).
function cm_cam(cam, o) {
    var F = cm_fn(cam, o), i, e, C = cam.content || [], lag, LG;
    if (o.x || o.y) jzSetExpr(jzXf(cam.nul, 'ADBE Position'), F + 'var c=CAM(time);[value[0]+c[0],value[1]+c[1]]');
    if (o.s || o.sx || o.sy) jzSetExpr(jzXf(cam.nul, 'ADBE Scale'), F + 'var c=CAM(time);[value[0]*c[2],value[1]*c[3]]');
    if (o.rot) jzSetExpr(jzXf(cam.nul, 'ADBE Rotate Z'), F + 'value+CAM(time)[4]');
    for (i = 0; i < C.length; i++) {
        lag = 0; try { lag = C[i].startTime || 0; } catch (err) { lag = 0; }
        LG = 'var LAG=' + jzN(Math.max(0, lag)) + ';';
        if (o.skx) { e = jzEffect(C[i], 'ADBE Geometry2', 'JZ Cam Skew'); jzEX(e, 6, F + LG + '-CAM(time-LAG)[5]'); }
        if (lag > 0.001) {
            e = jzEffect(C[i], 'ADBE Geometry2', 'JZ Cam Lag'); jzEP(e, 3, 0);
            jzEX(e, 2, F + LG + 'var a=CAM(time),b=CAM(time-LAG),dx=b[0]-a[0],dy=b[1]-a[1],r=-a[4]*Math.PI/180,cs=Math.cos(r),sn=Math.sin(r);' +
                '[value[0]+(dx*cs-dy*sn)/a[2],value[1]+(dx*sn+dy*cs)/a[3]]');
            if (o.s || o.sx || o.sy) {
                jzEX(e, 4, F + LG + 'CAM(time-LAG)[3]/CAM(time)[3]*100');
                jzEX(e, 5, F + LG + 'CAM(time-LAG)[2]/CAM(time)[2]*100');
            }
            if (o.rot) jzEX(e, 8, F + LG + 'CAM(time-LAG)[4]-CAM(time)[4]');
        }
        if (o.blur) { e = jzEffect(C[i], 'ADBE Gaussian Blur 2', 'JZ Cam Blur'); jzEX(e, 1, F + 'var B=CAM(time)[6];B>0.4?B*' + jzN(2 * cm_px(cam)) + ':0'); }
    }
}
// pans / tilt: panP(u) = u*0.6 + inOutSine(u)*0.4
function cm_pan(cam, P, key, sign) {
    var A = cm_n(cm_p(P, 'a', key === 'y' ? 0.025 : 0.022)), o = { pre: 'var pp=cu*0.6+ios(cu)*0.4;', s: '1.02' };
    o[key] = cm_n(sign) + '*(pp-0.5)*2*' + (key === 'y' ? 'H' : 'W') + '*' + A + '*KM';
    cm_cam(cam, o);
}

// ================================================================ src/11p_looks.js
/* ---- pullOut \u2014 \u5F15\u304D: starts pushed in, eases back out to the full frame */
jzReg('cam', 'pullOut', {
    plan: function (rng, st) { return { a: rng.range(0.06, 0.09) }; },
    apply: function (cam, P) { cm_cam(cam, { s: '1+' + cm_n(cm_p(P, 'a', 0.07)) + '*KM*(1-oc(cu))' }); }
});

/* ---- panL / panR \u2014 \u5DE6\u30D1\u30F3 / \u53F3\u30D1\u30F3: slow sideways travel across the cut */
jzReg('cam', 'panL', {
    plan: function (rng, st) { return { a: rng.range(0.018, 0.028) }; },
    apply: function (cam, P) { cm_pan(cam, P, 'x', 1); }
});
jzReg('cam', 'panR', {
    plan: function (rng, st) { return { a: rng.range(0.018, 0.028) }; },
    apply: function (cam, P) { cm_pan(cam, P, 'x', -1); }
});

/* ---- tiltUp \u2014 \u30C6\u30A3\u30EB\u30C8: slow vertical travel (content drifts down) */
jzReg('cam', 'tiltUp', {
    plan: function (rng, st) { return { a: rng.range(0.02, 0.03) }; },
    apply: function (cam, P) { cm_pan(cam, P, 'y', 1); }
});

/* ---- handheld \u2014 \u624B\u6301\u3061: two-octave smooth noise on x / y / rotation */
jzReg('cam', 'handheld', {
    plan: function (rng, st) { return { f: rng.range(0.8, 1.3) }; },
    apply: function (cam, P) {
        cm_cam(cam, { pre: 'var f=' + cm_n(cm_p(P, 'f', 1)) + ';',
            x: '(vn(t*f*1.1,sd)*0.7+vn(t*f*2.9,sd+1)*0.3)*W*0.007*KM',
            y: '(vn(t*f*0.9,sd+2)*0.7+vn(t*f*3.3,sd+3)*0.3)*H*0.009*KM',
            rot: 'vn(t*f*0.8,sd+4)*0.7*KM', s: '1.012' });
    }
});

/* ---- beatPunch \u2014 \u62CD\u3067\u30BA\u30FC\u30E0: a zoom punch that decays on every beat (no beat data in AE: the browser's fixed 0.5 s clock) */
jzReg('cam', 'beatPunch', {
    plan: function (rng, st) { return { a: rng.range(0.03, 0.045) }; },
    apply: function (cam, P) {
        cm_cam(cam, { pre: 'var ks=Math.exp(-(((t%0.5)+0.5)%0.5)*9);',
            s: '1+' + cm_n(cm_p(P, 'a', 0.035)) + '*KM*ks', y: '-H*0.004*KM*ks' });
    }
});

/* ---- whipIn \u2014 \u30DB\u30A4\u30C3\u30D7\u30A4\u30F3: the frame arrives with a fast whip (offset + skew + blur settle in 0.3 s) */
jzReg('cam', 'whipIn', {
    plan: function (rng, st) { return { dir: rng.pick(['L', 'R', 'L', 'R', 'U', 'D']), d: rng.range(0.16, 0.24) }; },
    apply: function (cam, P) {
        var sg = (P.dir === 'L' || P.dir === 'U') ? -1 : 1, hor = (P.dir === 'L' || P.dir === 'R');
        var o = { pre: 'var r=1-oe(cl(t/0.3)),dd=' + cm_n(cm_p(P, 'd', 0.2)) + '*KM*r;', s: '1+0.04*r', blur: '22*KM*r' };
        if (hor) { o.x = cm_n(sg) + '*W*dd'; o.skx = cm_n(sg) + '*9*r*KM'; }
        else o.y = cm_n(sg) + '*H*dd*0.7';
        cm_cam(cam, o);
    }
});

/* ---- crashZoom \u2014 \u30AF\u30E9\u30C3\u30B7\u30E5\u30BA\u30FC\u30E0: late in the cut a sudden zoom slams in (blur flash + small jolt) */
jzReg('cam', 'crashZoom', {
    plan: function (rng, st) { return { at: rng.range(0.6, 0.75), a: rng.range(0.08, 0.11) }; },
    apply: function (cam, P) {
        var dur = cm_dur(cam), tc = dur < 0.8 ? dur * 0.5 : Math.max(dur * cm_p(P, 'at', 0.65), dur - 0.6);
        cm_cam(cam, { pre: 'var tc=' + jzN(tc) + ',dt=t-tc,q=dt<0?0:oe(cl(dt/0.1)),sh=dt<0?0:Math.exp(-dt*6)*q;',
            s: 'dt<0?1-0.008*KM*cl((t-tc+0.25)/0.25):1+Math.min(0.12,' + cm_n(cm_p(P, 'a', 0.1)) + '*KM)*q',
            blur: 'dt<0?0:12*KM*Math.max(0,1-Math.abs(dt-0.05)/0.08)',
            x: 'rs(1)*W*0.004*sh*KM', y: 'rs(2)*H*0.004*sh*KM' });
    }
});

/* ---- bounce \u2014 \u30D0\u30A6\u30F3\u30B9: damped springy zoom / hop at the cut start */
jzReg('cam', 'bounce', {
    plan: function (rng, st) { return { a: rng.range(0.045, 0.065) }; },
    apply: function (cam, P) {
        cm_cam(cam, { pre: 'var dd=Math.exp(-t*5.5);',
            s: '1-' + cm_n(cm_p(P, 'a', 0.07)) + '*KM*dd*Math.cos(t*16)', y: '-H*0.012*KM*dd*Math.sin(t*16)' });
    }
});

/* ---- roll \u2014 \u30ED\u30FC\u30EB: the frame rolls linearly through level across the cut */
jzReg('cam', 'roll', {
    plan: function (rng, st) { return { dir: rng.pick([1, -1]), a: rng.range(2.5, 4) }; },
    apply: function (cam, P) {
        cm_cam(cam, { rot: cm_n(cm_p(P, 'dir', 1)) + '*(cu-0.5)*' + cm_n(cm_p(P, 'a', 3)) + '*KM', s: '1.025' });
    }
});

/* ---- driftDiag \u2014 \u659C\u3081\u30C9\u30EA\u30D5\u30C8: diagonal drift with a slow push */
jzReg('cam', 'driftDiag', {
    plan: function (rng, st) { return { dx: rng.pick([1, -1]), dy: rng.pick([1, -1]) }; },
    apply: function (cam, P) {
        cm_cam(cam, { pre: 'var uu=ios(cu)*0.5+cu*0.5;',
            x: '(uu-0.5)*W*0.035*KM*' + cm_n(cm_p(P, 'dx', 1)), y: '(uu-0.5)*H*0.03*KM*' + cm_n(cm_p(P, 'dy', 1)),
            s: '1.02+0.025*KM*uu' });
    }
});

/* ---- shakeHard \u2014 \u5F37\u3044\u63FA\u308C: a hard random shake (24 steps / s) that dies out */
jzReg('cam', 'shakeHard', {
    plan: function (rng, st) { return {}; },
    apply: function (cam, P) {
        cm_cam(cam, { pre: 'var amp=Math.exp(-t*4.5)*KM;if(amp<0.01)amp=0;',
            x: 'rs(1)*W*0.022*amp', y: 'rs(2)*H*0.02*amp', rot: 'rs(3)*1.6*amp', s: '1+0.03*amp', blur: '2.5*amp' });
    }
});

/* ---- dollyIn \u2014 \u30C9\u30EA\u30FC: accelerating push-in (in-cubic) with a slight rise */
jzReg('cam', 'dollyIn', {
    plan: function (rng, st) { return { a: rng.range(0.08, 0.11) }; },
    apply: function (cam, P) {
        cm_cam(cam, { pre: 'var q=ic(cu);', s: '1+Math.min(0.14,' + cm_n(cm_p(P, 'a', 0.1)) + '*KM)*q', y: '-H*0.008*KM*q' });
    }
});

/* ---- stepZoom \u2014 \u6BB5\u968E\u30BA\u30FC\u30E0: 2\u20133 snap zooms (no beats in AE: evenly spaced, the browser's no-beat fallback) */
jzReg('cam', 'stepZoom', {
    plan: function (rng, st) { return { n: rng.int(2, 3), a: rng.range(0.035, 0.045) }; },
    apply: function (cam, P) {
        var dur = cm_dur(cam), n = Math.max(1, Math.round(cm_p(P, 'n', 2))), ts = [], i;
        for (i = 0; i < n; i++) ts.push(jzN(dur * (i + 1) / (n + 1)));
        cm_cam(cam, { pre: 'var ts=[' + ts.join(',') + '],aa=' + cm_n(cm_p(P, 'a', 0.04)) + '*KM,sz=1,bz=0;' +
            'for(var i=0;i<ts.length;i++){var d=t-ts[i];if(d<0)continue;sz+=aa*oe(cl(d/0.07));bz+=5*KM*(1-cl(d/0.06));}',
            s: 'Math.min(1.15,sz)', blur: 'bz' });
    }
});

// ================================================================ src/11p_bgcamB.js
/* ---- orbitDrift \u2014 \u5468\u56DE: the frame circles slowly (x / y on a circle, rotation follows) */
jzReg('cam', 'orbitDrift', {
    plan: function (rng, st) { return { dir: rng.pick([1, -1]), a0: rng.range(0, 6.28), sp: rng.range(1.1, 1.6) }; },
    apply: function (cam, P) {
        var d = cm_n(cm_p(P, 'dir', 1));
        cm_cam(cam, { pre: 'var th=' + cm_n(cm_p(P, 'a0', 0)) + '+' + d + '*t*' + cm_n(cm_p(P, 'sp', 1.3)) + ',r=oc(cl(t/0.7));',
            x: 'Math.cos(th)*W*0.016*KM*r', y: 'Math.sin(th)*H*0.02*KM*r', rot: 'Math.sin(th)*0.9*KM*r*' + d, s: '1.02' });
    }
});

/* ---- barrelRoll \u2014 \u30D0\u30EC\u30EB\u30ED\u30FC\u30EB: opens rolled ~90\u00B0, rolls level with a small overshoot (dip in scale + blur) */
jzReg('cam', 'barrelRoll', {
    plan: function (rng, st) { return { dir: rng.pick([1, -1]), a: rng.range(70, 110), d: rng.range(0.42, 0.55) }; },
    apply: function (cam, P) {
        cm_cam(cam, { pre: 'var K1=Math.min(1,KM),q=cl(t/' + cm_n(cm_p(P, 'd', 0.5)) + '),r=q>=1?0:1-ob(q,1.3);',
            rot: cm_n(cm_p(P, 'dir', 1)) + '*' + cm_n(cm_p(P, 'a', 90)) + '*K1*r',
            s: 'q>=1?1:1-0.12*K1*Math.sin(Math.PI*Math.min(1,q*1.25))', blur: 'q>=1?0:9*K1*cl(1-q*2.2)' });
    }
});

/* ---- pendulumSway \u2014 \u632F\u308A\u5B50: the frame hangs from a pivot above the screen (rotation + coupled sideways arc) */
jzReg('cam', 'pendulumSway', {
    plan: function (rng, st) { return { a: rng.range(1.8, 2.6), per: rng.range(2, 3), side: rng.pick([1, -1]) }; },
    apply: function (cam, P) {
        cm_cam(cam, { pre: 'var damp=0.75+0.25*Math.exp(-t*0.6),LL=W*0.62,phi=' + cm_n(cm_p(P, 'a', 2.2)) + '*KM*' + cm_n(cm_p(P, 'side', 1)) +
            '*Math.cos(t/' + cm_n(cm_p(P, 'per', 2.4)) + '*2*Math.PI)*damp*Math.PI/180;',
            x: '-LL*Math.sin(phi)', y: '-LL*(1-Math.cos(phi))', rot: 'phi*180/Math.PI', s: '1.02' });
    }
});

/* ---- focusIn \u2014 \u30D4\u30F3\u30C8\u5408\u308F\u305B: opens out of focus and pulls sharp (lens breathing) */
jzReg('cam', 'focusIn', {
    plan: function (rng, st) { return { d: rng.range(0.5, 0.8), b: rng.range(10, 15) }; },
    apply: function (cam, P) {
        cm_cam(cam, { pre: 'var q=oc(cl(t/' + cm_n(cm_p(P, 'd', 0.65)) + '));',
            blur: '(1-q)*' + cm_n(cm_p(P, 'b', 12)) + '*Math.min(1,KM)', s: '1+0.03*(1-q)+0.012*KM*cu' });
    }
});

/* ---- rackFocus \u2014 \u30D4\u30F3\u307C\u3051: sharp, then drifts out of focus towards the end of the cut */
jzReg('cam', 'rackFocus', {
    plan: function (rng, st) { return { b: rng.range(5, 8), at: rng.range(0.6, 0.7) }; },
    apply: function (cam, P) {
        var dur = cm_dur(cam), s0 = Math.max(dur * cm_p(P, 'at', 0.65), dur - 0.9), dd = Math.max(0.2, dur - s0);
        cm_cam(cam, { pre: 'var q=ios(cl((t-' + cm_n(s0) + ')/' + cm_n(dd) + '));',
            blur: 'q*' + cm_n(cm_p(P, 'b', 6.5)) + '*Math.min(1,KM)', s: '1.01-0.02*q*KM', y: 'H*0.004*q' });
    }
});

/* ---- earthquake \u2014 \u5730\u9707: constant rumble + a jolt every period (no beats in AE: the browser's fixed-period fallback) */
jzReg('cam', 'earthquake', {
    plan: function (rng, st) { return { per: rng.range(0.55, 0.8), a: rng.range(0.85, 1.1) }; },
    apply: function (cam, P) {
        var per = cm_n(cm_p(P, 'per', 0.65));
        cm_cam(cam, { pre: 'var K2=KM*' + cm_n(cm_p(P, 'a', 1)) + ',hit=Math.exp(-(((t%' + per + ')+' + per + ')%' + per + ')*7),amp=K2*(0.14+hit);',
            x: 'rs(11)*W*0.005*amp', y: 'rs(12)*H*0.014*amp', rot: 'rs(13)*0.45*amp', s: '1.02+0.012*hit*K2', blur: '1.5*hit*K2' });
    }
});

/* ---- floatNoise \u2014 \u6D6E\u904A: slow floating drift (noise + a gentle sine bob, breathing scale) */
jzReg('cam', 'floatNoise', {
    plan: function (rng, st) { return { f: rng.range(0.8, 1.2) }; },
    apply: function (cam, P) {
        cm_cam(cam, { pre: 'var tf=t*' + cm_n(cm_p(P, 'f', 1)) + ',s7=sd+7;',
            x: 'vn(tf*0.6,s7)*W*0.02*KM', y: '(vn(tf*0.5+5,s7+1)*0.7+0.3*Math.sin(tf*1.3))*H*0.024*KM',
            rot: 'vn(tf*0.3+9,s7+2)*1.3*KM', s: '1.025+0.012*Math.sin(tf*0.8)' });
    }
});

/* ---- vertigo \u2014 \u3081\u307E\u3044: creeping push-in whose perspective wavers (stretch / squash, skew and roll grow with the zoom) */
jzReg('cam', 'vertigo', {
    plan: function (rng, st) { return { dir: rng.pick([1, -1]), a: rng.range(0.05, 0.07) }; },
    apply: function (cam, P) {
        var d = cm_n(cm_p(P, 'dir', 1));
        cm_cam(cam, { pre: 'var z=ios(cu),ww=Math.sin(t*2.3);',
            s: '1+' + cm_n(cm_p(P, 'a', 0.06)) + '*KM*z', sx: '1+0.03*KM*z*ww', sy: '1-0.026*KM*z*ww',
            skx: '2.2*KM*z*Math.sin(t*1.7)*' + d, rot: '0.8*KM*z*Math.sin(t*1.1+1)*' + d });
    }
});

/* ---- tiltDown \u2014 \u30C6\u30A3\u30EB\u30C8\u30C0\u30A6\u30F3: the line rises into frame from below and settles, easing out of a slight zoom */
jzReg('cam', 'tiltDown', {
    plan: function (rng, st) { return { a: rng.range(0.024, 0.032) }; },
    apply: function (cam, P) {
        cm_cam(cam, { pre: 'var q=oc(cu),A=H*' + cm_n(cm_p(P, 'a', 0.028)) + '*KM;', y: 'A*(1.2-1.6*q)', s: '1.035-0.02*q' });
    }
});

/* ---- spiralIn \u2014 \u6E26\u30BA\u30FC\u30E0: spirals in from a small, rotated, off-centre frame */
jzReg('cam', 'spiralIn', {
    plan: function (rng, st) { return { dir: rng.pick([1, -1]), a0: rng.range(0, 6.28), d: rng.range(0.9, 1.3) }; },
    apply: function (cam, P) {
        var d = cm_p(P, 'dir', 1);
        cm_cam(cam, { pre: 'var e=oc(cl(t/' + cm_n(cm_p(P, 'd', 1.1)) + ')),r=1-e,th=' + cm_n(cm_p(P, 'a0', 0)) + '+' + cm_n(d) + '*e*2*Math.PI*0.8;',
            x: 'Math.cos(th)*W*0.03*KM*r', y: 'Math.sin(th)*H*0.035*KM*r', rot: cm_n(-d) + '*7*KM*r', s: '1-0.08*KM*r+0.015*KM*cu' });
    }
});

/* ---- snapPan \u2014 \u30B9\u30CA\u30C3\u30D7\u30D1\u30F3: holds one framing, whips to the opposite one mid-cut (skew + blur on the whip), holds again */
jzReg('cam', 'snapPan', {
    plan: function (rng, st) { return { dir: rng.pick([1, -1]), a: rng.range(0.024, 0.032), at: rng.range(0.45, 0.6) }; },
    apply: function (cam, P) {
        var dur = cm_dur(cam), d = cm_n(cm_p(P, 'dir', 1)), A = 'W*' + cm_n(cm_p(P, 'a', 0.028)) + '*KM*' + d;
        if (dur < 1.1) { cm_cam(cam, { pre: 'var A=' + A + ';', x: 'A*0.5*(1-2*cu)', s: '1.02' }); return; }
        cm_cam(cam, { pre: 'function ioc(x){x=cl(x);return x<0.5?4*x*x*x:1-Math.pow(-2*x+2,3)/2;}' +
            'var A=' + A + ',drift=W*0.005*KM*' + d + '*(cu-0.5),dt=t-' + cm_n(dur * cm_p(P, 'at', 0.5)) + ',q=ioc(cl(dt/0.16)),bl=(dt>0&&dt<0.16)?Math.sin(Math.PI*dt/0.16):0;',
            x: 'A*(1-2*q)-drift', s: '1.02+0.015*bl', skx: '-' + d + '*5*KM*bl', blur: '14*KM*bl' });
    }
});

/* ---- jelly \u2014 \u3077\u308B\u3093: squash-and-stretch wobble on landing (no beat re-wobbles in AE) */
jzReg('cam', 'jelly', {
    plan: function (rng, st) { return { a: rng.range(0.045, 0.065), f: rng.range(18, 24) }; },
    apply: function (cam, P) {
        cm_cam(cam, { pre: 'var ww=Math.exp(-t*5)*Math.cos(t*' + cm_n(cm_p(P, 'f', 21)) + '),A=' + cm_n(cm_p(P, 'a', 0.055)) + '*KM;',
            sx: '1+A*ww', sy: '1-A*ww*0.9', y: 'H*0.008*KM*ww', s: '1.01' });
    }
});

// ================================================================ decor pack 1 (AE port of 21 HUD / geometry decor entries in src/11p_decor.js)
// decor.build(ctx, bb, d): layers in the content comp (time 0 = cut start). Every entry follows the cut's exit through K (jzTH).
// The browser draws all of these on the main pass only (ghost off) -> jzNoGhost on every layer, except the pieces the browser
// ghosts (glitchRects' fg / accent slivers and triangleSpin's orbiting solid).

// ---------------------------------------------------------------- shared helpers (prefix dc1_)
// the browser's deterministic hash J.h / J.r / J.rs / J.rr (so placements match the web frame)
function dc1_h(a, b, c, d, e) {
    var h = 0x9e3779b9 ^ (a | 0);
    h = jzImul(h ^ (h >>> 16), 0x85ebca6b);
    h = (h + jzImul((b | 0) + 0x632be5ab, 0xc2b2ae35)) | 0;
    h = jzImul(h ^ (h >>> 13), 0xc2b2ae35);
    h = (h + jzImul((c | 0) + 0x5bd1e995, 0x27d4eb2f)) | 0;
    h = jzImul(h ^ (h >>> 15), 0x165667b1);
    h = (h + jzImul((d | 0) + 0x1b873593, 0x85ebca6b)) | 0;
    h = jzImul(h ^ (h >>> 16), 0x27d4eb2f);
    h = (h + jzImul((e | 0) + 0x68e31da4, 0x9e3779b1)) | 0;
    h ^= h >>> 15; h = jzImul(h, 0x2c1b3c6d); h ^= h >>> 12; h = jzImul(h, 0x297a2d39); h ^= h >>> 15;
    return h >>> 0;
}
function dc1_r(a, b, c, d, e) { return dc1_h(a, b, c, d, e) / 4294967296; }
function dc1_rs(a, b, c, d, e) { return dc1_r(a, b, c, d, e) * 2 - 1; }
function dc1_rr(lo, hi, a, b, c, d, e) { return lo + (hi - lo) * dc1_r(a, b, c, d, e); }
// sizes: browser U(env) = min(W, H) / 1080, MG = 5% safe margin, FS = small label size
function dc1_u(ctx) { return jzU(ctx) / 1080; }
function dc1_mg(ctx) { return Math.round(jzU(ctx) * 0.05); }
function dc1_fs(ctx) { return Math.max(12, 16 * dc1_u(ctx)); }
function dc1_len(x, y) { return Math.sqrt(x * x + y * y); }
function dc1_p(n, k) { return jzPad(Math.max(0, Math.floor(n)), k || 2); }          // browser pad2
// browser getBB: lyric box clamped to the frame (+10%), centred box when missing
function dc1_bb(ctx, bb) {
    var W = ctx.W, H = ctx.H;
    if (!bb || !(bb.x1 > bb.x0) || !(bb.y1 > bb.y0)) bb = { x0: W * 0.35, x1: W * 0.65, y0: H * 0.4, y1: H * 0.6 };
    var b = { x0: Math.max(bb.x0, -W * 0.1), x1: Math.min(bb.x1, W * 1.1), y0: Math.max(bb.y0, -H * 0.1), y1: Math.min(bb.y1, H * 1.1) };
    if (b.x1 <= b.x0) { b.x0 = bb.x0; b.x1 = bb.x1; }
    if (b.y1 <= b.y0) { b.y0 = bb.y0; b.y1 = bb.y1; }
    b.cx = (b.x0 + b.x1) / 2; b.cy = (b.y0 + b.y1) / 2; b.w = b.x1 - b.x0; b.h = b.y1 - b.y0;
    return b;
}
function dc1_hit(x0, y0, x1, y1, bb, pad) { pad = pad || 0; return !(x1 < bb.x0 - pad || x0 > bb.x1 + pad || y1 < bb.y0 - pad || y0 > bb.y1 + pad); }
// browser cornerSpot: a screen corner (inside the margin) clear of the lyric -> { x, y, cx, cy, ok, sx, sy }
function dc1_corner(ctx, bb, w, h, d, mk) {
    var W = ctx.W, H = ctx.H, m = dc1_mg(ctx) * (mk || 1), sx0 = d.right ? 1 : -1, sy0 = d.low ? 1 : -1;
    var order = [[sx0, sy0], [-sx0, sy0], [sx0, -sy0], [-sx0, -sy0]], best = null, bestA = 1e18, i;
    for (i = 0; i < 4; i++) {
        var sx = order[i][0], sy = order[i][1], X = sx > 0 ? W - m - w : m, Y = sy > 0 ? H - m - h : m;
        if (!dc1_hit(X, Y, X + w, Y + h, bb, 8)) return { x: X, y: Y, cx: X + w / 2, cy: Y + h / 2, ok: true, sx: sx, sy: sy };
        var ov = Math.max(0, Math.min(X + w, bb.x1) - Math.max(X, bb.x0)) * Math.max(0, Math.min(Y + h, bb.y1) - Math.max(Y, bb.y0));
        if (ov < bestA) { bestA = ov; best = { x: X, y: Y, cx: X + w / 2, cy: Y + h / 2, ok: false, sx: sx, sy: sy }; }
    }
    return best;
}
// browser nearBB: a w x h box next to the lyric (outside it, inside the safe margin), else a corner
function dc1_near(ctx, bb, w, h, d, gap) {
    var W = ctx.W, H = ctx.H, m = dc1_mg(ctx) * 0.8, sx0 = d.right ? 1 : -1, sy0 = d.low ? 1 : -1, i;
    var order = d.corner ? [[sx0, sy0], [-sx0, sy0], [sx0, -sy0], [-sx0, -sy0]] : [[sx0, sy0], [sx0, -sy0], [-sx0, sy0], [-sx0, -sy0]], tries = [];
    for (i = 0; i < 4; i++) {
        var sx = order[i][0], sy = order[i][1];
        var ax = sx > 0 ? bb.x1 - w : bb.x0, ox = sx > 0 ? bb.x1 + gap : bb.x0 - gap - w;
        var ay = sy > 0 ? bb.y1 + gap : bb.y0 - gap - h, iy = sy > 0 ? bb.y1 - h : bb.y0;
        if ((d.v | 0) % 2) tries.push([ox, iy, sx, sy], [ax, ay, sx, sy], [ox, ay, sx, sy]);
        else tries.push([ax, ay, sx, sy], [ox, iy, sx, sy], [ox, ay, sx, sy]);
    }
    for (i = 0; i < tries.length; i++) {
        var X = jzClamp(tries[i][0], m, Math.max(m, W - m - w)), Y = jzClamp(tries[i][1], m, Math.max(m, H - m - h));
        if (!dc1_hit(X, Y, X + w, Y + h, bb, gap * 0.4)) return { x: X, y: Y, cx: X + w / 2, cy: Y + h / 2, ok: true, sx: tries[i][2], sy: tries[i][3] };
    }
    return dc1_corner(ctx, bb, w, h, d);
}
// expression headers: jzTH (DUR IN OS OD SD, easings, PO, K) + ioc (inOutCubic) + T = song time, ST = 12 fps step
var DC1_X = 'function ioc(x){x=cl(x);return x<0.5?4*x*x*x:1-Math.pow(-2*x+2,3)/2;}';
function dc1_hd(ctx) { return jzTH(ctx) + DC1_X + 'var T=time+' + jzN(ctx.cut.start || 0) + ',ST=Math.floor(T*12+1e-6);\n'; }
// light header for many small per-group expressions (PO, ST, SD, cl, ic, hh)
function dc1_lh(ctx) {
    var c = ctx.cut, hard = c.outDur != null && c.outDur <= 0, od = hard ? 0.05 : Math.max(0.12, c.outDur || 0.15);
    return 'var OS=' + jzN(hard ? c.dur : c.dur - od) + ',OD=' + jzN(od) + ',SD=' + (c.seed % 99991) + ',T=time+' + jzN(c.start || 0) + ';' +
        'function cl(x){return Math.max(0,Math.min(1,x));}function ic(x){x=cl(x);return x*x*x;}function hh(n){var x=Math.sin(n*127.1+311.7)*43758.5453;return x-Math.floor(x);}' +
        'var PO=cl((time-OS)/OD),ST=Math.floor(T*12+1e-6);';
}
// layer opacity = a * K * factor(expression)
function dc1_op(ctx, L, f, a) {
    if (a != null) jzXf(L, 'ADBE Opacity').setValue(Math.max(0, Math.min(100, a * 100)));
    jzSetExpr(jzXf(L, 'ADBE Opacity'), dc1_hd(ctx) + 'value*K*(' + (f || '1') + ')');
    return L;
}
// browser label(): small mono text, never ghosted. o: size, align, color, font, track, rot, a (alpha), f (fade factor expr), expr (sourceText expr)
function dc1_label(ctx, text, x, y, o) {
    o = o || {};
    var L = jzText(ctx, String(text), { font: o.font || jzMonoF(ctx), size: o.size || dc1_fs(ctx), color: o.color || ctx.sc.sub, x: x, y: y, align: o.align || 'left', track: o.track != null ? o.track : 0.08, name: o.name });
    if (o.rot) jzXf(L, 'ADBE Rotate Z').setValue(o.rot);
    if (o.expr) jzSetExpr(L.property('ADBE Text Properties').property('ADBE Text Document'), dc1_hd(ctx) + o.expr);
    jzNoGhost(L);
    dc1_op(ctx, L, o.f, o.a);
    return L;
}
// several short labels in ONE text layer: line k holds strs[k]; per-glyph offsets move it to pts[k] (x by align, y = centre)
function dc1_lines(ctx, strs, pts, o) {
    var fs = o.size, LD = fs * 1.2, k, j, offs = [];
    var L = jzText(ctx, strs.join('\r'), { font: o.font || jzMonoF(ctx), size: fs, color: o.color || ctx.sc.sub, x: 0, y: 0, align: o.align || 'left', track: o.track != null ? o.track : 0.08, leading: LD, name: o.name });
    jzXf(L, 'ADBE Anchor Point').setValue([0, -0.36 * fs]);
    jzXf(L, 'ADBE Position').setValue([pts[0][0], pts[0][1]]);
    for (k = 0; k < strs.length; k++) { var gl = jzGlyphs(strs[k]); for (j = 0; j < gl.length; j++) offs.push([pts[k][0] - pts[0][0], pts[k][1] - pts[0][1] - k * LD]); }
    if (strs.length > 1) jzCharOffsets(L, offs, 'JZ Place');
    jzNoGhost(L);
    dc1_op(ctx, L, o.f, o.a);
    return L;
}
function dc1_layer(ctx, name, x, y) { return jzNoGhost(jzShapeLayer(ctx, name, x || 0, y || 0)); }
function dc1_sub(g, name) { var q = jzVecs(g).addProperty('ADBE Vector Group'); if (name) q.name = name; return q; }
function dc1_seg(g, x0, y0, x1, y1) { return jzAddPath(g, [[x0, y0], [x1, y1]], false); }
function dc1_gset(g, mn, v) { jzGX(g).property(mn).setValue(v); }
function dc1_gexp(ctx, g, mn, ex) { jzSetExpr(jzGX(g).property(mn), dc1_hd(ctx) + ex); }
function dc1_sexp(ctx, prop, ex) { jzSetExpr(prop, dc1_hd(ctx) + ex); }
function dc1_trim(ctx, g, endEx, startEx) { return jzAddTrimPaths(g, endEx ? dc1_hd(ctx) + endEx : null, startEx ? dc1_hd(ctx) + startEx : null); }
function dc1_cap(st) { try { st.property('ADBE Vector Stroke Line Cap').setValue(2); st.property('ADBE Vector Stroke Line Join').setValue(2); } catch (e) {} return st; }
function dc1_col(hex) { var c = jzHex(hex); return '[' + jzN(c[0]) + ',' + jzN(c[1]) + ',' + jzN(c[2]) + ',1]'; }
// repeater at the end of group g: n copies, each offset by (px, py) and rot degrees around the group origin
function dc1_rep(g, n, px, py, rot) {
    var rp = jzVecs(g).addProperty('ADBE Vector Filter - Repeater'), idx = rp.propertyIndex;
    rp = jzVecs(g).property(idx);
    rp.property('ADBE Vector Repeater Copies').setValue(n);
    var rt = rp.property('ADBE Vector Repeater Transform');
    rt.property('ADBE Vector Repeater Position').setValue([px || 0, py || 0]);
    if (rot) rt.property('ADBE Vector Repeater Rotation').setValue(rot);
    return rp;
}
// open bezier arc (degrees, clockwise on screen, 0 = right) - starts where the browser's arcs start
function dc1_arc(g, cx, cy, r, a0, a1) {
    var n = Math.max(1, Math.ceil(Math.abs(a1 - a0) / 90)), da = (a1 - a0) / n * Math.PI / 180, k = 4 / 3 * Math.tan(da / 4) * r, v = [], it = [], ot = [];
    for (var i = 0; i <= n; i++) {
        var a = a0 * Math.PI / 180 + da * i, c = Math.cos(a), s = Math.sin(a);
        v.push([cx + c * r, cy + s * r]); it.push([s * k, -c * k]); ot.push([-s * k, c * k]);
    }
    var sh = new Shape(); sh.vertices = v; sh.inTangents = it; sh.outTangents = ot; sh.closed = false;
    var p = jzVecs(g).addProperty('ADBE Vector Shape - Group'); p.property('ADBE Vector Shape').setValue(sh);
    return p;
}
// dashed stroke (AE starts with an empty Dashes group; the preview model has fixed children).
// The dash is set before the gap is added: adding the gap invalidates a held reference to the dash in AE.
function dc1_dashItem(D, mn, v) {
    var p = null;
    try { p = D.addProperty(mn); } catch (e) { p = null; }
    if (!p) { try { p = D.property(mn); } catch (e1) { p = null; } }
    if (p) p.setValue(v);
}
function dc1_dash(st, dl, gp) {
    var D = st.property('ADBE Vector Stroke Dashes');
    dc1_dashItem(D, 'ADBE Vector Stroke Dash 1', dl);
    dc1_dashItem(D, 'ADBE Vector Stroke Gap 1', gp);
}
// rectangular layer mask; mode MaskMode.*, feather px or [fx, fy], expEx = Mask Expansion expression
function dc1_mask(L, x0, y0, x1, y1, mode, feather, expEx) {
    var m = L.property('ADBE Mask Parade').addProperty('ADBE Mask Atom'), sh = new Shape();
    sh.vertices = [[x1, y0], [x1, y1], [x0, y1], [x0, y0]]; sh.closed = true;
    m.property('ADBE Mask Shape').setValue(sh);
    if (feather) m.property('ADBE Mask Feather').setValue(feather instanceof Array ? feather : [feather, feather]);
    if (mode) m.maskMode = mode;
    if (expEx) jzSetExpr(m.property('ADBE Mask Offset'), expEx);
    return m;
}
// triangle polyline (closed by repeating the first point), apex up, radius R, around 0,0
function dc1_tri(R) { var p = []; for (var k = 0; k <= 3; k++) { var an = (-90 + k * 120) * Math.PI / 180; p.push([Math.cos(an) * R, Math.sin(an) * R]); } return p; }

// ================================================================ HUD / technical

/* ---- crosshair \u7167\u6E96\u7DDA: hairlines through the lyric centre, broken around it, graduated near the gap */
jzReg('decor', 'crosshair', { back: false, build: function (ctx, bb0, d) {
    var bb = dc1_bb(ctx, bb0), W = ctx.W, H = ctx.H, sc = ctx.sc, u = dc1_u(ctx), m = dc1_mg(ctx), i, q;
    var cx = bb.cx, cy = bb.cy, gx = 26 * u + bb.w * 0.02, gy = 22 * u + bb.h * 0.1, v = (d.v | 0) % 3, lw = Math.max(1, 1.3 * u);
    var arms = [[[cx, bb.y0 - gy], [cx, m * 0.5]], [[cx, bb.y1 + gy], [cx, H - m * 0.5]], [[bb.x0 - gx, cy], [m * 0.5, cy]], [[bb.x1 + gx, cy], [W - m * 0.5, cy]]];
    var S = dc1_layer(ctx, 'crosshair', 0, 0);
    for (i = 0; i < 4; i++) {
        var a = arms[i][0], b = arms[i][1], len = dc1_len(b[0] - a[0], b[1] - a[1]);
        if (len < 40 * u) continue;
        var EE = 'oe((time-' + jzN(i * 0.06) + ')/0.6)', g = jzGrp(S, 'arm ' + (i + 1));
        dc1_gset(g, 'ADBE Vector Position', a);
        dc1_gset(g, 'ADBE Vector Rotation', Math.atan2(b[1] - a[1], b[0] - a[0]) * 180 / Math.PI);
        // accent cap at the inner end
        var gc = dc1_sub(g, 'cap'); dc1_seg(gc, 0, -9 * u, 0, 9 * u); jzAddStroke(gc, (i < 2 || d.accent) ? sc.accent : sc.fg, 2.4 * u);
        dc1_gexp(ctx, gc, 'ADBE Vector Scale', 'var e=' + EE + ';[100,100*e]');
        dc1_gexp(ctx, gc, 'ADBE Vector Group Opacity', '(PO<0.02?100:0)*' + EE);
        if (v === 1) {
            var ge = dc1_sub(g, 'end'); jzAddEllipse(ge, 6 * u, 6 * u); jzAddStroke(ge, sc.fg, lw);
            dc1_gset(ge, 'ADBE Vector Position', [len, 0]); dc1_gexp(ctx, ge, 'ADBE Vector Scale', 'var e=' + EE + ';[100*e,100*e]');
        }
        // graduation near the gap: repeaters whose copies follow the drawn arm (and leave with the exit)
        if (v !== 2 || i < 2) {
            var st = 10 * u, nt = Math.min(20, Math.floor(len * 0.5 / st));
            for (q = 0; q < 2; q++) {
                var step = q ? st * 5 : st, nn = q ? Math.floor(nt / 5) : nt, tl = q ? 8 * u : 3.5 * u;
                if (nn < 1) continue;
                var gt = dc1_sub(g, q ? 'major ticks' : 'ticks');
                dc1_seg(gt, step, -tl, step, tl); jzAddStroke(gt, sc.fg, lw, 80);
                var rp = dc1_rep(gt, nn, step, 0, 0), SX = 'var L=' + jzN(len) + ',S=' + jzN(step) + ',o=Math.floor(ic(PO)*L/S+1e-6);';
                dc1_sexp(ctx, rp.property('ADBE Vector Repeater Copies'), SX + 'Math.max(0,Math.min(' + nn + ',Math.floor(' + EE + '*L/S+1e-6))-o)');
                dc1_sexp(ctx, rp.property('ADBE Vector Repeater Offset'), SX + 'o');
            }
        }
        var gl = dc1_sub(g, 'line'); dc1_seg(gl, 0, 0, len, 0); jzAddStroke(gl, sc.sub, lw, 75);
        dc1_trim(ctx, gl, '100*' + EE, '100*Math.min(ic(PO),' + EE + ')');
    }
    dc1_op(ctx, S, '1');
    if (v !== 2) {
        var fs = dc1_fs(ctx) * 0.85, f = 'oe((time-0.35)/0.3)';
        dc1_label(ctx, 'Y ' + dc1_p(bb.y0, 4), cx + 12 * u, bb.y0 - gy - fs * 0.9, { size: fs, f: f });
        dc1_label(ctx, 'Y ' + dc1_p(bb.y1, 4), cx + 12 * u, bb.y1 + gy + fs * 0.9, { size: fs, f: f });
        dc1_label(ctx, 'X ' + dc1_p(cx, 4), cx - 12 * u, H - m * 0.5 - fs * 0.6, { size: fs, f: f, align: 'right', color: sc.fg });
    }
} });

/* ---- cropMarks \u30C8\u30F3\u30DC: printer's crop / registration marks around the lyric */
jzReg('decor', 'cropMarks', { back: false, build: function (ctx, bb0, d) {
    var bb = dc1_bb(ctx, bb0), sc = ctx.sc, u = dc1_u(ctx), mn = Math.min(bb.w, bb.h), k, j;
    var pad = 14 * u + mn * 0.08 + (d.r || 0) * 10 * u, cm = 12 * u;
    var X0 = Math.max(cm, bb.x0 - pad), X1 = Math.min(ctx.W - cm, bb.x1 + pad), Y0 = Math.max(cm, bb.y0 - pad), Y1 = Math.min(ctx.H - cm, bb.y1 + pad);
    var g = 8 * u, Lm = 26 * u + mn * 0.05, b = 9 * u, lw = Math.max(1, u), cs = [[-1, -1], [1, -1], [1, 1], [-1, 1]];
    var S = dc1_layer(ctx, 'crop marks', 0, 0);
    // centre marks (\u5341) + register circles, scaled in from their foot
    var ccx = (X0 + X1) / 2, ccy = (Y0 + Y1) / 2, cl = 20 * u, rr = 6 * u, rc = d.accent ? sc.accent : sc.fg;
    var marks = [[ccx, Y0 - g, 0, -1], [ccx, Y1 + g, 0, 1]];
    if (d.big || bb.h > bb.w) { marks.push([X0 - g, ccy, -1, 0]); marks.push([X1 + g, ccy, 1, 0]); }
    for (k = 0; k < marks.length; k++) {
        var x = marks[k][0], y = marks[k][1], dx = marks[k][2], dy = marks[k][3], mx = x + dx * cl * 0.55, my = y + dy * cl * 0.55;
        var gm = jzGrp(S, 'register ' + (k + 1));
        dc1_seg(gm, x, y, x + dx * cl, y + dy * cl);
        dc1_seg(gm, mx - dy * cl * 0.45, my - dx * cl * 0.45, mx + dy * cl * 0.45, my + dx * cl * 0.45);
        if (d.big) jzAddEllipse(gm, rr * 2, rr * 2, mx, my);
        jzAddStroke(gm, rc, lw, 85);
        dc1_gset(gm, 'ADBE Vector Anchor', [x, y]); dc1_gset(gm, 'ADBE Vector Position', [x, y]);
        dc1_gexp(ctx, gm, 'ADBE Vector Scale', 'var e=oe((time-0.15)/0.45);[100*e,100*e]');
    }
    // corner marks: an outer and an inner (fainter) L per corner, drawn outwards
    for (k = 0; k < 4; k++) {
        var sx = cs[k][0], sy = cs[k][1], X = sx < 0 ? X0 : X1, Y = sy < 0 ? Y0 : Y1, gk = jzGrp(S, 'corner ' + (k + 1));
        for (j = 0; j < 2; j++) {
            var o = j * b, gq = dc1_sub(gk, j ? 'inner' : 'outer');
            dc1_seg(gq, X + sx * g, Y + sy * o, X + sx * (g + Lm), Y + sy * o);
            dc1_seg(gq, X + sx * o, Y + sy * g, X + sx * o, Y + sy * (g + Lm));
            jzAddStroke(gq, sc.fg, lw, j ? 55 : 90);
            dc1_trim(ctx, gq, '100*oe((time-' + jzN(k * 0.05) + ')/0.45)');
        }
    }
    dc1_op(ctx, S, '1');
} });

/* ---- reticle \u30ED\u30C3\u30AF\u30AA\u30F3: a targeting reticle that shrinks, spins and locks beside the lyric */
jzReg('decor', 'reticle', { back: false, build: function (ctx, bb0, d) {
    var bb = dc1_bb(ctx, bb0), sc = ctx.sc, u = dc1_u(ctx), fs0 = dc1_fs(ctx), k, st;
    var R = jzClamp(Math.min(bb.w, bb.h) * 0.3, 40 * u, 72 * u), LW = fs0 * 3.6;
    var sp = dc1_near(ctx, bb, R * 2.8 + LW, R * 2.6, d, 18 * u), lLeft = sp.cx > ctx.W / 2;
    var cx = lLeft ? sp.x + LW + R * 1.4 : sp.x + R * 1.4, cy = sp.cy, lw = Math.max(1.2, 1.6 * u), lw1 = Math.max(1, u);
    var LK = 'var lock=oe(time/0.6),s=(2.1-1.1*lock)*(1+0.25*ic(PO));', FA = 'Math.min(1,time/0.12)';
    var S = dc1_layer(ctx, 'reticle', cx, cy);
    // centre dot (blinks until locked)
    var gd = jzGrp(S, 'centre'); jzAddEllipse(gd, 5.2 * u, 5.2 * u); jzAddFill(gd, sc.accent);
    dc1_gexp(ctx, gd, 'ADBE Vector Group Opacity', 'oe(time/0.6)>0.97?100:(ST%2?100:20)');
    // three inward-pointing markers orbiting at 1.32 r
    var s5 = 5 * u;
    for (k = 0; k < 3; k++) {
        var gk = jzGrp(S, 'marker ' + (k + 1));
        jzAddPath(gk, [[-s5, 0], [s5 * 0.6, s5 * 0.8], [s5 * 0.6, -s5 * 0.8]], true); jzAddFill(gk, sc.accent);
        var AN = 'var an=(-time*30+' + jzN(k * 120 + (d.r || 0) * 60) + ')*Math.PI/180;';
        dc1_gexp(ctx, gk, 'ADBE Vector Position', LK + AN + 'var r2=' + jzN(R * 1.32) + '*s;[Math.cos(an)*r2,Math.sin(an)*r2]');
        dc1_gexp(ctx, gk, 'ADBE Vector Rotation', AN + 'an*180/Math.PI');
    }
    // cross ticks (fixed angles), lock arc, four spinning arcs: scaled by r/R, stroke widths compensated
    var gt = jzGrp(S, 'ticks');
    for (k = 0; k < 4; k++) { var an = k * Math.PI / 2; dc1_seg(gt, Math.cos(an) * R * 0.74, Math.sin(an) * R * 0.74, Math.cos(an) * R * 1.16, Math.sin(an) * R * 1.16); }
    st = jzAddStroke(gt, sc.fg, lw1, 90); dc1_sexp(ctx, st.property('ADBE Vector Stroke Width'), LK + jzN(lw1) + '/Math.max(0.05,s)');
    dc1_gexp(ctx, gt, 'ADBE Vector Scale', LK + '[100*s,100*s]');
    var gi = jzGrp(S, 'lock arc'); dc1_arc(gi, 0, 0, R * 0.62, -90, 270);
    st = jzAddStroke(gi, sc.sub, lw1, 60); dc1_sexp(ctx, st.property('ADBE Vector Stroke Width'), LK + jzN(lw1) + '/Math.max(0.05,s)');
    dc1_trim(ctx, gi, '100*oe(time/0.6)');
    dc1_gexp(ctx, gi, 'ADBE Vector Scale', LK + '[100*s,100*s]');
    var ga = jzGrp(S, 'arcs');
    for (k = 0; k < 4; k++) dc1_arc(ga, 0, 0, R, k * 90 - 28, k * 90 + 28);
    st = jzAddStroke(ga, sc.fg, lw);
    dc1_sexp(ctx, st.property('ADBE Vector Stroke Width'), LK + jzN(lw) + '/Math.max(0.05,s)');
    dc1_sexp(ctx, st.property('ADBE Vector Stroke Opacity'), LK + '(lock>0.97||ST%2==0)?100:50');
    dc1_gexp(ctx, ga, 'ADBE Vector Scale', LK + '[100*s,100*s]');
    dc1_gexp(ctx, ga, 'ADBE Vector Rotation', LK + '(1-lock)*140+time*16+' + jzN((d.r || 0) * 90));
    dc1_op(ctx, S, FA);
    // SCAN -> LOCK readout + percentage
    var fs = fs0 * 0.9, lx = lLeft ? cx - R * 1.5 : cx + R * 1.5, al = lLeft ? 'right' : 'left';
    var L1 = dc1_label(ctx, 'SCAN', lx, cy - R * 0.9, { size: fs, align: al, f: FA, expr: 'oe(time/0.6)>0.97?"LOCK":"SCAN"' });
    jzAnimator(L1, 'JZ Lock', [['ADBE Text Fill Color', jzHex(sc.accent)]], dc1_hd(ctx) + 'oe(time/0.6)>0.97?100:0');
    dc1_label(ctx, '000.0', lx, cy - R * 0.9 + fs * 1.3, { size: fs, align: al, a: 0.8, f: FA, expr: 'var v=(oe(time/0.6)*100).toFixed(1);while(v.length<5)v="0"+v;v' });
} });

/* ---- radar \u30EC\u30FC\u30C0\u30FC: a small scope with a sweeping beam and blips in a free corner */
jzReg('decor', 'radar', { back: false, build: function (ctx, bb0, d) {
    var bb = dc1_bb(ctx, bb0), sc = ctx.sc, u = dc1_u(ctx), fs = dc1_fs(ctx) * 0.85, k;
    var R = jzClamp(jzU(ctx) * 0.07, 40 * u, 96 * u), sp = dc1_corner(ctx, bb, R * 2 + 8 * u, R * 2 + fs * 2.2, d);
    var cx = sp.cx, cy = sp.y + R + 4 * u, lw = Math.max(1, u), a = sp.ok ? 1 : 0.35;
    var SW = 'var sw=(time*150+' + jzN((d.r || 0) * 360) + ')%360;';
    var S = dc1_layer(ctx, 'radar', cx, cy);
    jzSetExpr(jzXf(S, 'ADBE Scale'), dc1_hd(ctx) + 'var s=oe(time/0.5)*(1-0.15*ic(PO));[value[0]*s,value[1]*s]');
    // blips light up when the beam passes and fade (exp(-deg/70))
    var nb = 3 + (d.n | 0);
    for (k = 0; k < nb; k++) {
        var ang = dc1_r(d.seed, k, 1) * 360, rad = dc1_rr(0.2, 0.9, d.seed, k, 2), gb = jzGrp(S, 'blip ' + (k + 1));
        jzAddEllipse(gb, 8.4 * u, 8.4 * u); jzAddFill(gb, sc.fg);
        dc1_gset(gb, 'ADBE Vector Position', [Math.cos(ang * Math.PI / 180) * R * rad, Math.sin(ang * Math.PI / 180) * R * rad]);
        var GL = SW + 'var sn=((sw-' + jzN(ang) + ')%360+360)%360,gl=Math.exp(-sn/70);';
        dc1_gexp(ctx, gb, 'ADBE Vector Scale', GL + 'var q=(2+2.2*gl)/4.2*100;[q,q]');
        dc1_gexp(ctx, gb, 'ADBE Vector Group Opacity', GL + 'gl>0.03?gl*100:0');
    }
    // beam + fading trail (repeater, opacity 28% -> 2%)
    var gs = jzGrp(S, 'sweep'); dc1_gexp(ctx, gs, 'ADBE Vector Rotation', SW + 'sw');
    var gh = dc1_sub(gs, 'beam'); dc1_seg(gh, 0, 0, R, 0); jzAddStroke(gh, sc.accent, 1.6 * lw, 95);
    var gw = dc1_sub(gs, 'trail'); dc1_seg(gw, 0, 0, R, 0); jzAddStroke(gw, sc.accent, lw);
    var rp = dc1_rep(gw, 14, 0, 0, -3.2), rpt = rp.property('ADBE Vector Repeater Transform');
    rpt.property('ADBE Vector Repeater Opacity 1').setValue(28); rpt.property('ADBE Vector Repeater Opacity 2').setValue(2);
    rp.property('ADBE Vector Repeater Offset').setValue(1);
    // bezel ticks
    var gt = jzGrp(S, 'ticks');
    var t1 = dc1_sub(gt, 'minor'); dc1_seg(t1, R, 0, R + 2.5 * u, 0); jzAddStroke(t1, sc.fg, lw, 60); dc1_rep(t1, 36, 0, 0, 10);
    var t2 = dc1_sub(gt, 'major'); dc1_seg(t2, R, 0, R + 5 * u, 0); jzAddStroke(t2, sc.fg, lw, 60); dc1_rep(t2, 12, 0, 0, 30);
    var gc = jzGrp(S, 'cross'); dc1_seg(gc, -R, 0, R, 0); dc1_seg(gc, 0, -R, 0, R); jzAddStroke(gc, sc.sub, lw, 30);
    var fr = [1, 0.66, 0.33];
    for (k = 0; k < 3; k++) {
        var gr = jzGrp(S, 'ring ' + (k + 1)); dc1_arc(gr, 0, 0, R * fr[k], -90, 270); jzAddStroke(gr, sc.sub, lw, 55);
        dc1_trim(ctx, gr, '100*cl(oe(time/0.5)*1.4-' + jzN(k * 0.15) + ')');
    }
    dc1_op(ctx, S, '1', a);
    var ea = 'oe((time-0.3)/0.3)', ly = sp.y + R * 2 + fs * 1.4;
    dc1_label(ctx, 'RDR-' + dc1_p((ctx.cut.line | 0) + 1), cx - R, ly, { size: fs, a: a, f: ea });
    dc1_label(ctx, '000\u00B0', cx + R, ly, { size: fs, align: 'right', color: sc.fg, a: a, f: ea, expr: SW + 'var q=""+Math.floor(Math.max(0,sw));while(q.length<3)q="0"+q;q+"\\u00B0"' });
} });

/* ---- progressRing \u9032\u884C\u30EA\u30F3\u30B0: a thin gauge that fills with the cut's progress */
jzReg('decor', 'progressRing', { back: false, build: function (ctx, bb0, d) {
    var bb = dc1_bb(ctx, bb0), sc = ctx.sc, u = dc1_u(ctx), R = jzClamp(jzU(ctx) * 0.05, 34 * u, 64 * u), q;
    var sp = dc1_near(ctx, bb, R * 2 + 24 * u, R * 2 + 24 * u, d, 22 * u), cx = sp.cx, cy = sp.cy, lw = Math.max(1, u), a = sp.ok ? 1 : 0.4;
    var PG = 'var e=oe(time/0.5),pg=cl(time/' + jzN(Math.max(0.1, ctx.cut.dur)) + ');';
    var S = dc1_layer(ctx, 'progress ring', cx, cy);
    if ((d.v | 0) % 2) {                 // 24 segments that light up in accent
        var circ = 2 * Math.PI * R, dl = circ * 11 / 360, gp = circ * 4 / 360;
        var gl = jzGrp(S, 'lit'); dc1_arc(gl, 0, 0, R, -90, 270);
        var s1 = jzAddStroke(gl, sc.accent, 3 * u); dc1_dash(s1, dl, gp);
        dc1_trim(ctx, gl, PG + 'var n=Math.floor(pg*24+1e-3);Math.max(0,Math.min(100*e,(15*n-1)/3.6))');
        dc1_gset(gl, 'ADBE Vector Rotation', 2);
        var gu = jzGrp(S, 'unlit'); dc1_arc(gu, 0, 0, R, -90, 270);
        var s2 = jzAddStroke(gu, sc.sub, lw, 35); dc1_dash(s2, dl, gp);
        dc1_trim(ctx, gu, PG + '100*e');
        dc1_gset(gu, 'ADBE Vector Rotation', 2);
    } else {                             // continuous arc with a head dot
        var gd = jzGrp(S, 'head'); jzAddEllipse(gd, 6.4 * u, 6.4 * u, 0, -R); jzAddFill(gd, sc.accent);
        dc1_gexp(ctx, gd, 'ADBE Vector Rotation', PG + '360*pg*e');
        var gp2 = jzGrp(S, 'progress'); dc1_arc(gp2, 0, 0, R, -90, 270); jzAddStroke(gp2, sc.accent, 2.6 * u);
        dc1_trim(ctx, gp2, PG + '100*pg*e');
        var gb = jzGrp(S, 'track'); dc1_arc(gb, 0, 0, R, -90, 270); jzAddStroke(gb, sc.sub, lw, 35);
        dc1_trim(ctx, gb, PG + '100*e');
    }
    // 60 bezel ticks drawn clockwise
    var r0 = R + 6 * u, gt = jzGrp(S, 'ticks');
    for (q = 0; q < 2; q++) {
        var gq = dc1_sub(gt, q ? 'major' : 'minor'); dc1_seg(gq, 0, -r0, 0, -(r0 + (q ? 6 : 3) * u)); jzAddStroke(gq, sc.sub, lw, 55);
        var rp = dc1_rep(gq, q ? 12 : 60, 0, 0, q ? 30 : 6);
        dc1_sexp(ctx, rp.property('ADBE Vector Repeater Copies'), PG + 'Math.ceil(' + (q ? 12 : 60) + '*e-1e-6)');
    }
    dc1_op(ctx, S, '1', a);
    var fs = R * 0.52;
    dc1_label(ctx, '00', cx - fs * 0.12, cy, { size: fs, align: 'center', color: sc.fg, a: a, f: 'oe(time/0.5)', track: 0.02, expr: PG + 'var v=Math.floor(pg*100);v<10?"0"+v:""+v' });
    dc1_label(ctx, '%', cx + fs * 0.72, cy + fs * 0.12, { size: fs * 0.42, a: a, f: 'oe(time/0.5)' });
} });

/* ---- timecodeBar \u30BF\u30A4\u30E0\u30B3\u30FC\u30C9: rolling SMPTE readout + a mini scrub bar with in/out points */
jzReg('decor', 'timecodeBar', { back: false, build: function (ctx, bb0, d) {
    var bb = dc1_bb(ctx, bb0), W = ctx.W, H = ctx.H, sc = ctx.sc, u = dc1_u(ctx), m = dc1_mg(ctx), fs = dc1_fs(ctx), q;
    var w = Math.min(W * 0.38, 460 * u), h = fs * 3.4, low = !!d.low, x0 = d.right ? W - m - w : m, y0 = low ? H - m - h : m;
    if (dc1_hit(x0, y0, x0 + w, y0 + h, bb, 10 * u)) { low = !low; y0 = low ? H - m - h : m; }
    var a = dc1_hit(x0, y0, x0 + w, y0 + h, bb, 0) ? 0.3 : 1, ty = y0 + fs * 0.8, E = 'oe(time/0.5)';
    var Ltc = dc1_label(ctx, 'TC', x0, ty + fs * 0.12, { size: fs * 0.8, color: sc.accent, a: a });
    var tcW = jzSize(Ltc)[0] + fs * 0.8 * 0.7;
    dc1_label(ctx, '00:00:00:00', x0 + tcW, ty, { size: fs * 1.25, color: sc.fg, a: a, track: 0.06,
        expr: 'var t=Math.max(0,T),fr=Math.floor(t*24+1e-6);function p(n){n=Math.floor(Math.max(0,n));return n<10?"0"+n:""+n;}' +
            'var s=p(t/3600)+":"+p((t/60)%60)+":"+p(t%60)+":"+p(fr%24);s.substr(0,Math.ceil(s.length*cl(time/0.35)))' });
    dc1_label(ctx, 'F 0000', x0 + w, ty + fs * 0.12, { size: fs * 0.8, align: 'right', a: a, f: E, expr: 'var s=""+Math.floor(time*24+1e-6);while(s.length<4)s="0"+s;"F "+s' });
    var by = y0 + h - fs * 0.6, lw = Math.max(1, u), PG = 'var e=' + E + ',pg=cl(time/' + jzN(Math.max(0.1, ctx.cut.dur)) + ');';
    var S = dc1_layer(ctx, 'timecode bar', 0, 0);
    var gm = jzGrp(S, 'playhead'); jzAddPath(gm, [[x0 - 5 * u, by - 12 * u], [x0 + 5 * u, by - 12 * u], [x0, by - 4 * u]], true); jzAddFill(gm, sc.accent);
    dc1_gexp(ctx, gm, 'ADBE Vector Position', PG + '[' + jzN(w) + '*pg*e,0]');
    var gp = jzGrp(S, 'played'); dc1_seg(gp, x0, by, x0 + w, by); jzAddStroke(gp, sc.fg, 2.2 * u); dc1_trim(ctx, gp, PG + '100*pg*e');
    var gi = jzGrp(S, 'in'); dc1_seg(gi, x0, by + 4 * u, x0, by + 10 * u); jzAddStroke(gi, sc.fg, lw);
    dc1_gexp(ctx, gi, 'ADBE Vector Group Opacity', PG + '100*e');
    var go = jzGrp(S, 'out'); dc1_seg(go, x0, by + 4 * u, x0, by + 10 * u); jzAddStroke(go, sc.fg, lw);
    dc1_gexp(ctx, go, 'ADBE Vector Group Opacity', PG + '100*e');
    dc1_gexp(ctx, go, 'ADBE Vector Position', PG + '[' + jzN(w) + '*e,0]');
    var gt = jzGrp(S, 'ticks'), tk = [[48, 2], [12, 4], [4, 7]];
    for (q = 0; q < 3; q++) {
        var gq = dc1_sub(gt, 'ticks ' + tk[q][0]); dc1_seg(gq, x0, by, x0, by - tk[q][1] * u); jzAddStroke(gq, sc.sub, lw, 60);
        var rp = dc1_rep(gq, tk[q][0] + 1, w / tk[q][0], 0, 0);
        dc1_sexp(ctx, rp.property('ADBE Vector Repeater Copies'), PG + 'Math.floor(' + tk[q][0] + '*e+1e-6)+1');
    }
    var gb = jzGrp(S, 'scrub'); dc1_seg(gb, x0, by, x0 + w, by); jzAddStroke(gb, sc.sub, lw, 60); dc1_trim(ctx, gb, PG + '100*e');
    dc1_op(ctx, S, '1', a);
    var ly = by + 9 * u + fs * 0.35;
    dc1_label(ctx, 'IN', x0 + 4 * u, ly, { size: fs * 0.6, a: a * 0.8, f: E });
    var Lo = dc1_label(ctx, 'OUT', x0 + w - 4 * u, ly, { size: fs * 0.6, align: 'right', a: a * 0.8, f: E });
    jzSetExpr(jzXf(Lo, 'ADBE Position'), dc1_hd(ctx) + '[value[0]-' + jzN(w) + '*(1-' + E + '),value[1]]');
} });

/* ---- rulerEdge \u7AEF\u306E\u5B9A\u898F: a graduated ruler on one screen edge whose markers track the lyric */
jzReg('decor', 'rulerEdge', { back: false, build: function (ctx, bb0, d) {
    var bb = dc1_bb(ctx, bb0), W = ctx.W, H = ctx.H, sc = ctx.sc, u = dc1_u(ctx), m = dc1_mg(ctx), i, q;
    var vert = !!d.corner, fs = dc1_fs(ctx) * 0.72, lw = Math.max(1, u), st = 10 * u;
    var len = vert ? H : W, a0 = m, a1 = len - m, side = vert ? (d.right ? 1 : -1) : (d.low ? 1 : -1);
    var base = vert ? (side > 0 ? W - m * 0.55 : m * 0.55) : (side > 0 ? H - m * 0.55 : m * 0.55), inw = -side;
    var P2 = function (al, off) { return vert ? [base + inw * off, al] : [al, base + inw * off]; };
    var n = Math.floor((a1 - a0) / st), HE = 'var e=oc(time/0.55),hd=' + jzN(a0) + '+' + jzN(a1 - a0) + '*e;';
    var S = dc1_layer(ctx, 'ruler edge', 0, 0);
    // lyric markers: span (scaled out from the screen centre), mid triangle
    var lo = vert ? bb.y0 : bb.x0, hi = vert ? bb.y1 : bb.x1, from = len / 2, off = 26 * u;
    var Lc = jzClamp(lo, a0, a1), Hc = jzClamp(hi, a0, a1), Mc = jzClamp((lo + hi) / 2, a0, a1), EM = 'var em=oc((time-0.2)/0.5);';
    var gsp = jzGrp(S, 'span'), fp = P2(from, off), p1 = P2(Lc, off), p2 = P2(Hc, off);
    dc1_seg(gsp, p1[0], p1[1], p2[0], p2[1]);
    var c1 = P2(Lc, off - 5 * u), c2 = P2(Lc, off + 5 * u), c3 = P2(Hc, off - 5 * u), c4 = P2(Hc, off + 5 * u);
    dc1_seg(gsp, c1[0], c1[1], c2[0], c2[1]); dc1_seg(gsp, c3[0], c3[1], c4[0], c4[1]);
    jzAddStroke(gsp, sc.accent, 2 * u, 90);
    dc1_gset(gsp, 'ADBE Vector Anchor', fp); dc1_gset(gsp, 'ADBE Vector Position', fp);
    dc1_gexp(ctx, gsp, 'ADBE Vector Scale', EM + (vert ? '[100,100*em]' : '[100*em,100]'));
    dc1_gexp(ctx, gsp, 'ADBE Vector Group Opacity', EM + '100*em');
    var tp = P2(Mc, 3 * u), s5 = 5 * u;
    var tri = vert ? [[tp[0], tp[1]], [tp[0] + inw * s5 * 1.6, tp[1] - s5], [tp[0] + inw * s5 * 1.6, tp[1] + s5]] : [[tp[0], tp[1]], [tp[0] - s5, tp[1] + inw * s5 * 1.6], [tp[0] + s5, tp[1] + inw * s5 * 1.6]];
    var gtr = jzGrp(S, 'mid'); jzAddPath(gtr, tri, true); jzAddFill(gtr, sc.fg);
    var MV = EM + 'var dm=-(' + jzN(Mc - from) + ')*(1-em);';
    dc1_gexp(ctx, gtr, 'ADBE Vector Position', MV + (vert ? '[0,dm]' : '[dm,0]'));
    dc1_gexp(ctx, gtr, 'ADBE Vector Group Opacity', EM + '100*em');
    // graduation: 1 / 5 / 10 step repeaters that follow the drawing head
    var tk = [[1, 4.5], [5, 9], [10, 14]], gt = jzGrp(S, 'ticks');
    for (q = 0; q < 3; q++) {
        var stp = st * tk[q][0], t0 = P2(a0, 0), t1 = P2(a0, tk[q][1] * u), gq = dc1_sub(gt, 'ticks ' + tk[q][0]);
        dc1_seg(gq, t0[0], t0[1], t1[0], t1[1]); jzAddStroke(gq, sc.sub, lw, 70);
        var nq = Math.floor(n / tk[q][0]) + 1, rp = dc1_rep(gq, nq, vert ? 0 : stp, vert ? stp : 0, 0);
        dc1_sexp(ctx, rp.property('ADBE Vector Repeater Copies'), HE + 'Math.min(' + nq + ',Math.floor((hd-' + jzN(a0) + ')/' + jzN(stp) + '+1e-6)+1)');
    }
    var gb = jzGrp(S, 'rail'), b0 = P2(a0, 0), b1 = P2(a1, 0);
    dc1_seg(gb, b0[0], b0[1], b1[0], b1[1]); jzAddStroke(gb, sc.sub, lw, 50); dc1_trim(ctx, gb, HE + '100*e');
    dc1_op(ctx, S, '1');
    // numbers every 10 ticks, one text layer; each shows once the head has passed it
    var strs = [], pts = [], al = vert ? (inw > 0 ? 'left' : 'right') : 'left';
    for (i = 0; i <= n; i += 10) { strs.push(dc1_p(i, 3)); pts.push(P2(a0 + i * st + (vert ? 0 : 3 * u), 20 * u + fs * 0.4)); }
    var Ln = dc1_lines(ctx, strs, pts, { size: fs, align: al, a: 0.7 });
    jzAnimator(Ln, 'JZ Reveal', [['ADBE Text Opacity', 0]], dc1_hd(ctx) + HE + 'var j=Math.floor((textIndex-1)/3);(' + jzN(a0) + '+j*' + jzN(10 * st) + '<=hd-' + jzN(20 * u) + ')?0:100');
    // measured size of the lyric
    var lp = P2(Mc, off + 8 * u + fs);
    var Lv = dc1_label(ctx, dc1_p(hi - lo, 4), lp[0] + (vert ? 0 : 6 * u), lp[1], { size: fs, color: sc.fg, align: al, f: 'oc((time-0.2)/0.5)' });
    jzSetExpr(jzXf(Lv, 'ADBE Position'), dc1_hd(ctx) + MV + (vert ? '[value[0],value[1]+dm]' : '[value[0]+dm,value[1]]'));
} });

/* ---- dimension \u5BF8\u6CD5\u7DDA: drafting dimension lines measuring the lyric (width / height) */
jzReg('decor', 'dimension', { back: false, build: function (ctx, bb0, d) {
    var bb = dc1_bb(ctx, bb0), W = ctx.W, H = ctx.H, sc = ctx.sc, u = dc1_u(ctx), m = dc1_mg(ctx);
    var lw = Math.max(1, u), fs = dc1_fs(ctx) * 0.85, arch = (d.v | 0) % 2 === 1, gap = 20 * u + bb.h * 0.1;
    var below = !!d.low, y = below ? bb.y1 + gap : bb.y0 - gap;
    if (y < m * 0.6 || y > H - m * 0.6) { below = !below; y = below ? bb.y1 + gap : bb.y0 - gap; }
    var x0 = bb.x0, x1 = bb.x1, cx = (x0 + x1) / 2, sy = below ? 1 : -1, E2 = 'oe((time-0.05)/0.35)', EA = 'var e=oe(time/0.55),ea=cl((e-0.7)/0.3);';
    var Lw = dc1_label(ctx, String(Math.round(x1 - x0)), cx, y, { size: fs, align: 'center', color: d.accent ? sc.accent : sc.fg, f: E2 });
    var LH = (jzSize(Lw)[0] + 14 * u) / 2, S = dc1_layer(ctx, 'dimension', 0, 0);
    var trim = function (half) { return EA + 'var L=' + jzN(half) + ',g=' + jzN(LH) + ';L>g?100*cl((e*L-g)/(L-g)):0'; };
    // width: arrows, line (gap for the value, grows out from the centre), extension lines
    var ga = jzGrp(S, 'arrows w');
    if (arch) { dc1_seg(ga, x0 - 5 * u, y + 5 * u, x0 + 5 * u, y - 5 * u); dc1_seg(ga, x1 - 5 * u, y + 5 * u, x1 + 5 * u, y - 5 * u); jzAddStroke(ga, sc.fg, 1.6 * u); }
    else { jzAddPath(ga, [[x0, y], [x0 + 10 * u, y - 3.5 * u], [x0 + 10 * u, y + 3.5 * u]], true); jzAddPath(ga, [[x1, y], [x1 - 10 * u, y - 3.5 * u], [x1 - 10 * u, y + 3.5 * u]], true); jzAddFill(ga, sc.fg); }
    dc1_gexp(ctx, ga, 'ADBE Vector Group Opacity', EA + '100*ea');
    var gl = jzGrp(S, 'dim w'); dc1_seg(gl, cx - LH, y, x0, y); dc1_seg(gl, cx + LH, y, x1, y); jzAddStroke(gl, sc.fg, lw, 90);
    dc1_trim(ctx, gl, trim(cx - x0));
    var ge = jzGrp(S, 'ext w'), ey0 = (below ? bb.y1 : bb.y0) + sy * 6 * u, ey1 = y + sy * 8 * u;
    dc1_seg(ge, x0, ey0, x0, ey1); dc1_seg(ge, x1, ey0, x1, ey1); jzAddStroke(ge, sc.sub, lw, 70); dc1_trim(ctx, ge, '100*' + E2);
    // height on one side
    if (d.big || (d.v | 0) % 3 === 0) {
        var gx = 20 * u + bb.w * 0.02, right = !!d.right, x = right ? bb.x1 + gx : bb.x0 - gx;
        if (x < m || x > W - m) { right = !right; x = right ? bb.x1 + gx : bb.x0 - gx; }
        var sx = right ? 1 : -1, cy = bb.cy;
        var Lh = dc1_label(ctx, String(Math.round(bb.h)), x, cy, { size: fs, align: 'center', rot: -90, color: sc.fg, f: E2 });
        LH = (jzSize(Lh)[0] + 14 * u) / 2;
        var gah = jzGrp(S, 'arrows h');
        if (arch) { dc1_seg(gah, x - 5 * u, bb.y0 + 5 * u, x + 5 * u, bb.y0 - 5 * u); dc1_seg(gah, x - 5 * u, bb.y1 + 5 * u, x + 5 * u, bb.y1 - 5 * u); jzAddStroke(gah, sc.fg, 1.6 * u); }
        else { jzAddPath(gah, [[x, bb.y0], [x - 3.5 * u, bb.y0 + 10 * u], [x + 3.5 * u, bb.y0 + 10 * u]], true); jzAddPath(gah, [[x, bb.y1], [x - 3.5 * u, bb.y1 - 10 * u], [x + 3.5 * u, bb.y1 - 10 * u]], true); jzAddFill(gah, sc.fg); }
        dc1_gexp(ctx, gah, 'ADBE Vector Group Opacity', EA + '100*ea');
        var glh = jzGrp(S, 'dim h'); dc1_seg(glh, x, cy - LH, x, bb.y0); dc1_seg(glh, x, cy + LH, x, bb.y1); jzAddStroke(glh, sc.fg, lw, 90);
        dc1_trim(ctx, glh, trim(cy - bb.y0));
        var geh = jzGrp(S, 'ext h'), ex0 = (right ? bb.x1 : bb.x0) + sx * 6 * u, ex1 = x + sx * 8 * u;
        dc1_seg(geh, ex0, bb.y0, ex1, bb.y0); dc1_seg(geh, ex0, bb.y1, ex1, bb.y1); jzAddStroke(geh, sc.sub, lw, 70); dc1_trim(ctx, geh, '100*' + E2);
    }
    dc1_op(ctx, S, '1');
} });

/* ---- indexNum \u901A\u3057\u756A\u53F7: a thin numeral "03 / 12" with rolling digits in a free corner */
jzReg('decor', 'indexNum', { back: false, build: function (ctx, bb0, d) {
    var bb = dc1_bb(ctx, bb0), sc = ctx.sc, u = dc1_u(ctx), c = ctx.cut, i, k, lines = 0;
    try { lines = ctx.plan && ctx.plan.lines ? ctx.plan.lines.length : 0; } catch (e0) { lines = 0; }
    var idx = c.line >= 0 ? (c.line | 0) + 1 : (c.index | 0) + 1, num = dc1_p(idx, 2), tot = '/' + dc1_p(Math.max(lines, idx), 2);
    var font = (d.v | 0) % 2 ? 'mincho_light' : 'gothic_light', S = jzClamp(jzU(ctx) * 0.1, 64 * u, 132 * u), ts = S * 0.3, LD = S * 1.25;
    // one text strip per digit (the digit and the ones it rolls through, one per line) behind a window mask
    var digits = jzChars(num), strips = [], nw = 0;
    for (i = 0; i < digits.length; i++) {
        var dg = parseInt(digits[i], 10), steps = d.mode === 'count' ? 6 + i * 3 : 3, ls = [];
        for (k = 0; k <= steps; k++) ls.push(String(((dg - k) % 10 + 10) % 10));
        var Ls = jzText(ctx, ls.join('\r'), { font: font, size: S, color: sc.fg, x: 0, y: 0, align: 'center', track: 0.02, leading: LD, name: 'index digit ' + (i + 1) });
        var dw = jzRect(Ls).width;
        strips.push([Ls, steps, dw]); nw += dw;
    }
    var Lt = jzText(ctx, tot, { font: font, size: ts, color: sc.sub, x: 0, y: 0, align: 'left', track: 0.06 });
    var tw = jzSize(Lt)[0], w = nw + tw + S * 0.12, h = S * 1.25;
    var sp = dc1_corner(ctx, bb, w, h, d, 1.1), A = sp.ok ? 1 : 0.35, x0 = sp.x, base = sp.y + S * 0.8, x = x0, AF = 'oc(time/0.2)', E2 = 'oe((time-0.25)/0.4)';
    for (i = 0; i < strips.length; i++) {
        var L = strips[i][0], dwi = strips[i][2];
        jzXf(L, 'ADBE Anchor Point').setValue([0, -0.36 * S]); jzXf(L, 'ADBE Position').setValue([x + dwi / 2, base]);
        dc1_mask(L, -dwi / 2 - 2, -0.36 * S - S * 0.5, dwi / 2 + 2, -0.36 * S + S * 0.48);
        jzAnimator(L, 'JZ Roll', [['ADBE Text Position 3D', [0, -LD * strips[i][1], 0]]], dc1_hd(ctx) + '(1-oe((time-' + jzN(0.04 + i * 0.08) + ')/0.55))*100');
        jzNoGhost(L); dc1_op(ctx, L, AF, A);
        x += dwi;
    }
    jzXf(Lt, 'ADBE Position').setValue([x + S * 0.1, base + S * 0.26]);
    jzNoGhost(Lt); dc1_op(ctx, Lt, AF + '*' + E2, A);
    var ry = base + S * 0.52, Sh = dc1_layer(ctx, 'index rule', 0, 0);
    var gr = jzGrp(Sh, 'accent'); jzAddRect(gr, S * 0.22, 3 * u, 0, x0 + S * 0.11, ry); jzAddFill(gr, sc.accent);
    dc1_gset(gr, 'ADBE Vector Anchor', [x0, ry]); dc1_gset(gr, 'ADBE Vector Position', [x0, ry]);
    dc1_gexp(ctx, gr, 'ADBE Vector Scale', '[100*' + E2 + ',100]');
    var gl = jzGrp(Sh, 'rule'); dc1_seg(gl, x0, ry, x0 + w, ry); jzAddStroke(gl, sc.sub, Math.max(1, u), 80); dc1_trim(ctx, gl, '100*oe((time-0.1)/0.5)');
    dc1_op(ctx, Sh, AF, A);
    dc1_label(ctx, c.line >= 0 ? 'LYRIC' : 'INTRO', x0, ry + dc1_fs(ctx) * 0.9, { size: dc1_fs(ctx) * 0.72, a: A, f: AF + '*' + E2, track: 0.3 });
} });

/* ---- dateStamp \u65E5\u4ED8\u5199\u771F: orange seven-segment date imprint like an old film camera */
function dc1_segDigit(x, y, dw, dh, t, dg, sk) {
    var SEG = ['abcdef', 'bc', 'abged', 'abgcd', 'fgbc', 'afgcd', 'afgedc', 'abc', 'abcdefg', 'abcdfg'], out = [], h2 = dh / 2, k = t / 2, i, j;
    var hs = function (xa, xb, yy) { return [[xa + k, yy], [xa + 2 * k, yy - k], [xb - 2 * k, yy - k], [xb - k, yy], [xb - 2 * k, yy + k], [xa + 2 * k, yy + k]]; };
    var vs = function (xx, ya, yb) { return [[xx, ya + k], [xx + k, ya + 2 * k], [xx + k, yb - 2 * k], [xx, yb - k], [xx - k, yb - 2 * k], [xx - k, ya + 2 * k]]; };
    var S2 = { a: hs(x, x + dw, y), g: hs(x, x + dw, y + h2), d: hs(x, x + dw, y + dh), f: vs(x, y, y + h2), b: vs(x + dw, y, y + h2), e: vs(x, y + h2, y + dh), c: vs(x + dw, y + h2, y + dh) };
    var s = SEG[dg] || '';
    for (i = 0; i < s.length; i++) { var P = S2[s.charAt(i)], q = []; for (j = 0; j < P.length; j++) q.push([P[j][0] + (y + dh / 2 - P[j][1]) * sk, P[j][1]]); out.push(q); }
    return out;
}
jzReg('decor', 'dateStamp', { back: false, build: function (ctx, bb0, d) {
    var bb = dc1_bb(ctx, bb0), sc = ctx.sc, u = dc1_u(ctx), sd = d.seed, i, j, q;
    var yy = dc1_h(sd, 1) % 2 ? 90 + dc1_h(sd, 2) % 10 : dc1_h(sd, 3) % 27, mm = 1 + dc1_h(sd, 4) % 12, dd = 1 + dc1_h(sd, 5) % 28;
    var groups = [dc1_p(yy, 2), String(mm), dc1_p(dd, 2)];
    var dh = jzClamp(jzU(ctx) * 0.034, 24 * u, 46 * u), dw = dh * 0.52, t = dh * 0.12, gapD = dw * 0.42, gapG = dw * 1.35, width = gapG * 2 + dw * 0.5;
    for (i = 0; i < 3; i++) width += groups[i].length * (dw + gapD);
    var sp = dc1_corner(ctx, bb, width, dh * 1.4, { right: (d.v | 0) % 3 === 2 ? !d.right : true, low: true }, 1.2);
    var x = sp.x + dw * 0.5, y = sp.y + dh * 0.2, pieces = [];
    pieces.push([[x - dw * 0.1, y - t * 0.2], [x + t * 0.9, y - t * 0.2], [x + t * 0.2, y + dh * 0.32], [x - dw * 0.1 - t * 0.6, y + dh * 0.32]]);
    x += dw * 0.35;
    for (i = 0; i < 3; i++) {
        var gs = groups[i];
        for (j = 0; j < gs.length; j++) { var sg = dc1_segDigit(x, y, dw, dh, t, parseInt(gs.charAt(j), 10), 0.1); for (q = 0; q < sg.length; q++) pieces.push(sg[q]); x += dw + gapD; }
        x += gapG - gapD;
    }
    // every segment flickers on at its own time, then stays lit
    var S = dc1_layer(ctx, 'date stamp', 0, 0), A = sp.ok ? 1 : 0.4, LHX = dc1_lh(ctx);
    for (i = 0; i < pieces.length; i++) {
        var g = jzGrp(S, 'seg ' + (i + 1)); jzAddPath(g, pieces[i], true); jzAddFill(g, sc.accent);
        var t0 = 0.05 + dc1_r(sd, i, 7) * 0.35;
        jzSetExpr(jzGX(g).property('ADBE Vector Group Opacity'), LHX + 'var t0=' + jzN(t0) + ';time<t0?0:((time-t0<0.12&&hh(ST*7.13+' + (i * 13 + 1) + '+SD)<0.5)?30:' + jzN(92 * A) + ')');
    }
    // the browser's soft accent glow (canvas shadowBlur)
    var ds = jzEffect(S, 'ADBE Drop Shadow', 'JZ Glow');
    jzEP(ds, 1, jzHex(sc.accent)); jzEP(ds, 2, 204); jzEP(ds, 3, 0); jzEP(ds, 4, 0); jzEP(ds, 5, 12 * u);
    dc1_op(ctx, S, '1');
} });

/* ---- qrBlock QR\u98A8: a QR-like module block that scans in, with finder squares */
jzReg('decor', 'qrBlock', { back: false, build: function (ctx, bb0, d) {
    var bb = dc1_bb(ctx, bb0), sc = ctx.sc, u = dc1_u(ctx), N = 21, sd = d.seed, i, j, b, f, NB = 10;
    var S0 = jzClamp(jzU(ctx) * 0.1, 76 * u, 140 * u), c = S0 / N, fs = dc1_fs(ctx) * 0.72;
    var sp = dc1_corner(ctx, bb, S0, S0 + fs * 2, d, 1.1), x0 = sp.x, y0 = sp.y, A = sp.ok ? 1 : 0.35, buckets = [];
    for (i = 0; i < NB * 2; i++) buckets.push([]);
    var finder = function (fi, fj) {
        var F = [[0, 0], [N - 7, 0], [0, N - 7]];
        for (var q = 0; q < 3; q++) {
            var di = fi - F[q][0], dj = fj - F[q][1];
            if (di >= 0 && di < 7 && dj >= 0 && dj < 7) { var r = Math.max(Math.abs(di - 3), Math.abs(dj - 3)); return r === 3 || r <= 1 ? (r <= 1 ? 2 : 1) : 0; }
        }
        return -1;
    };
    // modules grouped by the moment the diagonal scan reaches them
    for (j = 0; j < N; j++) for (i = 0; i < N; i++) {
        f = finder(i, j);
        if (f === -1) {
            if ((i === 7 || j === 7) && (i < 8 && j < 8 || i > N - 9 && j < 8 || i < 8 && j > N - 9)) f = 0;
            else if (i === 6 || j === 6) f = (i + j) % 2 === 0 ? 1 : 0;
            else f = dc1_r(sd, i, j, 3) < 0.48 ? 1 : 0;
        }
        if (!f) continue;
        var te = ((i + j) / (2 * N - 2) + 0.1 - (dc1_r(sd, i, j, 9) - 0.5) * 0.1) / 1.25;
        b = jzClamp(Math.floor(te * NB), 0, NB - 1);
        buckets[b + (f === 2 && d.accent ? NB : 0)].push([x0 + i * c, y0 + j * c]);
    }
    var S = dc1_layer(ctx, 'qr block', 0, 0), cs = c + 0.35;
    var gs = jzGrp(S, 'scan'); dc1_seg(gs, x0 - 6 * u, y0, x0 + S0 + 6 * u, y0); jzAddStroke(gs, sc.accent, 1.5 * u);
    dc1_gexp(ctx, gs, 'ADBE Vector Position', 'var e=cl(time/0.55);[0,' + jzN(S0 * 1.1) + '*e]');
    dc1_gexp(ctx, gs, 'ADBE Vector Group Opacity', 'var e=cl(time/0.55);(e<1&&e*1.1<1)?100:0');
    for (b = 0; b < NB * 2; b++) {
        var lst = buckets[b]; if (!lst.length) continue;
        var g = jzGrp(S, (b >= NB ? 'finder ' : 'modules ') + ((b % NB) + 1));
        for (i = 0; i < lst.length; i++) jzAddRect(g, cs, cs, 0, lst[i][0] + cs / 2, lst[i][1] + cs / 2);
        jzAddFill(g, b >= NB ? sc.accent : sc.fg);
        jzSetExpr(jzGX(g).property('ADBE Vector Group Opacity'), 'time>=' + jzN(0.55 * ((b % NB) + 0.5) / NB) + '?100:0');
    }
    // exit: modules drop out at random (browser: r < pOut * 1.1)
    var bd = jzEffect(S, 'ADBE Block Dissolve', 'JZ Out');
    jzEX(bd, 1, dc1_hd(ctx) + 'Math.min(100,PO*110)'); jzEP(bd, 2, c); jzEP(bd, 3, c);
    dc1_op(ctx, S, '1', A);
    var hx = (dc1_h(sd, 12) % 0xffffff).toString(16).toUpperCase();
    while (hx.length < 6) hx = '0' + hx;
    dc1_label(ctx, 'ID ' + hx, x0, y0 + S0 + fs * 1.2, { size: fs, a: A, f: 'oe((time-0.4)/0.3)', track: 0.14 });
} });

/* ---- glitchRects \u30B0\u30EA\u30C3\u30C1\u7247: flickering data slivers and hollow frames hugging the lyric's sides
   a pool of rects re-rolled every 12 fps step by expressions (the browser's per-step random set) */
jzReg('decor', 'glitchRects', { back: false, build: function (ctx, bb0, d) {
    var bb = dc1_bb(ctx, bb0), W = ctx.W, sc = ctx.sc, u = dc1_u(ctx), i, ly;
    var gap = 10 * u + bb.h * 0.06, ns = 0.7 + 0.2 * (d.n | 0), M = Math.round(13 * ns);
    var cols = [sc.fg, sc.accent, sc.ghostA || sc.accent, sc.ghostB || sc.sub], CX = '[' + dc1_col(cols[0]) + ',' + dc1_col(cols[1]) + ',' + dc1_col(cols[2]) + ',' + dc1_col(cols[3]) + ']';
    var BX = 'var U=' + jzN(u) + ',W=' + jzN(W) + ',X0=' + jzN(bb.x0) + ',X1=' + jzN(bb.x1) + ',Y0=' + jzN(bb.y0) + ',Y1=' + jzN(bb.y1) + ',GP=' + jzN(gap) + ';' +
        'var bu=Math.max(1-cl(time/0.45),PO>0?1-PO*0.6:0),idle=hh(ST*7.7+SD*0.11)<0.2;';
    var code = function (I) {
        return dc1_lh(ctx) + BX + 'var I=' + I + ';function R(k){return hh(ST*113.7+I*17.31+k*3.17+SD*0.37);}' +
            'var n=Math.round((bu>0?7+6*bu:(idle?3:1))*' + jzN(ns) + ');' +
            'var right=R(1)<0.5,h=(1.5+R(3)*9)*U,room=right?W-X1-GP:X0-GP,w=(18+R(4)*R(4)*220)*U,x,y;' +
            'if(room>60*U&&R(8)<0.75){y=Y0+(Y1-Y0)*R(2);w=Math.min(w,room-8*U);var sl=R(5)*Math.max(0,room-w-8*U)*0.5;x=right?X1+GP+sl:X0-GP-w-sl;}' +
            'else{y=R(9)<0.5?Y0-GP-R(2)*40*U:Y1+GP+R(2)*40*U;x=X0+(X1-w-X0)*R(5);}' +
            'x=Math.max(4*U,Math.min(W-w-4*U,x));' +
            'var hit=!(x+w<X0-2||x>X1+2||y+h<Y0-2||y>Y1+2),vis=I<n&&!hit,hol=R(6)<0.25,k=Math.floor(R(7)*4);' +
            'if(hol){y-=h*1.5;w*=0.7;h*=3.5;}';
    };
    // layer A (ghosted like the browser): solid fg / accent slivers; layer B (main pass only): ghost-tint slivers + hollow frames
    var SA = jzShapeLayer(ctx, 'glitch slivers', 0, 0), SB = dc1_layer(ctx, 'glitch frames', 0, 0);
    for (i = 0; i < M; i++) {
        var cd = code(i);
        for (ly = 0; ly < 2; ly++) {
            var g = jzGrp(ly ? SB : SA, 'sliver ' + (i + 1)), rc = jzAddRect(g, 10, 10);
            jzSetExpr(rc.property('ADBE Vector Rect Size'), cd + '[w,h]');
            jzSetExpr(rc.property('ADBE Vector Rect Position'), cd + '[x+w/2,y+h/2]');
            if (ly) { var st = jzAddStroke(g, sc.fg, Math.max(1, u), 80); jzSetExpr(st.property('ADBE Vector Stroke Opacity'), cd + 'hol?80:0'); }
            var fl = jzAddFill(g, cols[0], 90);
            jzSetExpr(fl.property('ADBE Vector Fill Color'), cd + CX + '[k]');
            if (ly) jzSetExpr(fl.property('ADBE Vector Fill Opacity'), cd + 'hol?0:90');
            jzSetExpr(jzGX(g).property('ADBE Vector Group Opacity'), cd + (ly ? '(vis&&(hol||k>=2))?100:0' : '(vis&&!hol&&k<2)?100:0'));
        }
    }
    dc1_op(ctx, SA, '1'); dc1_op(ctx, SB, '1');
    // hex readout beside the lyric during bursts / idle ticks
    var fs = dc1_fs(ctx) * 0.75, Lb = dc1_label(ctx, '0xFFFF', 0, 0, { size: fs, color: sc.accent }), lwd = jzSize(Lb)[0];
    var LC = dc1_lh(ctx) + BX + 'function R(k){return hh(ST*57.3+1683+k*3.17+SD*0.37);}var right=R(1)<0.5;';
    jzSetExpr(Lb.property('ADBE Text Properties').property('ADBE Text Document'), LC + 'var v=Math.floor(hh(ST*31.7+SD*0.53)*65535).toString(16).toUpperCase();while(v.length<4)v="0"+v;"0x"+v');
    jzSetExpr(jzXf(Lb, 'ADBE Position'), LC + '[right?X1+GP:X0-GP-' + jzN(lwd) + ',Y0+(Y1-Y0)*R(2)-10*U]');
    jzSetExpr(jzXf(Lb, 'ADBE Opacity'), LC + '(bu>0.2||idle)?100*(1-ic(PO)):0');
} });

// ================================================================ geometry

/* ---- concentricSquares \u540C\u5FC3\u56DB\u89D2: nested hairline squares, a slow twist or an endless tunnel */
jzReg('decor', 'concentricSquares', { back: false, build: function (ctx, bb0, d) {
    var bb = dc1_bb(ctx, bb0), sc = ctx.sc, u = dc1_u(ctx), S0 = jzClamp(jzU(ctx) * 0.17, 100 * u, 220 * u), i, k;
    var sp = d.corner ? dc1_corner(ctx, bb, S0 * 1.2, S0 * 1.2, d) : dc1_near(ctx, bb, S0 * 1.2, S0 * 1.2, d, 24 * u);
    var n = 5 + (d.n | 0), lw = Math.max(1, 1.2 * u), tunnel = (d.v | 0) % 2 === 1, A = sp.ok ? 1 : 0.35, dir = d.right ? 1 : -1;
    var S = dc1_layer(ctx, 'concentric squares', sp.cx, sp.cy);
    var gd = jzGrp(S, 'centre'); jzAddEllipse(gd, 4.8 * u, 4.8 * u); jzAddFill(gd, sc.accent);
    dc1_gexp(ctx, gd, 'ADBE Vector Group Opacity', '100*oe((time-0.3)/0.3)');
    for (i = 0; i < n; i++) {
        var s = tunnel ? S0 * 0.5 : S0 * 0.5 * (1 - i / n * 0.86), pts = [], acc = i === (d.n | 0), w = acc ? lw * 1.6 : lw;
        for (k = 0; k <= 4; k++) { var an = (45 + k * 90) * Math.PI / 180; pts.push([Math.cos(an) * s * Math.SQRT2, Math.sin(an) * s * Math.SQRT2]); }
        var F = 'var f=((' + jzN(i / n) + '+time*0.18)%1);';
        var g = jzGrp(S, 'square ' + (i + 1)); jzAddPath(g, pts, false);
        var st = jzAddStroke(g, acc ? sc.accent : sc.fg, w, (0.45 + 0.55 * (1 - i / n)) * 100);
        if (tunnel) jzSetExpr(st.property('ADBE Vector Stroke Width'), F + jzN(w) + '/(0.08+0.92*f)');
        dc1_trim(ctx, g, '100*oe((time-' + jzN(i * 0.045) + ')/0.5)');
        if (tunnel) {
            dc1_gset(g, 'ADBE Vector Rotation', (d.r || 0) > 0.5 ? 45 : 0);
            dc1_gexp(ctx, g, 'ADBE Vector Scale', F + 'var q=100*(0.08+0.92*f);[q,q]');
            dc1_gexp(ctx, g, 'ADBE Vector Group Opacity', F + '100*Math.sin(Math.PI*f)');
        } else dc1_gexp(ctx, g, 'ADBE Vector Rotation', jzN(i * (6 + (d.r || 0) * 8)) + '+time*' + jzN(7 * dir));
    }
    dc1_op(ctx, S, '1', A);
} });

/* ---- triangleSpin \u56DE\u8EE2\u4E09\u89D2: a huge hairline triangle around the lyric, or a counter-rotating pair with an orbiting solid */
jzReg('decor', 'triangleSpin', { back: false, build: function (ctx, bb0, d) {
    var bb = dc1_bb(ctx, bb0), W = ctx.W, H = ctx.H, sc = ctx.sc, u = dc1_u(ctx), lw = Math.max(1, 1.4 * u), dir = d.right ? 1 : -1, PR = (d.r || 0) * 120;
    var rin = dc1_len(bb.w / 2, bb.h / 2) + 18 * u, E = 'var e=oe(time/0.6);', S, g1, g2;
    if (d.big && rin * 2 < Math.max(W, H) * 0.62) {           // one big triangle whose edges stay clear of the lyric
        var RB = rin * 2, ROT = E + 'var rot=' + jzN(dir) + '*((1-e)*-70+time*5)+' + jzN(PR) + ';', SCL = 'var s=100*(1+0.08*ic(PO));[s,s]';
        S = dc1_layer(ctx, 'triangle', bb.cx, bb.cy);
        g1 = jzGrp(S, 'inner'); jzAddPath(g1, dc1_tri(RB), false); jzAddStroke(g1, sc.fg, lw, 75);
        dc1_trim(ctx, g1, E + '100*e');
        dc1_gexp(ctx, g1, 'ADBE Vector Rotation', ROT + 'rot'); dc1_gexp(ctx, g1, 'ADBE Vector Scale', SCL);
        g2 = jzGrp(S, 'outer'); jzAddPath(g2, dc1_tri(RB * 1.07), false); jzAddStroke(g2, d.accent ? sc.accent : sc.sub, lw, 45);
        dc1_trim(ctx, g2, '100*oe((time-0.12)/0.6)');
        dc1_gexp(ctx, g2, 'ADBE Vector Rotation', ROT + '-rot*0.6+60'); dc1_gexp(ctx, g2, 'ADBE Vector Scale', SCL);
        dc1_op(ctx, S, '1');
        return;
    }
    var S0 = jzClamp(Math.min(W, H) * 0.19, 110 * u, 230 * u), sp = d.corner ? dc1_corner(ctx, bb, S0, S0, d) : dc1_near(ctx, bb, S0, S0, d, 24 * u), A = sp.ok ? 1 : 0.35;
    var cx = sp.cx, cy = sp.cy + S0 * 0.06, R = S0 * 0.42;
    S = dc1_layer(ctx, 'triangle pair', cx, cy);
    var gd = jzGrp(S, 'centre'); jzAddEllipse(gd, 4.4 * u, 4.4 * u); jzAddFill(gd, sc.accent);
    dc1_gexp(ctx, gd, 'ADBE Vector Group Opacity', '100*oe(time/0.6)');
    g1 = jzGrp(S, 'outer'); jzAddPath(g1, dc1_tri(R), false); jzAddStroke(g1, sc.fg, lw * 1.1);
    dc1_trim(ctx, g1, E + '100*e');
    dc1_gexp(ctx, g1, 'ADBE Vector Rotation', E + jzN(dir) + '*(time*16+(1-e)*-90)+' + jzN(PR));
    g2 = jzGrp(S, 'inner'); jzAddPath(g2, dc1_tri(R * 0.62), false); jzAddStroke(g2, sc.sub, lw, 80);
    dc1_trim(ctx, g2, '100*oe((time-0.1)/0.6)');
    dc1_gexp(ctx, g2, 'ADBE Vector Rotation', E + jzN(-dir) + '*(time*24+(1-e)*-140)+60');
    dc1_op(ctx, S, '1', A);
    // the small solid triangle orbiting the pair (ghosted in the browser -> no jzNoGhost)
    var So = jzShapeLayer(ctx, 'triangle orbit', cx, cy), go = jzGrp(So, 'orbiter');
    jzAddPath(go, dc1_tri(R * 0.13), true); jzAddFill(go, sc.accent);
    var AN = 'var an=(time*' + jzN(50 * dir) + '+' + jzN((d.r || 0) * 360) + ')*Math.PI/180;';
    dc1_gexp(ctx, go, 'ADBE Vector Position', AN + '[Math.cos(an)*' + jzN(R * 1.18) + ',Math.sin(an)*' + jzN(R * 1.18) + ']');
    dc1_gexp(ctx, go, 'ADBE Vector Rotation', 'time*' + jzN(90 * dir));
    dc1_gexp(ctx, go, 'ADBE Vector Scale', 'var x=cl((time-0.25)/0.35),q=x<=0?0:100*ob(x,1.8);[q,q]');
    dc1_op(ctx, So, '1', A);
} });

/* ---- lineBurst \u653E\u5C04\u7DDA: short emphasis dashes radiating from the lyric's outline */
function dc1_superR(a, b, p, th) { var c = Math.abs(Math.cos(th)), s = Math.abs(Math.sin(th)); return 1 / Math.pow(Math.pow(c / a, p) + Math.pow(s / b, p), 1 / p); }
jzReg('decor', 'lineBurst', { back: false, build: function (ctx, bb0, d) {
    var bb = dc1_bb(ctx, bb0), sc = ctx.sc, u = dc1_u(ctx), sd = d.seed, i, j = 1, b;
    var pad = 18 * u + bb.h * 0.14, A = bb.w / 2 * 1.08 + pad, B = bb.h / 2 * 1.12 + pad, M = 180, pts = [], cum = [0];
    for (i = 0; i <= M; i++) {
        var th = i / M * Math.PI * 2, r = dc1_superR(A, B, 4, th); pts.push([Math.cos(th) * r, Math.sin(th) * r]);
        if (i) cum.push(cum[i - 1] + dc1_len(pts[i][0] - pts[i - 1][0], pts[i][1] - pts[i - 1][1]));
    }
    // dashes evenly spaced along the outline, grouped by start delay (4 buckets) and colour
    var L = cum[M], N = Math.round(jzClamp(L / (34 * u), 24, 76) * (0.8 + 0.1 * (d.n | 0))), NBK = 4, sets = [];
    for (i = 0; i < NBK * 2; i++) sets.push([]);
    for (i = 0; i < N; i++) {
        var s = ((i + 0.5 + dc1_rs(sd, i, 1) * 0.3) / N) * L;
        while (j < M && cum[j] < s) j++;
        var k = (s - cum[j - 1]) / Math.max(1e-6, cum[j] - cum[j - 1]);
        var px = pts[j - 1][0] + (pts[j][0] - pts[j - 1][0]) * k, py = pts[j - 1][1] + (pts[j][1] - pts[j - 1][1]) * k;
        var tx = pts[j][0] - pts[j - 1][0], ty = pts[j][1] - pts[j - 1][1], tl = dc1_len(tx, ty) || 1; tx /= tl; ty /= tl;
        var nx = ty, ny = -tx; if (nx * px + ny * py < 0) { nx = -nx; ny = -ny; }
        var len = dc1_rr(10, 30, sd, i, 3) * u * (d.big ? 1.5 : 1) * 1.2, d0 = dc1_r(sd, i, 2) * 8 * u;
        b = Math.min(NBK - 1, Math.floor(dc1_r(sd, i, 5) * NBK)) + (i % 6 === 0 ? NBK : 0);
        sets[b].push([px + nx * d0, py + ny * d0, px + nx * (d0 + len), py + ny * (d0 + len)]);
    }
    var S = dc1_layer(ctx, 'line burst', bb.cx, bb.cy);
    for (b = NBK * 2 - 1; b >= 0; b--) {
        if (!sets[b].length) continue;
        var acc = b >= NBK, g = jzGrp(S, (acc ? 'accent ' : 'dashes ') + ((b % NBK) + 1));
        for (i = 0; i < sets[b].length; i++) dc1_seg(g, sets[b][i][0], sets[b][i][1], sets[b][i][2], sets[b][i][3]);
        dc1_cap(jzAddStroke(g, acc ? sc.accent : sc.fg, acc ? 2.4 * u : 1.6 * u, acc ? 100 : 85));
        dc1_trim(ctx, g, 'var be=0.5+0.5*Math.sin(time*5);100*oe((time-' + jzN(((b % NBK) + 0.5) * 0.18 / NBK) + ')/0.35)*(1+0.1*be)/1.2');
    }
    // exit: the dashes fly outward (browser: +60 px along the normal)
    jzSetExpr(jzXf(S, 'ADBE Scale'), dc1_hd(ctx) + 'var s=1+ic(PO)*' + jzN(60 * u / ((A + B) / 2)) + ';[value[0]*s,value[1]*s]');
    dc1_op(ctx, S, '1');
} });

/* ---- plusGrid \u30D7\u30E9\u30B9\u683C\u5B50: a precise grid of + marks with a scanning highlight */
jzReg('decor', 'plusGrid', { back: false, build: function (ctx, bb0, d) {
    var bb = dc1_bb(ctx, bb0), sc = ctx.sc, u = dc1_u(ctx), v = d.v | 0, i, j;
    var cols = 4 + (d.n | 0) + v % 3, rows = 2 + v % 2 + (d.big ? 1 : 0), s = jzClamp(jzU(ctx) * 0.034, 24 * u, 44 * u), ps = s * 0.22;
    var w = (cols - 1) * s + ps * 2, h = (rows - 1) * s + ps * 2;
    var sp = d.corner ? dc1_corner(ctx, bb, w, h, d) : dc1_near(ctx, bb, w, h, d, 26 * u), A = sp.ok ? 1 : 0.35, NC = cols * rows;
    var HL = 'var hl=Math.floor(time*7+' + jzN((d.r || 0) * 50) + ')%' + NC + ';', S = dc1_layer(ctx, 'plus grid', 0, 0), k = ps * 1.5;
    var gx = jzGrp(S, 'highlight'); dc1_seg(gx, -k, -k, k, k); dc1_seg(gx, -k, k, k, -k); jzAddStroke(gx, sc.accent, 1.8 * u);
    dc1_gexp(ctx, gx, 'ADBE Vector Position', HL + '[' + jzN(sp.x + ps) + '+(hl%' + cols + ')*' + jzN(s) + ',' + jzN(sp.y + ps) + '+Math.floor(hl/' + cols + ')*' + jzN(s) + ']');
    dc1_gexp(ctx, gx, 'ADBE Vector Group Opacity', 'time>0.5?100:0');
    for (j = 0; j < rows; j++) for (i = 0; i < cols; i++) {
        var g = jzGrp(S, 'plus ' + (j * cols + i + 1)); dc1_seg(g, -ps, 0, ps, 0); dc1_seg(g, 0, -ps, 0, ps); jzAddStroke(g, sc.sub, Math.max(1, 1.3 * u));
        dc1_gset(g, 'ADBE Vector Position', [sp.x + ps + i * s, sp.y + ps + j * s]);
        dc1_gexp(ctx, g, 'ADBE Vector Scale', HL + 'var x=cl((time-' + jzN((i + j) * 0.03) + ')/0.3),q=x<=0?0:100*ob(x,2);(hl==' + (j * cols + i) + '&&time>0.5)?[0,0]:[q,q]');
    }
    dc1_op(ctx, S, '1', A);
} });

/* ---- guides \u30AC\u30A4\u30C9\u7DDA: dashed layout guides along the lyric's edges, with handles and readouts */
jzReg('decor', 'guides', { back: false, build: function (ctx, bb0, d) {
    var bb = dc1_bb(ctx, bb0), W = ctx.W, H = ctx.H, sc = ctx.sc, u = dc1_u(ctx), i;
    var pad = 10 * u + bb.h * 0.05, lw = Math.max(1, 1.1 * u), X0 = bb.x0 - pad, X1 = bb.x1 + pad, Y0 = bb.y0 - pad, Y1 = bb.y1 + pad, cx = (X0 + X1) / 2, cy = (Y0 + Y1) / 2;
    var col = d.accent ? sc.accent : (sc.accent2 && jzContrast(sc.accent2, sc.bg) > 2 ? sc.accent2 : sc.accent);
    var vert = d.big || (d.v | 0) % 2 === 1 || bb.h > bb.w, EA = 'oe((time-0.3)/0.3)', hs = 3.5 * u;
    var S = dc1_layer(ctx, 'guides', 0, 0);
    var gh = jzGrp(S, 'handles'), cn = [[X0, Y0], [X1, Y0], [X0, Y1], [X1, Y1]];
    for (i = 0; i < 4; i++) jzAddPath(gh, [[cn[i][0] - hs, cn[i][1] - hs], [cn[i][0] + hs, cn[i][1] - hs], [cn[i][0] + hs, cn[i][1] + hs], [cn[i][0] - hs, cn[i][1] + hs]], true);
    jzAddStroke(gh, sc.fg, lw); dc1_gexp(ctx, gh, 'ADBE Vector Group Opacity', '100*' + EA);
    var gl = jzGrp(S, 'guides'), ys = [Y0, Y1], xs = [X0, X1];
    for (i = 0; i < 2; i++) if (ys[i] > 0 && ys[i] < H) dc1_seg(gl, cx - W * 0.55, ys[i], cx + W * 0.55, ys[i]);
    if (vert) for (i = 0; i < 2; i++) if (xs[i] > 0 && xs[i] < W) dc1_seg(gl, xs[i], cy - H * 0.55, xs[i], cy + H * 0.55);
    var st = jzAddStroke(gl, col, lw, 75); dc1_dash(st, 7 * u, 5 * u);
    dc1_trim(ctx, gl, '50+50*oe(time/0.6)', '50-50*oe(time/0.6)');
    dc1_op(ctx, S, '1');
    var fs = dc1_fs(ctx) * 0.75, m = dc1_mg(ctx) * 0.5;
    dc1_label(ctx, 'Y ' + dc1_p(Y0, 4), m, Y0 - fs * 0.8, { size: fs, color: col, f: EA });
    dc1_label(ctx, 'Y ' + dc1_p(Y1, 4), m, Y1 + fs * 0.9, { size: fs, color: col, f: EA });
    if (vert) dc1_label(ctx, 'X ' + dc1_p(X1, 4), X1 + 6 * u, m + fs * 0.6, { size: fs, color: col, f: EA });
} });

/* ---- waveLine \u6CE2\u7DDA: a long, precise sine hairline pair drifting across the free band
   the wave travels (group position, wrapped per wavelength) inside layer masks: soft ends, draw-on and wipe-off by mask expansion */
jzReg('decor', 'waveLine', { back: false, build: function (ctx, bb0, d) {
    var bb = dc1_bb(ctx, bb0), W = ctx.W, H = ctx.H, sc = ctx.sc, u = dc1_u(ctx), m = dc1_mg(ctx), q, x;
    var above = bb.y0 - m * 0.5, below = H - m * 0.5 - bb.y1, lo = !!d.low;
    if ((lo ? below : above) < 60 * u && (lo ? above : below) > (lo ? below : above)) lo = !lo;
    var room = lo ? below : above, y = lo ? bb.y1 + Math.min(room * 0.5, 40 * u + room * 0.22) : bb.y0 - Math.min(room * 0.5, 40 * u + room * 0.22);
    if (room < 30 * u) return;
    var A = jzClamp(room * 0.1, 5 * u, 14 * u), lam = jzClamp(jzU(ctx) * 0.07, 44 * u, 96 * u), dir = d.right ? 1 : -1, x0 = m, x1 = W - m;
    var dots = (d.v | 0) % 3 === 2;
    var lines = dots ? [[A, 0, sc.fg, 3.6 * u, 85, 0]] : [[A * 0.55, Math.PI * 0.6, d.accent ? sc.accent : sc.sub, Math.max(1, u), 60, 0.12], [A, 0, sc.fg, 1.4 * u, 85, 0]];
    for (q = 0; q < lines.length; q++) {
        var amp = lines[q][0], off = lines[q][1], pts = [];
        for (x = x0 - lam; x <= x1 + 0.1; x += 6 * u) pts.push([x, y + Math.sin((x - x0) / lam * Math.PI * 2 + off) * amp]);
        var S = dc1_layer(ctx, dots ? 'wave dots' : 'wave line', 0, 0), g = jzGrp(S, 'wave');
        jzAddPath(g, pts, false);
        var st = jzAddStroke(g, lines[q][2], lines[q][3], lines[q][4]);
        if (dots) { dc1_cap(st); dc1_dash(st, 0, 12 * u); }
        dc1_gexp(ctx, g, 'ADBE Vector Position', 'var s=-time*' + jzN(2.4 * dir * lam / (2 * Math.PI)) + ';s-=Math.floor(s/' + jzN(lam) + ')*' + jzN(lam) + ';[s,0]');
        dc1_mask(S, x0 + lam * 0.6, y - lam * 2, x1 - lam * 0.6, y + lam * 2, null, [lam * 1.2, 0]);
        dc1_mask(S, x0 - 10, y - lam * 2, x0, y + lam * 2, MaskMode.INTERSECT, 0, dc1_hd(ctx) + jzN(x1 - x0) + '*ioc((time-' + jzN(lines[q][5]) + ')/0.7)');
        dc1_mask(S, x0 - 10, y - lam * 2, x0, y + lam * 2, MaskMode.SUBTRACT, 0, dc1_hd(ctx) + jzN(x1 - x0) + '*ic(PO)');
        dc1_op(ctx, S, '1');
    }
} });

/* ---- spiralLine \u6E26\u5DFB\u304D: an Archimedean or square spiral drawn on from its centre */
jzReg('decor', 'spiralLine', { back: false, build: function (ctx, bb0, d) {
    var bb = dc1_bb(ctx, bb0), sc = ctx.sc, u = dc1_u(ctx), R = jzClamp(jzU(ctx) * 0.085, 54 * u, 104 * u), k, j = 1;
    var sp = dc1_near(ctx, bb, R * 2.3, R * 2.3, d, 20 * u), A = sp.ok ? 1 : 0.35, turns = 3 + (d.n | 0) * 0.5, pts = [], sq = (d.v | 0) % 2 === 1;
    if (sq) {
        var n = Math.round(turns * 4), dd = R / (n / 2), x = 0, y = 0, DR = [[1, 0], [0, 1], [-1, 0], [0, -1]];
        pts.push([0, 0]);
        for (k = 0; k < n; k++) { var Lk = dd * (Math.floor(k / 2) + 1); x += DR[k % 4][0] * Lk; y += DR[k % 4][1] * Lk; pts.push([x, y]); }
    } else {
        var M = Math.round(turns * 40);
        for (k = 0; k <= M; k++) { var th = k / M * turns * Math.PI * 2, r = R * k / M; pts.push([Math.cos(th) * r, Math.sin(th) * r]); }
    }
    // arc-length table so the head dot rides the drawing end
    var cum = [0], tab = [], NT = 40;
    for (k = 1; k < pts.length; k++) cum.push(cum[k - 1] + dc1_len(pts[k][0] - pts[k - 1][0], pts[k][1] - pts[k - 1][1]));
    var Lt = cum[cum.length - 1];
    for (k = 0; k <= NT; k++) {
        var sv = Lt * k / NT; while (j < pts.length - 1 && cum[j] < sv) j++;
        var f = jzClamp((sv - cum[j - 1]) / Math.max(1e-6, cum[j] - cum[j - 1]), 0, 1);
        tab.push('[' + jzN(pts[j - 1][0] + (pts[j][0] - pts[j - 1][0]) * f) + ',' + jzN(pts[j - 1][1] + (pts[j][1] - pts[j - 1][1]) * f) + ']');
    }
    var S = dc1_layer(ctx, 'spiral', sp.cx, sp.cy), g = jzGrp(S, 'spiral');
    dc1_gexp(ctx, g, 'ADBE Vector Rotation', 'time*' + jzN(18 * (d.right ? 1 : -1)) + '+' + jzN((d.r || 0) * 360));
    var gd = dc1_sub(g, 'head'); jzAddEllipse(gd, 6.4 * u, 6.4 * u); jzAddFill(gd, sc.accent);
    dc1_gexp(ctx, gd, 'ADBE Vector Position', 'var e=ioc(time/0.7),P=[' + tab.join(',') + '],f=e*' + NT + ',i=Math.min(' + (NT - 1) + ',Math.floor(f)),k=f-i;[P[i][0]+(P[i+1][0]-P[i][0])*k,P[i][1]+(P[i+1][1]-P[i][1])*k]');
    dc1_gexp(ctx, gd, 'ADBE Vector Group Opacity', 'ioc(time/0.7)>ic(PO)?100:0');
    var gl = dc1_sub(g, 'line'); jzAddPath(gl, pts, false); dc1_cap(jzAddStroke(gl, sc.fg, 1.4 * u, 85));
    dc1_trim(ctx, gl, '100*ioc(time/0.7)', '100*Math.min(ic(PO),ioc(time/0.7))');
    dc1_op(ctx, S, '1', A);
} });

/* ---- halftonePatch \u7DB2\u70B9: a halftone (dot or line screen) gradient bleeding from a screen corner, under the lyric
   AE-native screen: a luma matte solid = one radial (dot) / mirrored linear (line) cell -> Motion Tile -> + corner falloff ramp (50% blend)
   -> Threshold, so the dot radius / line weight follows the falloff; a colour solid below takes it as LUMA matte and grows in by mask */
jzReg('decor', 'halftonePatch', { back: true, build: function (ctx, bb0, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, u = dc1_u(ctx), sd = d.seed, dur = ctx.comp.duration;
    var sx = d.right ? 1 : -1, sy = d.low ? 1 : -1, ox = sx > 0 ? W : 0, oy = sy > 0 ? H : 0;
    var pw = W * dc1_rr(0.38, 0.58, sd, 1), ph = H * dc1_rr(0.38, 0.6, sd, 2), g = jzClamp(jzU(ctx) * 0.018, 12 * u, 22 * u);
    while ((pw / g) * (ph / g) > 1100) g *= 1.15;
    var col = d.accent ? sc.accent : sc.sub, al = (d.accent ? 0.3 : 0.2) * (jzLum(sc.bg) < 0.5 ? 1 : 0.8), lines = (d.v | 0) % 2 === 1;
    var R = Math.sqrt(pw * ph), bx = ox - sx * g * 0.5, by = oy - sy * g * 0.5;
    // colour (below): a soft pw x ph ellipse at the corner, scaled out of the corner with the browser's growth front (d < 1.3 * t / 0.7)
    var C = ctx.comp.layers.addSolid(jzHex(col), 'halftone', W, H, 1, dur), mk = C.property('ADBE Mask Parade').addProperty('ADBE Mask Atom'), es = new Shape(), K4 = 0.5523;
    es.vertices = [[ox, oy - ph], [ox + pw, oy], [ox, oy + ph], [ox - pw, oy]];
    es.inTangents = [[-pw * K4, 0], [0, -ph * K4], [pw * K4, 0], [0, ph * K4]]; es.outTangents = [[pw * K4, 0], [0, ph * K4], [-pw * K4, 0], [0, -ph * K4]]; es.closed = true;
    mk.property('ADBE Mask Shape').setValue(es);
    mk.property('ADBE Mask Feather').setValue([R * 0.25, R * 0.25]);
    jzXf(C, 'ADBE Anchor Point').setValue([ox, oy]); jzXf(C, 'ADBE Position').setValue([ox, oy]);
    jzSetExpr(jzXf(C, 'ADBE Scale'), dc1_hd(ctx) + 'var s=100*Math.max(0,cl(time/0.7)*1.3-0.1);[s,s]');
    jzNoGhost(C); dc1_op(ctx, C, '1', al);
    // screen (above, used as luma matte)
    var Mt = ctx.comp.layers.addSolid([0, 0, 0], 'halftone screen', W, H, 1, dur), r1, tl;
    r1 = jzEffect(Mt, 'ADBE Ramp', 'JZ Cell');
    if (lines) { jzEP(r1, 1, [bx, by]); jzEP(r1, 2, [1, 1, 1]); jzEP(r1, 3, [bx, by + g * 0.5]); jzEP(r1, 4, [0, 0, 0]); jzEP(r1, 5, 1); }
    else { jzEP(r1, 1, [bx, by]); jzEP(r1, 2, [1, 1, 1]); jzEP(r1, 3, [bx + g * 0.5, by]); jzEP(r1, 4, [0, 0, 0]); jzEP(r1, 5, 2); }
    if (lines) { var mr = jzEffect(Mt, 'ADBE Mirror', 'JZ Cell Mirror'); jzEP(mr, 1, [bx, by]); jzEP(mr, 2, 270); }
    tl = jzEffect(Mt, 'ADBE Tile', 'JZ Screen');
    jzEP(tl, 1, [bx, by]); jzEP(tl, 2, lines ? 100 : g / W * 100); jzEP(tl, 3, g / H * 100);
    if (!lines) { jzEP(tl, 7, 180); jzEP(tl, 8, 1); }          // alternate rows shifted by half a cell, like the browser
    // falloff: dots use a round ramp (radius = geometric mean of pw, ph); the line screen uses radius ph and the layer is
    // stretched horizontally around the corner to pw (horizontal lines stay horizontal), max line weight 92% like the browser
    var r2 = jzEffect(Mt, 'ADBE Ramp', 'JZ Falloff'), RF = lines ? ph : R, top = lines ? 0.92 : 1;
    jzEP(r2, 1, [ox, oy]); jzEP(r2, 2, [top, top, top]); jzEP(r2, 3, [ox - sx * RF, oy]); jzEP(r2, 4, [0, 0, 0]); jzEP(r2, 5, 2); jzEP(r2, 7, 0.5);
    var th = jzEffect(Mt, 'ADBE Threshold2', 'JZ Dots'); jzEP(th, 1, 128);
    if (lines) { jzXf(Mt, 'ADBE Anchor Point').setValue([ox, oy]); jzXf(Mt, 'ADBE Position').setValue([ox, oy]); jzXf(Mt, 'ADBE Scale').setValue([pw / ph * 100, 100]); }
    jzNoGhost(Mt);
    C.trackMatteType = TrackMatteType.LUMA;
} });

/* ---- checkerStrip \u5E02\u677E: a small checkerboard band that wipes open and scrolls */
jzReg('decor', 'checkerStrip', { back: false, build: function (ctx, bb0, d) {
    var bb = dc1_bb(ctx, bb0), W = ctx.W, sc = ctx.sc, u = dc1_u(ctx), v = (d.v | 0) % 3, rows = v === 1 ? 3 : (v === 2 ? 1 : 2), i, j;
    var c = jzClamp(jzU(ctx) * 0.016, 10 * u, 18 * u), L = Math.min(W * 0.32, 400 * u), h = rows * c;
    var sp = dc1_corner(ctx, bb, L, h + (v === 2 ? 10 * u : 0), d, 1), A = sp.ok ? 1 : 0.35, x0 = sp.x, y0 = sp.y + (v === 2 ? 5 * u : 0);
    var S = dc1_layer(ctx, 'checker strip', 0, 0);
    if (v === 2) { var gr = jzGrp(S, 'rules'); dc1_seg(gr, x0, y0 - 5 * u, x0 + L, y0 - 5 * u); dc1_seg(gr, x0, y0 + h + 5 * u, x0 + L, y0 + h + 5 * u); jzAddStroke(gr, sc.fg, Math.max(1, u), 80); }
    var g = jzGrp(S, 'cells');
    for (j = 0; j < rows; j++) for (i = -2; i * c < L + 2 * c; i++) {
        if ((i + j) % 2 !== 0) continue;
        var cw = c * (v === 1 ? jzClamp(1 - (i * c) / L * 0.95, 0, 1) : 1);
        if (cw > 0.01) jzAddRect(g, cw, cw, 0, x0 + i * c + c / 2, y0 + j * c + c / 2);
    }
    jzAddFill(g, d.accent ? sc.accent : sc.fg, 90);
    dc1_gexp(ctx, g, 'ADBE Vector Position', '[(time*' + jzN(c * 1.6 * (d.right ? -1 : 1)) + ')%' + jzN(2 * c) + ',0]');
    // window: static strip clip + a growing intersect mask (the browser's clip to x0 .. x0 + L * e)
    dc1_mask(S, x0, y0 - 10 * u, x0 + L, y0 + h + 10 * u, null, 0);
    dc1_mask(S, x0 - 10, y0 - 10 * u, x0, y0 + h + 10 * u, MaskMode.INTERSECT, 0, dc1_hd(ctx) + jzN(L) + '*oe(time/0.55)');
    dc1_op(ctx, S, '1', A);
} });

// ================================================================ decor part 2 (AE port of src/11p_decor.js: rhythm & orbit, particles & light,
// hand-drawn marks, type ornaments). decor.build(ctx, bb, d): layers go into the cut's content comp (time 0 = cut start).
// Everything the browser draws on the main pass only is kept out of the ghosts (jzNoGhost). Particles are ONE shape layer each:
// one group per particle, its motion baked into a short Position / Scale / Rotation expression (per-index constants come from
// the browser's own hash, so placements match the web frame).

// ---------------------------------------------------------------- shared helpers (prefix dc2_)
// the browser's deterministic hash J.h / J.r / J.rs / J.rr and value noise J.noise1
function dc2_h(a, b, c, d, e) {
    var h = 0x9e3779b9 ^ (a | 0);
    h = jzImul(h ^ (h >>> 16), 0x85ebca6b);
    h = (h + jzImul((b | 0) + 0x632be5ab, 0xc2b2ae35)) | 0;
    h = jzImul(h ^ (h >>> 13), 0xc2b2ae35);
    h = (h + jzImul((c | 0) + 0x5bd1e995, 0x27d4eb2f)) | 0;
    h = jzImul(h ^ (h >>> 15), 0x165667b1);
    h = (h + jzImul((d | 0) + 0x1b873593, 0x85ebca6b)) | 0;
    h = jzImul(h ^ (h >>> 16), 0x27d4eb2f);
    h = (h + jzImul((e | 0) + 0x68e31da4, 0x9e3779b1)) | 0;
    h ^= h >>> 15; h = jzImul(h, 0x2c1b3c6d); h ^= h >>> 12; h = jzImul(h, 0x297a2d39); h ^= h >>> 15;
    return h >>> 0;
}
function dc2_r(a, b, c, d, e) { return dc2_h(a, b, c, d, e) / 4294967296; }
function dc2_rs(a, b, c, d, e) { return dc2_r(a, b, c, d, e) * 2 - 1; }
function dc2_rr(lo, hi, a, b, c, d, e) { return lo + (hi - lo) * dc2_r(a, b, c, d, e); }
function dc2_noise(x, seed) { var i = Math.floor(x), f = x - i, u = f * f * (3 - 2 * f), a = dc2_rs(seed, i), b = dc2_rs(seed, i + 1); return a + (b - a) * u; }
// browser sizes: u = 1 at a 1080 px short side, MG = safe margin, FS = small label size
function dc2_u(ctx) { return jzU(ctx) / 1080; }
function dc2_mg(ctx) { return Math.round(jzU(ctx) * 0.05); }
function dc2_fs(ctx) { return Math.max(12 * ctx.u, 16 * dc2_u(ctx)); }
function dc2_dark(ctx) { return jzLum(ctx.sc.bg) < 0.5; }
function dc2_ioc(x) { x = jzClamp(x, 0, 1); return x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2; }
// lyric bbox as the browser's getBB (clamped a little outside the frame)
function dc2_bb(ctx, bb) {
    var W = ctx.W, H = ctx.H;
    if (!bb || !(bb.x1 > bb.x0) || !(bb.y1 > bb.y0)) bb = { x0: W * 0.35, x1: W * 0.65, y0: H * 0.4, y1: H * 0.6 };
    var b = { x0: Math.max(bb.x0, -W * 0.1), x1: Math.min(bb.x1, W * 1.1), y0: Math.max(bb.y0, -H * 0.1), y1: Math.min(bb.y1, H * 1.1) };
    if (b.x1 <= b.x0) { b.x0 = bb.x0; b.x1 = bb.x1; }
    if (b.y1 <= b.y0) { b.y0 = bb.y0; b.y1 = bb.y1; }
    b.cx = (b.x0 + b.x1) / 2; b.cy = (b.y0 + b.y1) / 2; b.w = b.x1 - b.x0; b.h = b.y1 - b.y0;
    return b;
}
function dc2_hit(x0, y0, x1, y1, bb, pad) { pad = pad || 0; return !(x1 < bb.x0 - pad || x0 > bb.x1 + pad || y1 < bb.y0 - pad || y0 > bb.y1 + pad); }
// a w\u00D7h box next to the lyric (browser nearBB) / a free screen corner (cornerSpot): { x, y, cx, cy, ok, sx, sy }
function dc2_corner(ctx, bb, w, h, P, mk) {
    var W = ctx.W, H = ctx.H, m = dc2_mg(ctx) * (mk || 1), sx0 = P.right ? 1 : -1, sy0 = P.low ? 1 : -1, best = null, bestA = 1e18, i;
    var order = [[sx0, sy0], [-sx0, sy0], [sx0, -sy0], [-sx0, -sy0]];
    for (i = 0; i < 4; i++) {
        var sx = order[i][0], sy = order[i][1], X = sx > 0 ? W - m - w : m, Y = sy > 0 ? H - m - h : m;
        if (!dc2_hit(X, Y, X + w, Y + h, bb, 8)) return { x: X, y: Y, cx: X + w / 2, cy: Y + h / 2, ok: true, sx: sx, sy: sy };
        var ov = Math.max(0, Math.min(X + w, bb.x1) - Math.max(X, bb.x0)) * Math.max(0, Math.min(Y + h, bb.y1) - Math.max(Y, bb.y0));
        if (ov < bestA) { bestA = ov; best = { x: X, y: Y, cx: X + w / 2, cy: Y + h / 2, ok: false, sx: sx, sy: sy }; }
    }
    return best;
}
function dc2_near(ctx, bb, w, h, P, gap) {
    var W = ctx.W, H = ctx.H, m = dc2_mg(ctx) * 0.8, sx0 = P.right ? 1 : -1, sy0 = P.low ? 1 : -1, tries = [], i;
    var order = P.corner ? [[sx0, sy0], [-sx0, sy0], [sx0, -sy0], [-sx0, -sy0]] : [[sx0, sy0], [sx0, -sy0], [-sx0, sy0], [-sx0, -sy0]];
    for (i = 0; i < 4; i++) {
        var sx = order[i][0], sy = order[i][1];
        var ax = sx > 0 ? bb.x1 - w : bb.x0, ox = sx > 0 ? bb.x1 + gap : bb.x0 - gap - w;
        var ay = sy > 0 ? bb.y1 + gap : bb.y0 - gap - h, iy = sy > 0 ? bb.y1 - h : bb.y0;
        if ((P.v | 0) % 2) tries.push([ox, iy, sx, sy], [ax, ay, sx, sy], [ox, ay, sx, sy]);
        else tries.push([ax, ay, sx, sy], [ox, iy, sx, sy], [ox, ay, sx, sy]);
    }
    for (i = 0; i < tries.length; i++) {
        var X = jzClamp(tries[i][0], m, Math.max(m, W - m - w)), Y = jzClamp(tries[i][1], m, Math.max(m, H - m - h));
        if (!dc2_hit(X, Y, X + w, Y + h, bb, gap * 0.4)) return { x: X, y: Y, cx: X + w / 2, cy: Y + h / 2, ok: true, sx: tries[i][2], sy: tries[i][3] };
    }
    return dc2_corner(ctx, bb, w, h, P, 1);
}
function dc2_clear(bb, x, y, pad, soft) {
    var dx = Math.max(bb.x0 - pad - x, 0, x - bb.x1 - pad), dy = Math.max(bb.y0 - pad - y, 0, y - bb.y1 - pad);
    return jzClamp(Math.sqrt(dx * dx + dy * dy) / soft, 0, 1);
}
// point at distance t along the rectangle's perimeter (clockwise from the top-left corner)
function dc2_perim(X0, Y0, X1, Y1, t) {
    if (t < X1 - X0) return [X0 + t, Y0];
    t -= X1 - X0; if (t < Y1 - Y0) return [X1, Y0 + t];
    t -= Y1 - Y0; if (t < X1 - X0) return [X1 - t, Y1];
    t -= X1 - X0; return [X0, Y1 - t];
}
// a character of the lyric: prefer kanji, then kana / latin (browser lyricChar)
function dc2_lyricChar(ctx, salt) {
    var c = ctx.cut, a = jzChars(String(c.text || c.lineText || '')), arr = [], kan = [], pool = [], i;
    for (i = 0; i < a.length; i++) if (jzTrim(a[i]) && !jzIsPunct(a[i])) arr.push(a[i]);
    if (!arr.length) return '';
    for (i = 0; i < arr.length; i++) if (jzIsKanji(arr[i])) kan.push(arr[i]);
    if (kan.length) pool = kan;
    else for (i = 0; i < arr.length; i++) if (jzIsKata(arr[i]) || jzIsHira(arr[i]) || /[A-Za-z]/.test(arr[i])) pool.push(arr[i]);
    var p = pool.length ? pool : arr;
    return p[dc2_h(c.seed, salt | 0, 5) % p.length];
}
// ---- expression snippets (kept short: many particle groups carry one each)
var DC2_CL = 'function cl(x){return Math.max(0,Math.min(1,x));}';
var DC2_OC = 'function oc(x){x=cl(x);return 1-Math.pow(1-x,3);}';
var DC2_OE = 'function oe(x){x=cl(x);return x>=1?1:1-Math.pow(2,-10*x);}';
var DC2_IC = 'function ic(x){x=cl(x);return x*x*x;}';
var DC2_OB = 'function ob(x,s){x=cl(x);var c=s+1;return 1+c*Math.pow(x-1,3)+s*Math.pow(x-1,2);}';
var DC2_IOC = 'function ioc(x){x=cl(x);return x<0.5?4*x*x*x:1-Math.pow(-2*x+2,3)/2;}';
var DC2_WR = 'function wr(v,lo,sp){return lo+(((v-lo)%sp)+sp)%sp;}';
var DC2_HH = 'function hh(n){var x=Math.sin(n*127.1+311.7)*43758.5453;return x-Math.floor(x);}';
// light header: T = time, PO = exit progress (same timing as jzTH) + the listed helper functions
function dc2_hd(ctx, fns) {
    var c = ctx.cut, hard = c.outDur != null && c.outDur <= 0, od = hard ? 0.05 : Math.max(0.12, c.outDur || 0.15);
    return 'var T=time,OS=' + jzN(hard ? c.dur : c.dur - od) + ',OD=' + jzN(od) + ';' + DC2_CL + (fns || '') + 'var PO=cl((T-OS)/OD);';
}
// layer opacity: value \u00D7 exit fade K \u00D7 optional factor (JZ_FNS names available)
function dc2_op(ctx, L, mul, val) {
    if (val != null) jzXf(L, 'ADBE Opacity').setValue(jzClamp(val, 0, 100));
    jzSetExpr(jzXf(L, 'ADBE Opacity'), jzTH(ctx) + 'value*K' + (mul ? '*(' + mul + ')' : ''));
}
function dc2_sub(g, name) { var q = jzVecs(g).addProperty('ADBE Vector Group'); if (name) q.name = name; return q; }
function dc2_gx(g, mn, ex) { jzSetExpr(jzGX(g).property(mn), ex); }
function dc2_stroke(g, col, w, op, round) {
    var s = jzAddStroke(g, col, w, op);
    if (round) { try { s.property('ADBE Vector Stroke Line Cap').setValue(2); s.property('ADBE Vector Stroke Line Join').setValue(2); } catch (e) {} }
    return s;
}
function dc2_dash(st, d, gp) {   // AE starts with an empty Dashes group; the preview model has fixed children
    // add both entries first, THEN fetch them: adding the gap invalidates a reference held to the dash
    var D = st.property('ADBE Vector Stroke Dashes'), mn = ['ADBE Vector Stroke Dash 1', 'ADBE Vector Stroke Gap 1'], out = [], i, p;
    for (i = 0; i < 2; i++) { try { D.addProperty(mn[i]); } catch (e) {} }
    for (i = 0; i < 2; i++) { p = null; try { p = D.property(mn[i]); } catch (e2) { p = null; } out.push(p); }
    return out;
}
// subtract a rectangle (comp px, layer at 0,0) \u2014 front particles / orbits never cover the lyric
function dc2_hole(L, x0, y0, x1, y1, feather) {
    var m = L.property('ADBE Mask Parade').addProperty('ADBE Mask Atom'), sh = new Shape();
    sh.vertices = [[x1, y0], [x1, y1], [x0, y1], [x0, y0]]; sh.closed = true;
    m.property('ADBE Mask Shape').setValue(sh);
    try { m.maskMode = MaskMode.SUBTRACT; } catch (e) {}
    if (feather) m.property('ADBE Mask Feather').setValue([feather, feather]);
    return m;
}
function dc2_pts(list, k) { var o = [], i; for (i = 0; i < list.length; i++) o.push([list[i][0] * k, list[i][1] * k]); return o; }
function dc2_star5(R, r) { var o = [], i; for (i = 0; i < 10; i++) { var a = (-90 + i * 36) * Math.PI / 180, q = i % 2 ? r : R; o.push([Math.cos(a) * q, Math.sin(a) * q]); } return o; }
function dc2_heart(s) {
    var o = [], i;
    for (i = 0; i < 28; i++) { var t = i / 28 * Math.PI * 2, x = 16 * Math.pow(Math.sin(t), 3), y = 13 * Math.cos(t) - 5 * Math.cos(2 * t) - 2 * Math.cos(3 * t) - Math.cos(4 * t); o.push([x * s / 17, -y * s / 17]); }
    return o;
}
function dc2_glint(R, w) {      // 4-point concave star
    var o = [], out = [], i;
    for (i = 0; i < 8; i++) { var a = i * Math.PI / 4, q = i % 2 ? w : R; o.push([Math.cos(a) * q, Math.sin(a) * q]); }
    for (i = 0; i < 8; i++) { var p = o[i], n = o[(i + 1) % 8]; out.push(p); out.push([(p[0] + n[0]) / 2 * 0.65, (p[1] + n[1]) / 2 * 0.65]); }
    return out;
}
// the lyric's text lines (or columns) as boxes, from the text layers the layout built inside the bbox
function dc2_lines(ctx, bb, vert) {
    var comp = ctx.comp, boxes = [], lines = [], i, j;
    for (i = 1; i <= comp.numLayers; i++) {
        var L = comp.layer(i), tp = null;
        if (L.matchName !== 'ADBE Text Layer') continue;
        try { tp = L.property('ADBE Text Properties'); } catch (e0) { tp = null; }
        if (!tp) continue;
        var txt = '', just = 0;
        try { var td = tp.property('ADBE Text Document').value; txt = String(td.text); just = td.justification; } catch (e1) { continue; }
        var r = jzRect(L), a = jzXf(L, 'ADBE Anchor Point').value, p = jzXf(L, 'ADBE Position').value, s = jzXf(L, 'ADBE Scale').value;
        var x0 = p[0] + (r.left - a[0]) * s[0] / 100, y0 = p[1] + (r.top - a[1]) * s[1] / 100, w = r.width * s[0] / 100, h = r.height * s[1] / 100;
        var cx = x0 + w / 2, cy = y0 + h / 2;
        if (cx < bb.x0 || cx > bb.x1 || cy < bb.y0 || cy > bb.y1 || w * h < (bb.x1 - bb.x0) * (bb.y1 - bb.y0) * 0.04) continue;
        var rows = txt.split(/\r\n|\r|\n/), n = rows.length, mx = 1;
        if (vert || n < 2) { boxes.push({ x0: x0, x1: x0 + w, y0: y0, y1: y0 + h }); continue; }
        for (j = 0; j < n; j++) mx = Math.max(mx, jzCount(rows[j]));
        for (j = 0; j < n; j++) {
            var lw = w * jzCount(rows[j]) / mx, lx = just === ParagraphJustification.LEFT_JUSTIFY ? x0 : just === ParagraphJustification.RIGHT_JUSTIFY ? x0 + w - lw : cx - lw / 2;
            if (lw > 0) boxes.push({ x0: lx, x1: lx + lw, y0: y0 + h * j / n, y1: y0 + h * (j + 1) / n });
        }
    }
    for (i = 0; i < boxes.length; i++) {       // group into lines by centre (as the browser groups glyph boxes)
        var b = boxes[i], key = vert ? (b.x0 + b.x1) / 2 : (b.y0 + b.y1) / 2, sz = vert ? b.x1 - b.x0 : b.y1 - b.y0, Ln = null;
        for (j = 0; j < lines.length; j++) if (Math.abs(lines[j].k - key) < sz * 0.4) { Ln = lines[j]; break; }
        if (!Ln) lines.push({ k: key, x0: b.x0, x1: b.x1, y0: b.y0, y1: b.y1 });
        else { Ln.x0 = Math.min(Ln.x0, b.x0); Ln.x1 = Math.max(Ln.x1, b.x1); Ln.y0 = Math.min(Ln.y0, b.y0); Ln.y1 = Math.max(Ln.y1, b.y1); }
    }
    lines.sort(function (p, q) { return vert ? q.k - p.k : p.k - q.k; });
    if (!lines.length) lines = [{ x0: bb.x0, x1: bb.x1, y0: bb.y0, y1: bb.y1 }];
    return lines;
}

// ================================================================ rhythm / geometry

/* ---- beatRing \u2014 \u62CD\u306E\u8F2A: rings breathing out from the centre on the beat. AE has no beat data: the beat is the cut's own rhythm
   (\u22480.5 s, snapped so a whole number of beats fills the cut \u2014 cuts are beat-snapped when a BPM is set). */
jzReg('decor', 'beatRing', {
    back: true,
    build: function (ctx, bb, d) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, u = dc2_u(ctx), c = ctx.cut, k;
        var len = jzClamp(c.dur / Math.max(1, Math.round(c.dur / 0.5)), 0.3, 0.9);
        var r0 = Math.min(W, H) * 0.2, r1 = Math.sqrt(W * W + H * H) * 0.55;
        var col = d.accent ? sc.accent : sc.sub, base = d.accent ? 0.45 : 0.4, dashed = (d.v | 0) % 2 === 1;
        var S = jzShapeLayer(ctx, 'beat ring', W / 2, H / 2);
        var hd = 'var LEN=' + jzN(len) + ',sn=((time%LEN)+LEN)%LEN;' + DC2_CL + DC2_OC;
        for (k = 0; k < 3; k++) {
            var g = jzGrp(S, 'ring ' + (k + 1)), hk = hd + 'var p=(sn+' + k + '*LEN)/(LEN*3),r=' + jzN(r0) + '+' + jzN(r1 - r0) + '*oc(p);';
            var e = jzAddEllipse(g, r0 * 2, r0 * 2);
            jzSetExpr(e.property('ADBE Vector Ellipse Size'), hk + '[2*r,2*r]');
            var st = jzAddStroke(g, col, 2 * u, 100);
            jzSetExpr(st.property('ADBE Vector Stroke Opacity'), hk + jzN(base * 100) + '*Math.pow(Math.max(0,1-p),1.6)');
            if (dashed) {       // 48 dashes of 4\u00B0 every 7.5\u00B0
                st.property('ADBE Vector Stroke Width').setValue(1.4 * u);
                var dg = dc2_dash(st, 10, 10);
                if (dg[0]) jzSetExpr(dg[0], hk + 'r*' + jzN(Math.PI * 2 * 4 / 360));
                if (dg[1]) jzSetExpr(dg[1], hk + 'r*' + jzN(Math.PI * 2 * 3.5 / 360));
            } else jzSetExpr(st.property('ADBE Vector Stroke Width'), hk + jzN(u) + '*(1+1.5*(1-p))');
        }
        var gc = jzGrp(S, 'pulse'), ec = jzAddEllipse(gc, r0 * 2, r0 * 2);
        jzSetExpr(ec.property('ADBE Vector Ellipse Size'), hd + 'var pl=Math.exp(-sn*9),r=' + jzN(r0) + '*(1+0.03*pl);[2*r,2*r]');
        var sp = jzAddStroke(gc, col, u, base * 60);      // after the ellipse is configured (the stroke invalidates ec)
        jzSetExpr(sp.property('ADBE Vector Stroke Width'), hd + jzN(u) + '*(1+2.2*Math.exp(-sn*9))');
        jzNoGhost(S);
        dc2_op(ctx, S, 'oc(time/0.4)');
    }
});

/* ---- orbitDots \u2014 \u5468\u56DE\u3059\u308B\u70B9: a tilted orbit around the lyric drawn on, 2-4 dots with fading trails; the lyric hides whatever passes behind it */
jzReg('decor', 'orbitDots', {
    back: false,
    build: function (ctx, bb0, d) {
        var bb = dc2_bb(ctx, bb0), W = ctx.W, H = ctx.H, sc = ctx.sc, u = dc2_u(ctx), i, k;
        var cx = bb.cx, cy = bb.cy, bw = bb.w, bh = bb.h, tall = bh > bw * 1.2, r = d.r == null ? 0.5 : d.r;
        var rx = tall ? Math.max(bw * 0.9, 50 * u) : Math.min(bw / 2 + 50 * u + bw * 0.08, W * 0.47);
        var ry = tall ? Math.min(bh / 2 + 50 * u + bh * 0.08, H * 0.47) : Math.max(bh * 0.9, 50 * u);
        var tilt = (r - 0.5) * 22 * Math.PI / 180, ct = Math.cos(tilt), st = Math.sin(tilt), th0 = r * Math.PI * 2;
        var S = jzShapeLayer(ctx, 'orbit', 0, 0), pts = [];
        // dots first (the first group is drawn on top), orbit hairline last
        var nd = 2 + (d.n | 0) % 3, dir = d.right ? 1 : -1;
        var PT = 'function pt(th){var x=Math.cos(th)*' + jzN(rx) + ',y=Math.sin(th)*' + jzN(ry) + ';return [' + jzN(cx) + '+x*' + jzN(ct) + '-y*' + jzN(st) + ',' + jzN(cy) + '+x*' + jzN(st) + '+y*' + jzN(ct) + '];}';
        var HD = dc2_hd(ctx, DC2_IC + DC2_IOC) + PT + 'var E=ioc(T/0.7);';
        for (k = 0; k < nd; k++) {
            var w = (0.35 + k * 0.22) * dir, sz = (k === 0 ? 5 : 3.2) * u, col = k === 0 ? sc.accent : sc.fg;
            var hk = HD + 'var th=' + jzN(th0 + k * Math.PI * 2 / nd) + '+T*' + jzN(w) + '*(1+2*ic(PO));';
            var gd = jzGrp(S, 'dot ' + (k + 1)), gh = dc2_sub(gd, 'head');
            jzAddEllipse(gh, sz * 2, sz * 2); jzAddFill(gh, col);
            dc2_gx(gh, 'ADBE Vector Position', hk + 'pt(th)');
            dc2_gx(gh, 'ADBE Vector Scale', hk + '[100*E,100*E]');
            for (i = 0; i < 5; i++) {      // trail: 5 short round-capped pieces, thinner and fainter behind the dot
                var f = 1 - (2 * i + 0.5) / 10, a1 = i * 0.09 * (w > 0 ? 1 : -1), a2 = (i + 1) * 0.09 * (w > 0 ? 1 : -1);
                var gt = dc2_sub(gd, 'trail ' + (i + 1)), L0 = 10;
                jzAddPath(gt, [[-L0 / 2, 0], [L0 / 2, 0]], false); dc2_stroke(gt, col, sz * 0.8 * f, 60 * f, true);
                var hs = hk + 'var p1=pt(th-(' + jzN(a1) + ')),p2=pt(th-(' + jzN(a2) + ')),dx=p2[0]-p1[0],dy=p2[1]-p1[1];';
                dc2_gx(gt, 'ADBE Vector Position', hs + '[(p1[0]+p2[0])/2,(p1[1]+p2[1])/2]');
                dc2_gx(gt, 'ADBE Vector Rotation', hs + 'Math.atan2(dy,dx)*180/Math.PI');
                dc2_gx(gt, 'ADBE Vector Scale', hs + '[Math.sqrt(dx*dx+dy*dy)/' + L0 + '*100,100]');
                dc2_gx(gt, 'ADBE Vector Group Opacity', hk + '100*E');
            }
        }
        for (i = 0; i <= 160; i++) { var th = th0 + i / 160 * Math.PI * 2, x = Math.cos(th) * rx, y = Math.sin(th) * ry; pts.push([cx + x * ct - y * st, cy + x * st + y * ct]); }
        var go = jzGrp(S, 'orbit');
        jzAddPath(go, pts, false); jzAddStroke(go, sc.sub, Math.max(1, u), 55);
        jzAddTrimPaths(go, DC2_CL + DC2_IOC + '100*ioc(time/0.7)');
        var pad = 8 * u; dc2_hole(S, bb.x0 - pad, bb.y0 - pad, bb.x1 + pad, bb.y1 + pad, 0);
        jzNoGhost(S);
        dc2_op(ctx, S);
    }
});

/* ---- constellation \u2014 \u661F\u5EA7: stars joined by hairlines, drawn in sequence, beside the lyric (or in a free corner) */
jzReg('decor', 'constellation', {
    back: false,
    build: function (ctx, bb0, d) {
        var bb = dc2_bb(ctx, bb0), sc = ctx.sc, u = dc2_u(ctx), s = d.seed, i, j;
        var w = jzClamp(jzU(ctx) * 0.3, 190 * u, 340 * u), h = w * 0.62;
        var sp = d.corner ? dc2_corner(ctx, bb, w, h, d, 1) : dc2_near(ctx, bb, w, h, d, 30 * u), a = sp.ok ? 1 : 0.35;
        var n = 5 + (d.n | 0), cells = [], raw = [];
        for (i = 0; i < 12; i++) cells.push([i % 4, Math.floor(i / 4), dc2_r(s, i, 1)]);
        cells.sort(function (p, q) { return p[2] - q[2]; });
        for (i = 0; i < n; i++) raw.push([sp.x + w * (cells[i][0] + 0.5 + dc2_rs(s, i, 2) * 0.35) / 4, sp.y + h * (cells[i][1] + 0.5 + dc2_rs(s, i, 3) * 0.35) / 3]);
        raw.sort(function (p, q) { return p[0] - q[0]; });
        var pts = [raw.shift()];
        while (raw.length) {
            var q0 = pts[pts.length - 1], bi = 0, bd = 1e18;
            for (j = 0; j < raw.length; j++) { var dd = (raw[j][0] - q0[0]) * (raw[j][0] - q0[0]) + (raw[j][1] - q0[1]) * (raw[j][1] - q0[1]); if (dd < bd) { bd = dd; bi = j; } }
            pts.push(raw.splice(bi, 1)[0]);
        }
        var mid = pts[Math.floor(n / 2)], tip = [sp.x + w * dc2_rr(0.3, 0.7, s, 9), sp.y + h * (mid[1] - sp.y > h / 2 ? 0.08 : 0.92)];
        var S = jzShapeLayer(ctx, 'constellation', 0, 0), all = pts.concat([tip]);
        // stars (on top), then the lines
        for (i = 0; i < all.length; i++) {
            var p = all[i], rr = (dc2_r(s, i, 4) < 0.3 ? 3.6 : 2.2) * u, gs = jzGrp(S, 'star ' + (i + 1));
            var pop = DC2_CL + DC2_OB + 'var q=cl((time-' + jzN(i * 0.06) + ')/0.3),s=q<=0?0:ob(q,2);';
            if (rr > 3 * u && i % 2 === 0) {      // cross glint on the bigger stars, twinkling
                var gx = dc2_sub(gs, 'glint'), L = 11 * u;
                jzAddPath(gx, [[-L, 0], [L, 0]], false); jzAddPath(gx, [[0, -L], [0, L]], false); jzAddStroke(gx, sc.accent, Math.max(1, u));
                var tw = 'var tw=0.65+0.35*noise(time*2.5+' + jzN(i * 3 + (s % 97)) + ');';
                dc2_gx(gx, 'ADBE Vector Scale', tw + '[100*tw,100*tw]');
                dc2_gx(gx, 'ADBE Vector Group Opacity', pop + tw + 'q>0?100*tw:0');
            }
            var gd = dc2_sub(gs, 'dot'); jzAddEllipse(gd, rr * 2, rr * 2); jzAddFill(gd, sc.fg);
            dc2_gx(gd, 'ADBE Vector Scale', pop + '[100*s,100*s]');
            jzGX(gs).property('ADBE Vector Position').setValue(p);
        }
        var gl = jzGrp(S, 'lines'); jzAddPath(gl, pts, false); jzAddStroke(gl, sc.sub, Math.max(1, u), 70);
        jzAddTrimPaths(gl, DC2_CL + DC2_IOC + '100*ioc((time-0.1)/0.8)');
        var gb = jzGrp(S, 'branch'); jzAddPath(gb, [mid, tip], false); jzAddStroke(gb, sc.sub, Math.max(1, u), 70);
        jzAddTrimPaths(gb, DC2_CL + '100*cl((time-0.6)/0.4)');
        jzNoGhost(S);
        dc2_op(ctx, S, null, a * 100);
        var fs = dc2_fs(ctx) * 0.72;
        var lab = jzText(ctx, 'C-' + jzLineNo(ctx), { font: jzMonoF(ctx), size: fs, color: sc.sub, x: all[0][0] + 8 * u, y: all[0][1] - 12 * u, align: 'left', track: 0.08 });
        jzNoGhost(lab);
        dc2_op(ctx, lab, 'oe((time-0.5)/0.3)', a * 100);
    }
});

// ================================================================ particles & light

/* ---- confetti \u2014 \u7D19\u5439\u96EA: tumbling paper bits drifting down around (never over) the lyric; they fall faster as the cut leaves */
jzReg('decor', 'confetti', {
    back: false,
    build: function (ctx, bb0, d) {
        var bb = dc2_bb(ctx, bb0), W = ctx.W, H = ctx.H, sc = ctx.sc, u = dc2_u(ctx), s = d.seed, i;
        var N = Math.min(56, 26 + (d.n | 0) * 9), cols = [sc.accent, sc.accent2 || sc.fg, sc.fg, sc.sub || sc.fg];
        var S = jzShapeLayer(ctx, 'confetti', 0, 0), HD = dc2_hd(ctx, DC2_WR);
        for (i = 0; i < N; i++) {
            var w0 = (11 + dc2_r(s, i, 8) * 11) * u, h0 = w0 * (0.4 + dc2_r(s, i, 9) * 0.3), g = jzGrp(S, 'bit ' + (i + 1));
            jzAddRect(g, w0, h0); jzAddFill(g, cols[i % 4], 95);
            dc2_gx(g, 'ADBE Vector Position', HD + '[' + jzN(dc2_r(s, i, 4) * W) + '+Math.sin(T*' + jzN(1 + dc2_r(s, i, 5) * 1.8) + '+' + jzN(dc2_r(s, i, 6) * 6) + ')*' + jzN((10 + dc2_r(s, i, 7) * 30) * u) +
                ',wr(' + jzN(dc2_r(s, i, 3) * H) + '+' + jzN((70 + dc2_r(s, i, 2) * 110) * u) + '*(1+PO*PO*PO)*T,' + jzN(-40 * u) + ',' + jzN(H + 80 * u) + ')]');
            dc2_gx(g, 'ADBE Vector Rotation', 'time*' + jzN((dc2_r(s, i, 11) - 0.5) * 8 * 180 / Math.PI) + '+' + jzN(dc2_r(s, i, 10) * 360));
            dc2_gx(g, 'ADBE Vector Scale', DC2_CL + DC2_OB + 'var q=cl((time-' + jzN(dc2_r(s, i, 1) * 0.35) + ')/0.3),k=q<=0?0:ob(q,1.8),fl=Math.max(0.15,Math.abs(Math.cos(time*' +
                jzN(2 + dc2_r(s, i, 12) * 4) + '+' + jzN(dc2_r(s, i, 13) * 6) + ')));[100*k*fl,100*k]');
        }
        dc2_hole(S, bb.x0 - 16 * u, bb.y0 - 16 * u, bb.x1 + 16 * u, bb.y1 + 16 * u, 10 * u);
        jzNoGhost(S);
        dc2_op(ctx, S);
    }
});

/* ---- petals \u2014 \u82B1\u3073\u3089: cherry petals tumbling diagonally on the wind, clear of the lyric */
var DC2_PETAL = [[0, -0.5], [0.16, -0.38], [0.3, -0.18], [0.36, 0.04], [0.32, 0.24], [0.22, 0.42], [0.1, 0.5], [0, 0.4], [-0.1, 0.5], [-0.22, 0.42], [-0.32, 0.24], [-0.36, 0.04], [-0.3, -0.18], [-0.16, -0.38]];
jzReg('decor', 'petals', {
    back: false,
    build: function (ctx, bb0, d) {
        var bb = dc2_bb(ctx, bb0), W = ctx.W, H = ctx.H, sc = ctx.sc, u = dc2_u(ctx), s = d.seed, i;
        var N = Math.min(26, 10 + (d.n | 0) * 5), dir = d.right ? 1 : -1, colB = dc2_dark(ctx) ? sc.fg : (sc.sub || sc.fg);
        var S = jzShapeLayer(ctx, 'petals', 0, 0), HD = dc2_hd(ctx, DC2_WR);
        for (i = 0; i < N; i++) {
            var L0 = (24 + dc2_r(s, i, 8) * 20) * u, g = jzGrp(S, 'petal ' + (i + 1)), a = dc2_r(s, i, 13) < 0.7;
            jzAddPath(g, dc2_pts(DC2_PETAL, L0), true); jzAddFill(g, a ? sc.accent : colB, a ? 90 : 85);
            dc2_gx(g, 'ADBE Vector Position', HD + '[wr(' + jzN(dc2_r(s, i, 4) * W) + '+' + jzN(dir * (30 + dc2_r(s, i, 3) * 50) * u) + '*T+Math.sin(T*' + jzN(0.8 + dc2_r(s, i, 5)) + '+' + jzN(dc2_r(s, i, 6) * 6) + ')*' + jzN(30 * u) +
                ',' + jzN(-60 * u) + ',' + jzN(W + 120 * u) + '),wr(' + jzN(dc2_r(s, i, 7) * H) + '+' + jzN((40 + dc2_r(s, i, 2) * 60) * u) + '*T,' + jzN(-60 * u) + ',' + jzN(H + 120 * u) + ')]');
            dc2_gx(g, 'ADBE Vector Rotation', 'time*' + jzN((dc2_r(s, i, 10) - 0.5) * 3 * 180 / Math.PI) + '+' + jzN(dc2_r(s, i, 9) * 360));
            dc2_gx(g, 'ADBE Vector Scale', DC2_CL + DC2_OC + 'var q=oc((time-' + jzN(dc2_r(s, i, 1) * 0.4) + ')/0.35),fx=Math.max(0.2,Math.abs(Math.cos(time*' +
                jzN(1.2 + dc2_r(s, i, 11) * 2) + '+' + jzN(dc2_r(s, i, 12) * 6) + ')));[100*q*fx,100*q]');
        }
        dc2_hole(S, bb.x0 - 18 * u, bb.y0 - 18 * u, bb.x1 + 18 * u, bb.y1 + 18 * u, 12 * u);
        jzNoGhost(S);
        dc2_op(ctx, S);
    }
});

/* ---- rainStreaks \u2014 \u96E8\u306E\u7B4B: fine slanted streaks in three depths falling behind the lyric */
jzReg('decor', 'rainStreaks', {
    back: true,
    build: function (ctx, bb, d) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, u = dc2_u(ctx), s = d.seed, i, b, dk = dc2_dark(ctx);
        var N = Math.min(110, 60 + (d.n | 0) * 16), sl = (d.right ? 1 : -1) * (8 + (d.r || 0) * 10) * Math.PI / 180, tn = Math.tan(sl);
        var S = jzShapeLayer(ctx, 'rain', 0, 0), HD = dc2_hd(ctx, DC2_WR), g;
        var col = dk ? (sc.sub || sc.fg) : sc.fg, k = dk ? 1 : 0.75, ws = [Math.max(1, 0.9 * u), Math.max(1, 1.2 * u), 1.6 * u], as = [18, 30, 46];
        // one depth band at a time (rain 3, 2, 1 \u2014 same stacking as before), each filled completely before the next band is
        // added to the contents (a new sibling group invalidates references to the earlier bands)
        for (b = 2; b >= 0; b--) {
            var band = jzGrp(S, 'rain ' + (b + 1));
            for (i = 0; i < N; i++) {
                var z = dc2_r(s, i, 1); if ((z < 0.4 ? 0 : z < 0.8 ? 1 : 2) !== b) continue;
                var v = (1200 + z * 1300) * u, len = (36 + z * 80) * u, span = H + len + 80 * u;
                var x0 = dc2_r(s, i, 3) * (W + H * Math.abs(tn)) - (tn > 0 ? H * tn : 0);
                g = dc2_sub(band, 'streak ' + (i + 1));
                jzAddPath(g, [[0, 0], [len * tn, len]], false);
                dc2_gx(g, 'ADBE Vector Position', HD + 'var y=wr(' + jzN(dc2_r(s, i, 2) * span) + '+' + jzN(v) + '*T,' + jzN(-len - 40 * u) + ',' + jzN(span) + ');[' + jzN(x0) + '+y*' + jzN(tn) + ',y]');
            }
            jzAddStroke(band, col, ws[b], as[b] * k);
        }
        jzNoGhost(S);
        dc2_op(ctx, S, 'oc(time/0.45)');
    }
});

/* ---- snow \u2014 \u96EA: soft flakes in three depths, gently swaying (under the lyric) */
jzReg('decor', 'snow', {
    back: true,
    build: function (ctx, bb, d) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, u = dc2_u(ctx), s = d.seed, i, dk = dc2_dark(ctx);
        var N = Math.min(90, 46 + (d.n | 0) * 14), col = dk ? sc.fg : (sc.sub || sc.fg), k = dk ? 1 : 0.55, dir = d.right ? 1 : -1;
        var S = jzShapeLayer(ctx, 'snow', 0, 0), HD = dc2_hd(ctx, DC2_WR);
        for (i = 0; i < N; i++) {
            var z = dc2_r(s, i, 1), lay = z < 0.5 ? 0 : z < 0.85 ? 1 : 2, rad = [2, 3.4, 5.6][lay] * u * (0.8 + dc2_r(s, i, 2) * 0.4);
            var vy = (22 + lay * 22 + dc2_r(s, i, 3) * 14) * u, g = jzGrp(S, 'flake ' + (i + 1));
            var gd = dc2_sub(g, 'dot'); jzAddEllipse(gd, rad * 2, rad * 2); jzAddFill(gd, col, [35, 55, 75][lay] * k);
            if (lay) { var gh = dc2_sub(g, 'halo'); jzAddEllipse(gh, rad * 4.4, rad * 4.4); jzAddFill(gh, col, 8 * k); }
            dc2_gx(g, 'ADBE Vector Position', HD + '[wr(' + jzN(dc2_r(s, i, 5) * W) + '+Math.sin(T*' + jzN(0.6 + dc2_r(s, i, 6)) + '+' + jzN(dc2_r(s, i, 7) * 6) + ')*' + jzN((8 + lay * 10) * u) + '+' + jzN(dir * 8 * u) + '*T,' +
                jzN(-10 * u) + ',' + jzN(W + 20 * u) + '),wr(' + jzN(dc2_r(s, i, 4) * H) + '+' + jzN(vy) + '*T,' + jzN(-10 * u) + ',' + jzN(H + 20 * u) + ')]');
        }
        jzNoGhost(S);
        dc2_op(ctx, S, 'oc(time/0.5)');
    }
});

/* ---- lightLeak \u2014 \u5149\u6F0F\u308C: soft warm light breathing in from a screen edge (screen on dark schemes, multiply on light ones).
   The browser's radial gradients are solids with a big feathered elliptical mask; the optional beam is a feathered band. */
function dc2_glow(ctx, name, col, x, y, R, alpha, dy, blend) {
    var W = ctx.W, H = ctx.H, SW = W * 1.6, SH = H * 1.6, ox = (W - SW) / 2, oy = (H - SH) / 2;
    var L = ctx.comp.layers.addSolid(jzHex(col), name, Math.round(SW), Math.round(SH), 1, ctx.comp.duration);
    var m = L.property('ADBE Mask Parade').addProperty('ADBE Mask Atom');
    m.property('ADBE Mask Shape').setValue(jzCircleShape(x - ox, y - oy, R * 0.4));    // radial falloff \u2248 disc + feather
    m.property('ADBE Mask Feather').setValue([R * 0.5, R * 0.5]);
    jzXf(L, 'ADBE Position').setValue([W / 2, H / 2]);
    if (dy) jzSetExpr(jzXf(L, 'ADBE Position'), '[value[0],value[1]+time*' + jzN(dy) + ']');
    L.blendingMode = blend;
    jzNoGhost(L);
    return L;
}
jzReg('decor', 'lightLeak', {
    back: true,
    build: function (ctx, bb, d) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, u = dc2_u(ctx), s = d.seed, dk = dc2_dark(ctx);
        function pick(c) { return c && (dk ? jzLum(c) > 0.2 : jzLum(c) < 0.85) ? c : null; }
        var c1 = pick(sc.accent) || pick(sc.accent2) || sc.sub || sc.fg, c2 = pick(sc.accent2) || c1;
        var sx = d.right ? 1 : -1, sy = d.low ? 1 : -1, M = Math.max(W, H), A = dk ? 0.5 : 0.28, dr = 18 * u * (d.corner ? 1 : -1);
        var blend = dk ? BlendingMode.SCREEN : BlendingMode.MULTIPLY, r = d.r == null ? 0.5 : d.r;
        var br = 'oc(time/0.6)*(0.85+0.15*Math.sin(time*1.4+' + jzN(r * 6) + '))';
        var L1 = dc2_glow(ctx, 'light leak', c1, (sx > 0 ? W : 0) + sx * M * 0.05, (sy > 0 ? H * 0.95 : H * 0.05), M * dc2_rr(0.42, 0.6, s, 1), A, dr, blend);
        dc2_op(ctx, L1, br, A * 115);
        var L2 = dc2_glow(ctx, 'light leak 2', c2, (sx > 0 ? W : 0) - sx * M * 0.02, H * dc2_rr(0.3, 0.7, s, 2), M * dc2_rr(0.22, 0.32, s, 3), A * 0.8, -dr, blend);
        dc2_op(ctx, L2, br, A * 92);
        if ((d.v | 0) % 2) {        // a slanted beam
            var w = W * 0.12, B = ctx.comp.layers.addSolid(jzHex(c1), 'light beam', Math.round(w * 6), Math.round(H * 2.2), 1, ctx.comp.duration);
            var bw = w * 6, bh = H * 2.2, m = B.property('ADBE Mask Parade').addProperty('ADBE Mask Atom'), sh = new Shape();
            sh.vertices = [[bw / 2 + w * 0.35, -bh], [bw / 2 + w * 0.35, bh * 2], [bw / 2 - w * 0.35, bh * 2], [bw / 2 - w * 0.35, -bh]]; sh.closed = true;
            m.property('ADBE Mask Shape').setValue(sh); m.property('ADBE Mask Feather').setValue([w * 1.3, w * 1.3]);
            jzXf(B, 'ADBE Position').setValue([sx > 0 ? W * 0.82 : W * 0.18, H / 2]);
            jzXf(B, 'ADBE Rotate Z').setValue(sx * 14);
            jzSetExpr(jzXf(B, 'ADBE Position'), 'var a=' + jzN(sx * 14 * Math.PI / 180) + ',o=time*' + jzN(dr * 0.5) + ';[value[0]+o*Math.cos(a),value[1]+o*Math.sin(a)]');
            B.blendingMode = blend; jzNoGhost(B);
            dc2_op(ctx, B, br, A * 50);
        }
    }
});

/* ---- bokeh \u2014 \u30DC\u30B1\u7389: out-of-focus light discs (or aperture hexagons) pulling into focus, drifting up */
jzReg('decor', 'bokeh', {
    back: true,
    build: function (ctx, bb, d) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, u = dc2_u(ctx), s = d.seed, i, k, dk = dc2_dark(ctx);
        var N = Math.min(18, 8 + (d.n | 0) * 3), hex = (d.v | 0) % 2 === 1, M = Math.min(W, H), cols = [sc.accent, sc.accent2 || sc.fg, sc.fg];
        var S = jzShapeLayer(ctx, 'bokeh', 0, 0), HD = dc2_hd(ctx, DC2_WR);
        for (i = 0; i < N; i++) {
            var R0 = M * (0.025 + dc2_r(s, i, 2) * dc2_r(s, i, 2) * 0.075), c = cols[i % 3], al = (dk ? 0.06 + dc2_r(s, i, 7) * 0.1 : 0.05 + dc2_r(s, i, 7) * 0.07);
            var g = jzGrp(S, 'disc ' + (i + 1));
            if (hex) { var hp = []; for (k = 0; k < 6; k++) { var an = (k * 60 + 15) * Math.PI / 180; hp.push([Math.cos(an) * R0, Math.sin(an) * R0]); } jzAddPath(g, hp, true); }
            else jzAddEllipse(g, R0 * 2, R0 * 2);
            jzAddStroke(g, c, 1.6 * u, Math.min(100, al * 130)); jzAddFill(g, c, al * 100);
            var f = DC2_OC + 'var f=oc((T-' + jzN(dc2_r(s, i, 1) * 0.4) + ')/0.6);';
            dc2_gx(g, 'ADBE Vector Position', HD + '[' + jzN(dc2_r(s, i, 3) * W) + '+Math.sin(T*0.5+' + jzN(dc2_r(s, i, 4) * 6) + ')*' + jzN(14 * u) + ',wr(' + jzN(dc2_r(s, i, 5) * H) + '-T*' + jzN((6 + dc2_r(s, i, 6) * 12) * u) + ',' + jzN(-R0) + ',' + jzN(H + R0 * 2) + ')]');
            dc2_gx(g, 'ADBE Vector Scale', HD + f + 'var k=1.5-0.5*f;[100*k,100*k]');
            dc2_gx(g, 'ADBE Vector Group Opacity', HD + f + '100*f');
        }
        jzNoGhost(S);
        dc2_op(ctx, S);
    }
});

/* ---- speedCorner \u2014 \u96C6\u4E2D\u7DDA: manga speed lines rushing in from the edges, stopping short of the lyric, flickering in length */
function dc2_superR(a, b, p, th) { var c = Math.abs(Math.cos(th)), s = Math.abs(Math.sin(th)); return 1 / Math.pow(Math.pow(c / a, p) + Math.pow(s / b, p), 1 / p); }
jzReg('decor', 'speedCorner', {
    back: false,
    build: function (ctx, bb0, d) {
        var bb = dc2_bb(ctx, bb0), W = ctx.W, H = ctx.H, sc = ctx.sc, u = dc2_u(ctx), s = d.seed, i;
        var cx = bb.cx, cy = bb.cy, pad = 30 * u + bb.h * 0.2, A = bb.w / 2 * 1.1 + pad, B = bb.h / 2 * 1.15 + pad;
        var N = Math.min(150, 80 + (d.n | 0) * 22), corners = !!d.corner;
        var S = jzShapeLayer(ctx, 'speed lines', 0, 0), GL = jzGrp(S, 'lines'), HD = dc2_hd(ctx, DC2_OE + DC2_IC + DC2_HH) + 'var st=Math.floor(T*12);';
        for (i = 0; i < N; i++) {
            var th = dc2_r(s, i, 1) * Math.PI * 2;
            if (corners) { var q = Math.floor(dc2_r(s, i, 2) * 4), base = Math.atan2((q < 2 ? -1 : 1) * H, (q % 2 ? 1 : -1) * W); th = base + (dc2_r(s, i, 3) - 0.5) * 0.7; }
            var c = Math.cos(th), sn = Math.sin(th);
            var tx = c > 0 ? (W + 4 - cx) / c : c < 0 ? (-4 - cx) / c : 1e9, ty = sn > 0 ? (H + 4 - cy) / sn : sn < 0 ? (-4 - cy) / sn : 1e9;
            var dEdge = Math.min(tx, ty), sr = dc2_superR(A, B, 4, th), dIn = sr + 20 * u + dc2_r(s, i, 4) * 0.35 * Math.max(0, dEdge - sr);
            if (dEdge - dIn < 30 * u) continue;
            var wd = (1.2 + dc2_r(s, i, 6) * 4.5) * u, Lm = dEdge - dIn, g = dc2_sub(GL, 'line ' + (i + 1));
            jzAddPath(g, [[0, -wd], [0, wd], [Lm, 0]], true);
            jzGX(g).property('ADBE Vector Position').setValue([cx + c * dEdge, cy + sn * dEdge]);
            jzGX(g).property('ADBE Vector Rotation').setValue(th * 180 / Math.PI + 180);
            dc2_gx(g, 'ADBE Vector Scale', HD + 'var e=oe((T-' + jzN(dc2_r(s, i, 5) * 0.15) + ')/0.3),j=0.85+0.15*hh(' + jzN((s % 9973) + i * 7.31) + '+st*1.37);[100*e*j*(1-ic(PO)),100]');
        }
        jzAddFill(GL, dc2_dark(ctx) ? sc.fg : (sc.ink || sc.fg));
        jzNoGhost(S);
        dc2_op(ctx, S, null, 80);
    }
});

/* ---- risingParticles \u2014 \u7ACB\u3061\u4E0A\u308B\u7C92: embers / motes rising and swaying, fading in and out over the height (under the lyric) */
jzReg('decor', 'risingParticles', {
    back: true,
    build: function (ctx, bb, d) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, u = dc2_u(ctx), s = d.seed, i;
        var N = Math.min(54, 24 + (d.n | 0) * 10), shape = (d.v | 0) % 3, col = d.accent || shape === 1 ? sc.accent : (dc2_dark(ctx) ? sc.fg : (sc.sub || sc.fg));
        var S = jzShapeLayer(ctx, 'rising particles', 0, 0), HD = dc2_hd(ctx, DC2_WR), lw = Math.max(1, u), sw = 14 * u;
        for (i = 0; i < N; i++) {
            var vy = (40 + dc2_r(s, i, 1) * 90) * u, span = H + 120 * u, ph = dc2_r(s, i, 5) * 6, f = 0.7 + dc2_r(s, i, 4), sz = (1.6 + dc2_r(s, i, 6) * 2.6) * u, tl = vy * 0.28;
            var hk = HD + 'var y=' + jzN(H + 60 * u) + '-wr(' + jzN(dc2_r(s, i, 2) * span) + '+' + jzN(vy) + '*T,0,' + jzN(span) + '),w=T*' + jzN(f) + '+' + jzN(ph) + ';';
            var g = jzGrp(S, 'mote ' + (i + 1));
            var gh = dc2_sub(g, 'head');
            if (shape === 2) jzAddPath(gh, [[0, -sz * 1.6], [sz, 0], [0, sz * 1.6], [-sz, 0]], true); else jzAddEllipse(gh, sz * 2, sz * 2);
            jzAddFill(gh, col);
            var gt = dc2_sub(g, 'trail'); jzAddPath(gt, [[0, 0], [0, tl]], false); dc2_stroke(gt, col, lw, 31, true);
            jzGX(gt).property('ADBE Vector Position').setValue([0, sz * 1.5]);
            dc2_gx(gt, 'ADBE Vector Rotation', hk + 'Math.atan2(Math.cos(w)*' + jzN(f * sw) + ',' + jzN(vy) + ')*180/Math.PI');
            if (sz > 3 * u) { var gg = dc2_sub(g, 'glow'); jzAddEllipse(gg, sz * 6.4, sz * 6.4); jzAddFill(gg, col, 8); }
            dc2_gx(g, 'ADBE Vector Position', hk + '[' + jzN(dc2_r(s, i, 3) * W) + '+Math.sin(w)*' + jzN(sw) + ',y]');
            dc2_gx(g, 'ADBE Vector Group Opacity', hk + 'var p=cl(1-y/' + jzN(H) + '),fd=Math.pow(Math.sin(Math.PI*p),0.7);fd<=0.05?0:100*(0.2+0.65*fd)');
        }
        jzNoGhost(S);
        dc2_op(ctx, S, 'oc(time/0.45)');
    }
});

/* ---- twinkle \u2014 \u304D\u3089\u3081\u304D: four-point glints twinkling just outside the lyric's box */
jzReg('decor', 'twinkle', {
    back: false,
    build: function (ctx, bb0, d) {
        var bb = dc2_bb(ctx, bb0), W = ctx.W, H = ctx.H, sc = ctx.sc, u = dc2_u(ctx), s = d.seed, i;
        var K = Math.min(7, 3 + (d.n | 0) + (d.big ? 1 : 0)), pad = 16 * u + bb.h * 0.12;
        var X0 = bb.x0 - pad, X1 = bb.x1 + pad, Y0 = bb.y0 - pad, Y1 = bb.y1 + pad, per = 2 * (X1 - X0 + Y1 - Y0), S = null;
        for (i = 0; i < K; i++) {
            var p = dc2_perim(X0, Y0, X1, Y1, (i + dc2_r(s, i, 1) * 0.6) / K * per);
            var x = jzClamp(p[0] + dc2_rs(s, i, 2) * 14 * u, 16 * u, W - 16 * u), y = jzClamp(p[1] + dc2_rs(s, i, 3) * 14 * u, 16 * u, H - 16 * u);
            if (dc2_clear(bb, x, y, 4 * u, 1) < 1) continue;
            if (!S) S = jzShapeLayer(ctx, 'twinkle', 0, 0);
            var R = (i === 0 ? 30 : 13 + dc2_r(s, i, 4) * 12) * u, g = jzGrp(S, 'glint ' + (i + 1)), L = R * 1.9;
            var gf = dc2_sub(g, 'star'); jzAddPath(gf, dc2_glint(R, R * 0.2), true); jzAddFill(gf, i % 3 === 0 ? sc.accent : sc.fg);
            var gl = dc2_sub(g, 'rays'); jzAddPath(gl, [[-L, 0], [L, 0]], false); jzAddPath(gl, [[0, -L], [0, L]], false); jzAddStroke(gl, sc.fg, Math.max(1, 0.9 * u), 50);
            jzGX(g).property('ADBE Vector Position').setValue([x, y]);
            var st = dc2_r(s, i, 6) * 0.3, cyc = 1.3 + dc2_r(s, i, 5) * 0.8;
            dc2_gx(g, 'ADBE Vector Scale', jzTH(ctx) + 'var ph=(((time-' + jzN(st) + ')/' + jzN(cyc) + '+' + jzN(dc2_r(s, i, 7)) + ')%1+1)%1,q=cl((time-' + jzN(st) + ')/0.3),it=q<=0?0:ob(q,2);' +
                'var k=Math.min(it,time>0.6?0.25+0.75*Math.pow(Math.sin(Math.PI*ph),2):it)*K;[100*k,100*k]');
        }
        if (!S) return;
        jzNoGhost(S);
        dc2_op(ctx, S);
    }
});

// ================================================================ hand-drawn

/* ---- brushStroke \u2014 \u7B46\u306E\u6255\u3044: a dry-brush sweep with bristle streaks and a tapering flick, painted across (under the lyric) */
jzReg('decor', 'brushStroke', {
    back: true,
    build: function (ctx, bb, d) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, u = dc2_u(ctx), s = d.seed, v = (d.v | 0) % 3, i, j, k;
        // colour + opacity chosen so the lyric (sc.fg) keeps >= 3:1 contrast on top of the stroke
        var cands = v === 1 ? [sc.dim, sc.accent2, sc.sub] : [sc.accent, sc.accent2, sc.ghostA, sc.ghostB, sc.sub, sc.dim], col = null, al = 0;
        for (i = 0; i < cands.length && !col; i++) {
            var c = cands[i]; if (!c || jzContrast(c, sc.bg) < 1.15) continue;
            for (var a = v === 1 ? 0.9 : 0.55; a >= 0.2; a -= 0.07) if (jzContrast(jzMixHex(sc.bg, c, a), sc.fg) >= 3) { col = c; al = a; break; }
        }
        if (!col) return;
        var T = jzU(ctx) * dc2_rr(0.11, 0.16, s, 1), yc = H * 0.5 + (d.low ? 1 : -1) * H * dc2_rr(0, 0.05, s, 2) + (W < H ? 0 : T * 0.1);
        var ltr = !!d.right, xa = W * dc2_rr(0.06, 0.16, s, 3), xb = W * dc2_rr(0.84, 0.95, s, 4), tilt = dc2_rs(s, 5) * T * 0.35, bow = dc2_rs(s, 6) * T * 0.25;
        var K = 22, SN = 26, S = jzShapeLayer(ctx, 'brush stroke', 0, 0), hairs = [[], []], bn, band;
        var HEAD = DC2_CL + 'var h=1-Math.pow(1-cl(time/0.5),3);';
        // hairs are collected per band first; each band group is then filled completely before the next one is added
        // (a new sibling group invalidates references to the earlier band)
        for (j = 0; j < K; j++) {
            var f = j / (K - 1) - 0.5, edge = Math.abs(f) * 2;
            var t0 = dc2_r(s, j, 1) * 0.05 + edge * edge * 0.06, t1 = 1 - edge * dc2_rr(0.12, 0.4, s, j, 2) - dc2_r(s, j, 3) * 0.06;
            if (t1 <= t0) continue;
            var gapAt = dc2_r(s, j, 4) < 0.45 ? dc2_rr(0.55, 0.9, s, j, 5) : 2, gapL = 0.03 + dc2_r(s, j, 6) * 0.05;
            var pieces = gapAt < t1 ? [[t0, Math.min(gapAt, t1)], [gapAt + gapL, t1]] : [[t0, t1]];
            bn = edge > 0.6 || dc2_r(s, j, 7) < 0.3 ? 1 : 0;
            for (k = 0; k < pieces.length; k++) {
                var ta = pieces[k][0], tb = pieces[k][1]; if (tb - ta < 0.01) continue;
                var m = Math.max(3, Math.round(SN * (tb - ta))), pts = [];
                for (i = 0; i <= m; i++) {
                    var t = ta + (tb - ta) * i / m, tt = ltr ? t : 1 - t, taper = 1 - Math.pow(t, 3) * 0.55;
                    pts.push([jzLerp(xa, xb, tt), yc + tilt * (tt - 0.5) + bow * Math.sin(tt * Math.PI) + f * T * taper + dc2_rs(s, j, Math.round(t * SN), 8) * 0.8 * u]);
                }
                hairs[bn].push({ name: 'hair ' + (j + 1) + '.' + (k + 1), pts: pts, ta: ta, tb: tb });
            }
        }
        for (bn = 0; bn < 2; bn++) {
            band = jzGrp(S, bn ? 'bristles fine' : 'bristles');
            for (k = 0; k < hairs[bn].length; k++) {
                var hr = hairs[bn][k], g = dc2_sub(band, hr.name);
                jzAddPath(g, hr.pts, false);
                jzAddTrimPaths(g, HEAD + '100*cl((h-' + jzN(hr.ta) + ')/' + jzN(hr.tb - hr.ta) + ')');
            }
            dc2_stroke(band, col, T / K * (bn ? 1.3 : 2.2), al * 100, true);
        }
        jzNoGhost(S);
        dc2_op(ctx, S);
    }
});

/* ---- tapePieces \u2014 \u30DE\u30B9\u30AD\u30F3\u30B0\u30C6\u30FC\u30D7: translucent torn-edged tape pinning the lyric's corners (printed stripes on most) */
jzReg('decor', 'tapePieces', {
    back: false,
    build: function (ctx, bb0, d) {
        var bb = dc2_bb(ctx, bb0), sc = ctx.sc, u = dc2_u(ctx), s = d.seed, i, k, r = d.r == null ? 0.5 : d.r;
        var Lt = jzClamp(jzU(ctx) * 0.115, 90 * u, 150 * u) * (0.9 + r * 0.2), Ht = Lt * 0.4, v = (d.v | 0) % 3;
        var cs = d.big ? [[-1, -1], [1, -1], [1, 1], [-1, 1]] : d.right ? [[1, -1], [-1, 1]] : [[-1, -1], [1, 1]];
        var col = [sc.accent2 && jzContrast(sc.accent2, sc.bg) > 1.6 ? sc.accent2 : (sc.sub || sc.fg), sc.accent, sc.sub || sc.fg][v];
        var S = jzShapeLayer(ctx, 'tape', 0, 0), hl = Lt / 2, hh = Ht / 2;
        for (k = 0; k < cs.length; k++) {
            var sx = cs[k][0], sy = cs[k][1], X = sx < 0 ? bb.x0 : bb.x1, Y = sy < 0 ? bb.y0 : bb.y1;
            var g = jzGrp(S, 'tape ' + (k + 1)), pts = [];
            if (v !== 1) {           // printed diagonal stripes, clipped to the tape (computed here: shape groups can't clip)
                var gs = dc2_sub(g, 'stripes'), xa = -hl + 3 * u, xb = hl - 3 * u;
                for (var x = -hl - Ht; x < hl + Ht; x += 9 * u) {
                    var s0 = jzClamp((xa - x) / Ht, 0, 1), s1 = jzClamp((xb - x) / Ht, 0, 1);
                    if (s1 - s0 > 0.02) jzAddPath(gs, [[x + Ht * s0, hh - Ht * s0], [x + Ht * s1, hh - Ht * s1]], false);
                }
                jzAddStroke(gs, dc2_dark(ctx) ? sc.bg : sc.fg, 2 * u, 14);
            }
            for (i = 0; i <= 5; i++) pts.push([-hl + (i % 2 ? 3 : 0) * u + dc2_rs(s, k, i, 2) * 2 * u, -hh + i / 5 * Ht]);
            for (i = 5; i >= 0; i--) pts.push([hl - (i % 2 ? 3 : 0) * u + dc2_rs(s, k, i, 3) * 2 * u, -hh + i / 5 * Ht]);
            var gp = dc2_sub(g, 'paper'); jzAddPath(gp, pts, true); jzAddFill(gp, col, 62);
            jzGX(g).property('ADBE Vector Position').setValue([X + sx * Ht * 0.55, Y + sy * Ht * 0.55]);
            var hd = DC2_CL + DC2_OC + 'var eq=oc((time-' + jzN(0.08 + k * 0.1) + ')/0.25);';
            dc2_gx(g, 'ADBE Vector Rotation', hd + jzN(-sx * sy * 40 + dc2_rs(s, k, 1) * 10) + '+(1-eq)*' + jzN(10 * sx));
            dc2_gx(g, 'ADBE Vector Scale', hd + 'var k=1+0.2*(1-eq);[100*k,100*k]');
            dc2_gx(g, 'ADBE Vector Group Opacity', hd + '100*eq');
        }
        jzNoGhost(S);
        dc2_op(ctx, S);
    }
});

/* ---- scribbleCircle \u2014 \u624B\u63CF\u304D\u306E\u56F2\u307F: a loose hand-drawn loop around the lyric, drawn on */
function dc2_handLoop(ctx, bb, d, turns) {
    var W = ctx.W, H = ctx.H, u = dc2_u(ctx), s = d.seed, pad = 14 * u + Math.min(bb.w, bb.h) * 0.08, i;
    var hw = bb.w / 2, hh = bb.h / 2, cx = bb.cx, cy = bb.cy;
    var lx = Math.max(hw * 0.9, Math.min(cx, W - cx) - 10 * u), ly = Math.max(hh * 0.9, Math.min(cy, H - cy) - 10 * u);
    var A = Math.min(hw * 1.22 + pad, lx), B = Math.min(hh * 1.22 + pad, ly);
    if ((hw / A) * (hw / A) + (hh / B) * (hh / B) > 1) {
        if (A < hw * 1.22 + pad) B = Math.min(ly, hh / Math.sqrt(Math.max(0.06, 1 - (hw / A) * (hw / A))) * 0.96 + pad * 0.3);
        else A = Math.min(lx, hw / Math.sqrt(Math.max(0.06, 1 - (hh / B) * (hh / B))) * 0.96 + pad * 0.3);
    }
    var th0 = (d.right ? -0.3 : 0.3) * Math.PI - Math.PI / 2, dir = d.right ? 1 : -1, M = 140, pts = [], tilt = dc2_rs(s, 1) * 2 * Math.PI / 180;
    for (i = 0; i <= M; i++) {
        var t = i / M, th = th0 + dir * t * turns * Math.PI * 2;
        var k = 1 + 0.03 * dc2_noise(t * 6 + (s % 97), s) + 0.05 * Math.max(0, t * turns - 0.85);
        var x = Math.cos(th) * A * k, y = Math.sin(th) * B * k;
        pts.push([cx + x * Math.cos(tilt) - y * Math.sin(tilt), cy + x * Math.sin(tilt) + y * Math.cos(tilt)]);
    }
    return pts;
}
jzReg('decor', 'scribbleCircle', {
    back: false,
    build: function (ctx, bb0, d) {
        var bb = dc2_bb(ctx, bb0), sc = ctx.sc, u = dc2_u(ctx);
        var pts = dc2_handLoop(ctx, bb, d, (d.v | 0) % 2 ? 1.85 : 1.12);
        var S = jzShapeLayer(ctx, 'scribble loop', 0, 0), g = jzGrp(S, 'loop');
        jzAddPath(g, pts, false);
        dc2_stroke(g, d.accent || !dc2_dark(ctx) ? sc.accent : sc.fg, 3 * u, 95, true);
        jzAddTrimPaths(g, DC2_CL + DC2_IOC + '100*ioc((time-0.05)/0.6)');
        jzNoGhost(S);
        dc2_op(ctx, S);
    }
});

/* ---- scribbleUnder \u2014 \u624B\u63CF\u304D\u4E0B\u7DDA: a hand-drawn underline \u2014 double swipe, wavy line or zig-zag scribble (beside vertical text) */
jzReg('decor', 'scribbleUnder', {
    back: false,
    build: function (ctx, bb0, d) {
        var bb = dc2_bb(ctx, bb0), sc = ctx.sc, u = dc2_u(ctx), s = d.seed, v = (d.v | 0) % 3, i, k;
        var vert = bb.h > bb.w * 1.3, L0 = vert ? bb.y0 : bb.x0, L1 = vert ? bb.y1 : bb.x1, len = L1 - L0;
        var base = vert ? bb.x1 + 14 * u + bb.w * 0.1 : bb.y1 + 14 * u + bb.h * 0.14;
        function mp(a, c) { return vert ? [base + c, a] : [a, base + c]; }
        function nz(t, k2) { return dc2_noise(t * 3 + k2 * 7, s) * 3 * u; }
        var strokes = [], t, x;
        if (v === 0) {
            var s1 = [], s2 = [];
            for (i = 0; i <= 24; i++) { t = i / 24; s1.push(mp(L0 - 8 * u + (len + 16 * u) * t, Math.sin(t * Math.PI) * 5 * u + nz(t, 1) - t * 4 * u)); }
            for (i = 0; i <= 16; i++) { t = i / 16; s2.push(mp(L0 + len * 0.18 + len * 0.78 * t, 11 * u + Math.sin(t * Math.PI) * 4 * u + nz(t, 2))); }
            strokes.push(s1, s2);
        } else if (v === 1) {
            var sw = [], lam = jzClamp(len / 22, 30 * u, 64 * u);
            for (x = 0; x <= len + 0.1; x += 5 * u) sw.push(mp(L0 + x, Math.sin(x / lam * Math.PI * 2) * 5 * u + nz(x / len, 3)));
            strokes.push(sw);
        } else {
            var sz = [];
            for (i = 0; i <= 5; i++) { t = i / 5; sz.push(mp(L0 + (i % 2 ? len * 0.96 : len * 0.04) + dc2_rs(s, i, 4) * 8 * u, t * 14 * u + nz(t, 5))); }
            strokes.push(sz);
        }
        var col = d.accent || !dc2_dark(ctx) ? sc.accent : sc.fg, S = jzShapeLayer(ctx, 'scribble underline', 0, 0);
        for (k = 0; k < strokes.length; k++) {
            var g = jzGrp(S, 'stroke ' + (k + 1));
            jzAddPath(g, strokes[k], false); dc2_stroke(g, col, (k ? 2.6 : 3.4) * u, 95, true);
            jzAddTrimPaths(g, DC2_CL + DC2_IOC + '100*ioc((time-' + jzN(0.05 + k * 0.28) + ')/' + (v === 2 ? '0.55' : '0.35') + ')');
        }
        jzNoGhost(S);
        dc2_op(ctx, S);
    }
});

/* ---- crossOut \u2014 \u63A8\u6572\u306E\u8D70\u308A\u66F8\u304D: a small "draft" word beside the lyric, struck out by hand, with a hand-drawn arrow to the lyric */
jzReg('decor', 'crossOut', {
    back: false,
    build: function (ctx, bb0, d) {
        var bb = dc2_bb(ctx, bb0), sc = ctx.sc, u = dc2_u(ctx), c = ctx.cut, i, k;
        var arr = jzChars(jzStrip(c.text)); if (arr.length < 1) return;
        var w0 = jzStrip((c.words && c.words[0]) || ''), n0 = jzChars(w0).length;
        var word = n0 >= 2 && n0 <= 4 ? w0 : arr.slice(0, Math.min(arr.length, 2 + (d.v | 0) % 3)).join('');
        var fs = jzClamp(bb.h * 0.3, 22 * u, 46 * u);
        var T = jzText(ctx, word, { font: jzSerifF(ctx), size: fs, color: sc.sub || sc.fg, x: 0, y: 0, align: 'center', track: 0.1 });
        var tw = jzSize(T)[0];
        var sp = dc2_near(ctx, bb, tw + 24 * u, fs * 1.8, { right: d.right, low: false, corner: d.corner, v: d.v }, 16 * u);
        var cx = sp.cx, cy = sp.cy, a = sp.ok ? 1 : 0.4;
        jzXf(T, 'ADBE Position').setValue([cx, cy]);
        jzNoGhost(T); dc2_op(ctx, T, 'oe(time/0.2)', a * 100);
        var x0 = cx - tw / 2 - 6 * u, x1 = cx + tw / 2 + 6 * u, strikes = [], z = [];
        if ((d.v | 0) % 2) strikes = [[[x0, cy - fs * 0.05], [x1, cy - fs * 0.12]], [[x0 + 4 * u, cy + fs * 0.1], [x1 - 2 * u, cy + fs * 0.02]]];
        else { for (i = 0; i <= 6; i++) z.push([jzLerp(x0, x1, i / 6), cy + (i % 2 ? -1 : 1) * fs * 0.28]); strikes = [z]; }
        var S = jzShapeLayer(ctx, 'strike out', 0, 0), col = sc.accent;
        // arrow to the nearest edge of the lyric
        var c1 = null, c3 = null;
        if (cy + fs * 0.6 < bb.y0) { c1 = [cx + tw * 0.25, cy + fs * 0.75]; c3 = [jzClamp(cx + tw * 0.35, bb.x0 + 10 * u, bb.x1 - 10 * u), bb.y0 - 6 * u]; }
        else if (cy - fs * 0.6 > bb.y1) { c1 = [cx + tw * 0.25, cy - fs * 0.75]; c3 = [jzClamp(cx + tw * 0.35, bb.x0 + 10 * u, bb.x1 - 10 * u), bb.y1 + 6 * u]; }
        else if (cx < bb.x0) { c1 = [cx + tw / 2 + 8 * u, cy + fs * 0.3]; c3 = [bb.x0 - 6 * u, jzClamp(cy + fs, bb.y0 + 10 * u, bb.y1 - 10 * u)]; }
        else { c1 = [cx - tw / 2 - 8 * u, cy + fs * 0.3]; c3 = [bb.x1 + 6 * u, jzClamp(cy + fs, bb.y0 + 10 * u, bb.y1 - 10 * u)]; }
        var dd = Math.sqrt((c3[0] - c1[0]) * (c3[0] - c1[0]) + (c3[1] - c1[1]) * (c3[1] - c1[1]));
        if (dd > 14 * u && dd < 260 * u) {
            var nx = -(c3[1] - c1[1]) / dd, ny = (c3[0] - c1[0]) / dd, bend = dd * 0.25, c2 = [(c1[0] + c3[0]) / 2 + nx * bend, (c1[1] + c3[1]) / 2 + ny * bend], ap = [];
            for (i = 0; i <= 12; i++) { var t = i / 12; ap.push([(1 - t) * (1 - t) * c1[0] + 2 * t * (1 - t) * c2[0] + t * t * c3[0], (1 - t) * (1 - t) * c1[1] + 2 * t * (1 - t) * c2[1] + t * t * c3[1]]); }
            var p = ap[12], q = ap[10], an = Math.atan2(p[1] - q[1], p[0] - q[0]), L = 9 * u;
            var gh = jzGrp(S, 'arrow head'); jzAddPath(gh, [[p[0] - Math.cos(an - 0.5) * L, p[1] - Math.sin(an - 0.5) * L], p, [p[0] - Math.cos(an + 0.5) * L, p[1] - Math.sin(an + 0.5) * L]], false);
            dc2_stroke(gh, col, 2 * u, 90, true);
            dc2_gx(gh, 'ADBE Vector Group Opacity', DC2_CL + DC2_OC + 'oc((time-0.6)/0.3)>0.95?100:0');
            var ga = jzGrp(S, 'arrow'); jzAddPath(ga, ap, false); dc2_stroke(ga, col, 2 * u, 90, true);
            jzAddTrimPaths(ga, DC2_CL + DC2_OC + '100*oc((time-0.6)/0.3)');
        }
        for (k = 0; k < strikes.length; k++) {
            var g = jzGrp(S, 'strike ' + (k + 1)); jzAddPath(g, strikes[k], false); dc2_stroke(g, col, 2.4 * u, 95, true);
            jzAddTrimPaths(g, DC2_CL + DC2_IOC + '100*ioc((time-' + jzN(0.3 + k * 0.14) + ')/0.25)');
        }
        jzNoGhost(S);
        dc2_op(ctx, S, null, a * 100);
    }
});

/* ---- highlightMark \u2014 \u86CD\u5149\u30DE\u30FC\u30AB\u30FC: a highlighter swipe under the lower half of each lyric line (screen / multiply, glyphs stay crisp).
   Lines come from the lyric's text layers (line breaks split a layer); without them the whole box is one line. */
jzReg('decor', 'highlightMark', {
    back: false,
    build: function (ctx, bb0, d) {
        var bb = dc2_bb(ctx, bb0), sc = ctx.sc, u = dc2_u(ctx), s = d.seed, dk = dc2_dark(ctx), i, k;
        var cands = [sc.accent, sc.accent2, sc.ghostA, sc.ghostB, sc.sub], lighten = jzLum(sc.fg) > jzLum(sc.bg), lb = jzLum(sc.bg), pool = [], col = null, weak = 1;
        for (i = 0; i < cands.length; i++) if (cands[i] && (lighten ? jzLum(cands[i]) > lb + 0.06 : jzLum(cands[i]) < lb - 0.06)) pool.push(cands[i]);
        for (i = 0; i < pool.length; i++) if (jzContrast(pool[i], sc.fg) >= 1.6) { col = pool[i]; break; }
        if (!col) { if (!pool.length) return; col = pool[0]; for (i = 1; i < pool.length; i++) if (jzContrast(pool[i], sc.fg) > jzContrast(col, sc.fg)) col = pool[i]; weak = 0.7; }
        var vert = bb.h > bb.w * 1.25, lines = dc2_lines(ctx, bb, vert), S = jzShapeLayer(ctx, 'highlighter', 0, 0), M = 14;
        for (k = 0; k < Math.min(4, lines.length); k++) {
            var Ln = lines[k], sz = vert ? Ln.x1 - Ln.x0 : Ln.y1 - Ln.y0, pts = [];
            var a0 = (vert ? Ln.y0 : Ln.x0) - sz * 0.12, a1 = (vert ? Ln.y1 : Ln.x1) + sz * 0.12;
            var c0 = vert ? Ln.x0 + sz * 0.52 : Ln.y0 + sz * 0.46, c1 = vert ? Ln.x1 + sz * 0.06 : Ln.y1 + sz * 0.06, sl = sz * 0.18, t, a;
            for (i = 0; i <= M; i++) { t = i / M; a = jzLerp(a0 + sl, a1, t); pts.push(vert ? [c1 + dc2_noise(t * 4, s) * 1.5 * u, a] : [a, c0 + dc2_noise(t * 4, s) * 1.5 * u]); }
            for (i = M; i >= 0; i--) { t = i / M; a = jzLerp(a0, a1 - sl, t); pts.push(vert ? [c0 + dc2_noise(t * 4 + 9, s) * 1.5 * u, a] : [a, c1 + dc2_noise(t * 4 + 9, s) * 1.5 * u]); }
            var g = jzGrp(S, 'swipe ' + (k + 1)), piv = vert ? [(c0 + c1) / 2, a0] : [a0, (c0 + c1) / 2];
            jzAddPath(g, pts, true); jzAddFill(g, col, (dk ? 60 : 62) * weak);
            jzGX(g).property('ADBE Vector Anchor').setValue(piv); jzGX(g).property('ADBE Vector Position').setValue(piv);
            dc2_gx(g, 'ADBE Vector Scale', DC2_CL + DC2_IOC + 'var e=ioc((time-' + jzN(0.1 + k * 0.22) + ')/0.4);' + (vert ? '[100,100*e]' : '[100*e,100]'));
        }
        S.blendingMode = lighten ? BlendingMode.SCREEN : BlendingMode.MULTIPLY;
        jzNoGhost(S);
        dc2_op(ctx, S);
    }
});

/* ---- heartsStars \u2014 \u30CF\u30FC\u30C8\u3068\u661F: little hearts / stars popping around the lyric, bobbing (filled ones ghost, outlined ones don't) */
jzReg('decor', 'heartsStars', {
    back: false,
    build: function (ctx, bb0, d) {
        var bb = dc2_bb(ctx, bb0), W = ctx.W, H = ctx.H, sc = ctx.sc, u = dc2_u(ctx), s = d.seed, i;
        var K = Math.min(7, 3 + (d.n | 0) + (d.big ? 1 : 0)), v = (d.v | 0) % 3, pad = 26 * u + bb.h * 0.15;
        var X0 = bb.x0 - pad, X1 = bb.x1 + pad, Y0 = bb.y0 - pad, Y1 = bb.y1 + pad, per = 2 * (X1 - X0 + Y1 - Y0);
        var cols = [sc.accent, sc.fg, sc.accent2 && jzContrast(sc.accent2, sc.bg) > 1.8 ? sc.accent2 : sc.accent], SF = null, SL = null;
        for (i = 0; i < K; i++) {
            var p = dc2_perim(X0, Y0, X1, Y1, (i + 0.3 + dc2_r(s, i, 1) * 0.4) / K * per);
            var x = jzClamp(p[0], 24 * u, W - 24 * u), y = jzClamp(p[1], 24 * u, H - 24 * u);
            if (dc2_clear(bb, x, y, 6 * u, 1) < 1) continue;
            var sz = (i === 0 ? 36 : 18 + dc2_r(s, i, 2) * 14) * u, isHeart = v === 0 || (v === 2 && i % 2 === 0);
            var pts = isHeart ? dc2_heart(sz) : dc2_star5(sz, sz * 0.45), line = dc2_r(s, i, 4) < 0.35, S;
            if (line) { if (!SL) SL = jzShapeLayer(ctx, 'hearts outline', 0, 0); S = SL; } else { if (!SF) SF = jzShapeLayer(ctx, 'hearts', 0, 0); S = SF; }
            var g = jzGrp(S, (isHeart ? 'heart ' : 'star ') + (i + 1));
            jzAddPath(g, pts, true);
            if (line) { var sk = jzAddStroke(g, cols[i % 3], 2.2 * u); try { sk.property('ADBE Vector Stroke Line Join').setValue(2); } catch (e) {} }
            else jzAddFill(g, cols[i % 3]);
            dc2_gx(g, 'ADBE Vector Position', '[' + jzN(x) + ',' + jzN(y) + '+Math.sin(time*3+' + jzN(i * 1.7) + ')*' + jzN(3 * u) + ']');
            dc2_gx(g, 'ADBE Vector Rotation', 'Math.sin(time*2+' + i + ')*10+' + jzN(dc2_rs(s, i, 3) * 15));
            dc2_gx(g, 'ADBE Vector Scale', dc2_hd(ctx, DC2_OB + DC2_IC) + 'var q=cl((T-' + jzN(0.05 + i * 0.07) + ')/0.3),k=(q<=0?0:ob(q,2.4))*(1-ic(PO));[100*k,100*k]');
        }
        if (SF) dc2_op(ctx, SF);
        if (SL) { jzNoGhost(SL); dc2_op(ctx, SL); }
    }
});

// ================================================================ type ornaments

/* ---- watermarkKanji \u2014 \u900F\u304B\u3057\u5927\u6F22\u5B57: one character of the lyric, huge and dim, cropped by the screen edge, sinking slowly */
jzReg('decor', 'watermarkKanji', {
    back: true,
    build: function (ctx, bb, d) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, u = dc2_u(ctx), v = d.v | 0;
        var ch = dc2_lyricChar(ctx, v); if (!ch) return;
        var S0 = Math.min(H * 0.86, W * 0.92), outline = v % 2 === 1, font = v % 3 === 0 ? ctx.st.fonts.display[0] : jzSerifF(ctx);
        var y = H * 0.5 + (d.low ? 1 : -1) * H * 0.06;
        var o = outline ? { font: font, size: S0, color: sc.sub || sc.fg, fill: false, stroke: 1.6 * u, strokeColor: sc.sub || sc.fg, x: 0, y: y }
            : { font: font, size: S0, color: sc.dim || jzLayC(sc, 0.12), x: 0, y: y };
        var L = jzText(ctx, ch, o);
        var E = DC2_CL + DC2_OC + 'var e=oc(time/0.6),k=1.06-0.06*e;';
        jzSetExpr(jzXf(L, 'ADBE Scale'), E + '[value[0]*k,value[1]*k]');
        jzSetExpr(jzXf(L, 'ADBE Position'), E + '[' + (d.right ? jzN(W) + '-' + jzN(S0 * 0.3) + '*k' : jzN(S0 * 0.3) + '*k') + ',value[1]-time*' + jzN(5 * u) + ']');
        jzNoGhost(L);
        dc2_op(ctx, L, 'oc(time/0.6)', outline ? 32 : 100);
    }
});

/* ---- verticalStrip \u2014 \u7E26\u66F8\u304D\u5E2F: the whole line set small and vertical at a screen edge (typed on), a hairline rule and the line number */
jzReg('decor', 'verticalStrip', {
    back: false,
    build: function (ctx, bb0, d) {
        var bb = dc2_bb(ctx, bb0), W = ctx.W, H = ctx.H, sc = ctx.sc, u = dc2_u(ctx), m = dc2_mg(ctx), c = ctx.cut, i, j;
        var all = jzChars(String(c.lineText || c.text || '').replace(/\s+/g, '\u3000')).slice(0, 26);
        if (!jzTrim(all.join(''))) return;
        var fs = jzClamp(jzU(ctx) * 0.022, 15 * u, 24 * u), track = 0.28, step = fs * (1 + track), clear = 44 * u, best = null;
        var font = (d.v | 0) % 2 ? jzSerifF(ctx) : jzBodyF(ctx), rs = [!!d.right, !d.right], ts = [!d.low, !!d.low];
        for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) {
            var right = rs[i], top = ts[j], x = right ? W - m * 0.95 : m * 0.95, hClear = x + fs * 1.8 < bb.x0 - clear || x - fs * 1.8 > bb.x1 + clear;
            var y0 = top ? m * 1.6 + fs : null, yEnd = top ? null : H - m * 1.3;
            var avail = hClear ? H - m * 2.9 - fs : top ? bb.y0 - clear - y0 : yEnd - (bb.y1 + clear);
            var n = Math.min(all.length, Math.floor(avail / step));
            if (n >= 4 && (!best || n > best.n + 1)) best = { right: right, top: top, x: x, y0: y0, yEnd: yEnd, n: n };
        }
        if (!best) return;
        var chars = all.slice(0, best.n), th = best.n * step - fs * track, Y0 = best.top ? best.y0 : best.yEnd - th, X = best.x;
        var L = jzText(ctx, chars.join('\r'), { font: font, size: fs, color: sc.fg, x: X, y: Y0, align: 'center', leading: step });
        var r = jzRect(L);
        jzXf(L, 'ADBE Anchor Point').setValue([r.left + r.width / 2, r.top]); jzXf(L, 'ADBE Position').setValue([X, Y0]);
        jzAnimator(L, 'JZ Type On', [['ADBE Text Opacity', 0]], 'var sh=Math.ceil(' + best.n + '*Math.max(0,Math.min(1,(time-0.08)/0.55)));textIndex>sh?100:0');
        jzNoGhost(L); dc2_op(ctx, L, null, 90);
        var rx = X + (best.right ? -1 : 1) * fs * 1.1, S = jzShapeLayer(ctx, 'strip rule', 0, 0);
        var gt = jzGrp(S, 'tick'); jzAddRect(gt, 4 * u, 8 * u, 0, rx, Y0 - fs * 0.2 - 8 * u); jzAddFill(gt, sc.accent);
        dc2_gx(gt, 'ADBE Vector Group Opacity', DC2_CL + DC2_OE + '100*oe(time/0.6)');
        var gr = jzGrp(S, 'rule'); jzAddPath(gr, [[rx, Y0 - fs * 0.2], [rx, Y0 - fs * 0.2 + th + fs * 0.4]], false); jzAddStroke(gr, sc.sub || sc.fg, Math.max(1, u), 70);
        jzAddTrimPaths(gr, DC2_CL + DC2_OE + '100*oe(time/0.6)');
        jzNoGhost(S); dc2_op(ctx, S);
        var lab = jzText(ctx, jzLineNo(ctx), { font: jzMonoF(ctx), size: dc2_fs(ctx) * 0.75, color: sc.sub || sc.fg, x: X, y: Y0 - fs * 1.6, align: 'center', track: 0.08 });
        jzNoGhost(lab); dc2_op(ctx, lab, 'oe(time/0.6)');
    }
});

/* ---- romajiLine \u2014 \u30ED\u30FC\u30DE\u5B57: a thin, widely tracked latin line under (or over) the lyric that decodes letter by letter */
jzReg('decor', 'romajiLine', {
    back: false,
    build: function (ctx, bb0, d) {
        var bb = dc2_bb(ctx, bb0), W = ctx.W, H = ctx.H, sc = ctx.sc, u = dc2_u(ctx), m = dc2_mg(ctx), c = ctx.cut, i;
        var raw = jzTrim(String(c.text || '').replace(/\s+/g, ' ')); if (!raw) return;
        var str = c.note ? String(c.note) : null;
        if (!str) {
            var rj = jzRomaji(raw.replace(/[\u3001\u3002\uFF01\uFF1F!?,.\u30FB\u300C\u300D]/g, ' '));
            if (rj) str = jzTrim(rj.toUpperCase().replace(/\s+/g, ' '));
            else { var cc = jzChars(raw), o = []; for (i = 0; i < cc.length && o.length < 6; i++) if (jzTrim(cc[i])) o.push('U+' + cc[i].charCodeAt(0).toString(16).toUpperCase()); str = o.join(' '); }
        }
        var font = (d.v | 0) % 2 ? jzBodyF(ctx) : jzMonoF(ctx), track = 0.34;
        var fs = jzClamp(jzU(ctx) * 0.018, 12 * u, 20 * u), maxW = Math.min(W - m * 2.4, Math.max(bb.w * 1.15, W * 0.4));
        var L = jzText(ctx, str, { font: font, size: fs, color: sc.sub || sc.fg, x: 0, y: 0, align: 'left', track: track });
        var src = L.property('ADBE Text Properties').property('ADBE Text Document');
        function setT(t2, f2) { var td = src.value; td.text = t2; if (f2) td.fontSize = f2; src.setValue(td); return jzSize(L)[0]; }
        var tw = jzSize(L)[0];
        if (tw > maxW) { fs = Math.max(10 * u, fs * maxW / tw); tw = setT(str, fs); }
        var guard = 0;
        while (tw > maxW && str.length > 4 && guard++ < 60) { str = str.slice(0, -2); tw = setT(str + '...'); if (tw <= maxW) { str += '...'; break; } }
        tw = setT(str);
        jzAnchor(L, 'left');
        var gap = 18 * u + bb.h * 0.1, below = !!d.low, y = below ? bb.y1 + gap + fs * 0.5 : bb.y0 - gap - fs * 0.5;
        if (y < m * 0.6 || y > H - m * 0.6) { below = !below; y = below ? bb.y1 + gap + fs * 0.5 : bb.y0 - gap - fs * 0.5; }
        var cx = jzClamp(bb.cx, m + tw / 2, W - m - tw / 2);
        jzXf(L, 'ADBE Position').setValue([cx - tw / 2, y]);
        // decode: hidden until 0.22 s before its turn, random capitals meanwhile (Character Offset within A\u2013Z), then the letter
        var gl = jzGlyphs(str), codes = [], n = gl.length;
        for (i = 0; i < n; i++) { var k = gl[i].charCodeAt(0); codes.push(k >= 65 && k <= 90 ? k - 65 : -1); }
        var TI = 'var i=textIndex-1,ti=0.06+i/' + Math.max(1, n) + '*0.5;';
        jzAnimator(L, 'JZ Hide', [['ADBE Text Opacity', 0]], TI + 'time<ti-0.22?100:0');
        jzAnimator(L, 'JZ Decode', [['ADBE Text Character Offset', 25]], DC2_HH + TI + 'var P=' + jzArrExpr(codes) + ',p=P[i],r=0;if(p>=0&&time<ti&&time>=ti-0.22){var q=Math.floor(hh(' + jzN(d.seed % 9973) + '+i*13.1+Math.floor(time*12)*7.7)*26);r=(q-p)/25*100;}r');
        jzNoGhost(L); dc2_op(ctx, L, 'oc(time/0.2)');
        var g2 = 14 * u, hl = 36 * u, S = jzShapeLayer(ctx, 'romaji rules', 0, 0), gr = jzGrp(S, 'rules');
        jzAddPath(gr, [[cx - tw / 2 - g2, y], [cx - tw / 2 - g2 - hl, y]], false); jzAddPath(gr, [[cx + tw / 2 + g2, y], [cx + tw / 2 + g2 + hl, y]], false);
        jzAddStroke(gr, d.accent ? sc.accent : (sc.sub || sc.fg), Math.max(1, u), 80);
        jzAddTrimPaths(gr, DC2_CL + DC2_OE + '100*oe((time-0.1)/0.5)');
        jzNoGhost(S); dc2_op(ctx, S);
    }
});

/* ---- bracketsJP \u2014 \u9685\u4ED8\u304D\u62EC\u5F27: bold \u3010 \u3011 clasping the lyric (\uFE3B \uFE3C over / under vertical text), sliding in and out */
function dc2_lentil(xo, yT, yB, aw, tb, side) {
    var pts = [[xo, yT], [xo - side * aw, yT]], M = 16, h = yB - yT, i;
    for (i = 1; i < M; i++) { var t = i / M, b = Math.pow(Math.sin(t * Math.PI), 0.7); pts.push([xo - side * (aw - (aw - tb) * b), yT + h * t]); }
    pts.push([xo - side * aw, yB], [xo, yB]);
    return pts;
}
jzReg('decor', 'bracketsJP', {
    back: false,
    build: function (ctx, bb0, d) {
        var bb = dc2_bb(ctx, bb0), W = ctx.W, H = ctx.H, sc = ctx.sc, u = dc2_u(ctx), i;
        var vert = bb.h > bb.w * 1.25, col = d.accent ? sc.accent : sc.fg, S = jzShapeLayer(ctx, 'brackets JP', 0, 0);
        var HD = dc2_hd(ctx, DC2_OE + DC2_IC) + 'var e=oe(T/0.45),sl=(1-e+ic(PO)*0.6)*' + jzN(60 * u) + ',k=0.5+0.5*e;';
        var aw, tb, gap, g;
        if (!vert) {
            var h = jzClamp(bb.h * 1.08 + 16 * u, 40 * u, H * 0.6), yc = bb.cy;
            aw = Math.min(h * 0.24, 26 * u + jzU(ctx) * 0.028); tb = aw * 0.3; gap = 12 * u + bb.h * 0.05;
            var xl = Math.max(bb.x0 - gap, aw + 6 * u), xr = Math.min(bb.x1 + gap, W - aw - 6 * u);
            var sides = [[xl - aw, -1], [xr + aw, 1]];
            for (i = 0; i < 2; i++) {
                var xo = sides[i][0], sd = sides[i][1];
                g = jzGrp(S, sd < 0 ? 'left' : 'right');
                jzAddPath(g, dc2_lentil(xo, yc - h / 2, yc + h / 2, aw, tb, sd), true); jzAddFill(g, col);
                jzGX(g).property('ADBE Vector Anchor').setValue([xo, yc]);
                dc2_gx(g, 'ADBE Vector Position', HD + '[' + jzN(xo) + (sd < 0 ? '-' : '+') + 'sl,' + jzN(yc) + ']');
                dc2_gx(g, 'ADBE Vector Scale', HD + '[100,100*k]');
            }
        } else {
            var wv = jzClamp(bb.w * 1.05 + 16 * u, 40 * u, W * 0.6), xc = bb.cx, xL = xc - wv / 2, M = 16;
            aw = Math.min(wv * 0.24, 26 * u + jzU(ctx) * 0.028); tb = aw * 0.3; gap = 12 * u + bb.w * 0.05;
            var yt = Math.max(bb.y0 - gap, aw + 6 * u), yb = Math.min(bb.y1 + gap, H - aw - 6 * u), ends = [[yt, -1], [yb, 1]];
            for (i = 0; i < 2; i++) {
                var yi = ends[i][0], dir = ends[i][1], p = [[xL, yi + dir * aw], [xL, yi]], j;
                for (j = 1; j < M; j++) { var t = j / M, b = Math.pow(Math.sin(t * Math.PI), 0.7); p.push([xL + wv * t, yi + dir * (aw - tb) * b]); }
                p.push([xL + wv, yi], [xL + wv, yi + dir * aw]);
                g = jzGrp(S, dir < 0 ? 'top' : 'bottom');
                jzAddPath(g, p, true); jzAddFill(g, col);
                jzGX(g).property('ADBE Vector Anchor').setValue([xc, yi]);
                dc2_gx(g, 'ADBE Vector Position', HD + '[' + jzN(xc) + ',' + jzN(yi) + (dir < 0 ? '-' : '+') + 'sl]');
                dc2_gx(g, 'ADBE Vector Scale', HD + '[100*k,100]');
            }
        }
        dc2_op(ctx, S, 'cl(time/0.12)');
    }
});

/* ---- seal \u2014 \u843D\u6B3E: a red seal stamp with one character of the lyric, pressed beside it (drops in large and tilted, settles) */
jzReg('decor', 'seal', {
    back: false,
    build: function (ctx, bb0, d) {
        var bb = dc2_bb(ctx, bb0), sc = ctx.sc, u = dc2_u(ctx), s = d.seed, v = d.v | 0, i, k;
        var ch = dc2_lyricChar(ctx, 3 + v); if (!ch) return;
        var S0 = jzClamp(jzU(ctx) * 0.074, 48 * u, 92 * u), h = S0 / 2, round = v % 2 === 1;
        var sp = dc2_near(ctx, bb, S0 * 1.3, S0 * 1.3, { right: v % 3 !== 2, low: true, corner: d.corner, v: d.v }, 14 * u), ok = sp.ok ? 1 : 0.4;
        var S = jzShapeLayer(ctx, 'seal', sp.cx, sp.cy), pts = [];
        if (round) { for (i = 0; i < 28; i++) { var an = i / 28 * Math.PI * 2, rr = h * (1 + dc2_rs(s, i, 2) * 0.025); pts.push([Math.cos(an) * rr, Math.sin(an) * rr]); } }
        else {
            var cn = [[-h, -h], [h, -h], [h, h], [-h, h]];
            for (k = 0; k < 4; k++) { var p0 = cn[k], p1 = cn[(k + 1) % 4]; for (i = 0; i < 4; i++) { var t = i / 4, jt = dc2_rs(s, k, i, 3) * 1.3 * u; pts.push([jzLerp(p0[0], p1[0], t) + (k % 2 ? jt : 0), jzLerp(p0[1], p1[1], t) + (k % 2 ? 0 : jt)]); } }
        }
        var gsp = jzGrp(S, 'speckles');
        for (i = 0; i < 9; i++) { var rd = (0.6 + dc2_r(s, i, 7) * 1.6) * u; jzAddEllipse(gsp, rd * 2, rd * 2, dc2_rs(s, i, 5) * h * 0.9, dc2_rs(s, i, 6) * h * 0.9); }
        jzAddFill(gsp, sc.bg, 70);
        if (v % 3 === 2) {
            var gi = jzGrp(S, 'inner');
            if (round) jzAddEllipse(gi, h * 1.68, h * 1.68); else jzAddRect(gi, h * 1.68, h * 1.68);
            jzAddStroke(gi, sc.bg, 1.6 * u);
        }
        var gb = jzGrp(S, 'stamp'); jzAddPath(gb, pts, true); jzAddFill(gb, sc.accent, 95);
        var serif = jzSerifF(ctx), T = jzText(ctx, ch, { font: serif === 'mincho_light' ? 'mincho_bold' : serif, size: S0 * 0.62, color: sc.bg, x: sp.cx, y: sp.cy + S0 * 0.02 });
        var HD = DC2_CL + DC2_OC + 'var p=cl((time-0.08)/0.3),q=oc(p);';
        var L2 = [S, T];
        for (i = 0; i < 2; i++) {
            jzSetExpr(jzXf(L2[i], 'ADBE Scale'), HD + 'var k=1.5-0.5*q;[value[0]*k,value[1]*k]');
            jzSetExpr(jzXf(L2[i], 'ADBE Rotate Z'), HD + jzN(dc2_rs(s, 1) * 5) + '-(1-q)*12');
            jzNoGhost(L2[i]);
            dc2_op(ctx, L2[i], 'cl(cl((time-0.08)/0.3)*4)', ok * 100);
        }
    }
});

// ================================================================ decor B part 1 (AE port of 27 decor entries in src/11p_decorB.js)
// Japanese motifs (kamon \u2026 kasumi), sci-fi HUD (hexGrid \u2026 circuit), print & stationery (swatches \u2026 indexTabs).
// decor.build(ctx, bb, d): layers live in the CONTENT comp (time 0 = cut start). Every entry here is drawn by the browser on the
// main pass only, so all its layers are kept out of the tinted ghosts (jzNoGhost, done once in db1_reg).
// Entries the browser wraps in withSettle() start after the lyric's entrance has settled: expressions use t = time - ST.

// ---------------------------------------------------------------- shared helpers (prefix db1_)
// the browser's deterministic hash J.h / J.r / J.rs (so seeded placements match the web frame)
function db1_h(a, b, c, d, e) {
    var h = 0x9e3779b9 ^ (a | 0);
    h = jzImul(h ^ (h >>> 16), 0x85ebca6b);
    h = (h + jzImul((b | 0) + 0x632be5ab, 0xc2b2ae35)) | 0;
    h = jzImul(h ^ (h >>> 13), 0xc2b2ae35);
    h = (h + jzImul((c | 0) + 0x5bd1e995, 0x27d4eb2f)) | 0;
    h = jzImul(h ^ (h >>> 15), 0x165667b1);
    h = (h + jzImul((d | 0) + 0x1b873593, 0x85ebca6b)) | 0;
    h = jzImul(h ^ (h >>> 16), 0x27d4eb2f);
    h = (h + jzImul((e | 0) + 0x68e31da4, 0x9e3779b1)) | 0;
    h ^= h >>> 15; h = jzImul(h, 0x2c1b3c6d); h ^= h >>> 12; h = jzImul(h, 0x297a2d39); h ^= h >>> 15;
    return h >>> 0;
}
function db1_r(a, b, c, d, e) { return db1_h(a, b, c, d, e) / 4294967296; }
function db1_rs(a, b, c, d, e) { return db1_r(a, b, c, d, e) * 2 - 1; }
var DB1_D = Math.PI / 180;
function db1_u(ctx) { return jzU(ctx) / 1080; }                       // browser U(env) at comp scale
function db1_mg(ctx) { return Math.round(jzU(ctx) * 0.05); }          // safe margin
function db1_fs(ctx) { return Math.max(12, 16 * db1_u(ctx)); }       // small label size
function db1_dark(sc) { return jzLum(sc.bg) < 0.5; }
function db1_vis(sc, c, mn) { return (c && jzContrast(c, sc.bg) >= (mn || 1.5)) ? c : sc.fg; }
function db1_acc(sc) { return db1_vis(sc, sc.accent); }
function db1_acc2(sc) { return db1_vis(sc, sc.accent2 || sc.accent); }
function db1_settle(ctx) { return jzClamp((ctx.cut.inDur || 0) * 0.6, 0, 0.35); }
function db1_pad(n, k) { return jzPad(Math.max(0, Math.floor(n)), k || 2); }
// colour as an expression literal [r,g,b,1]
function db1_cx(hex) { var c = jzHex(hex); return '[' + jzN(c[0]) + ',' + jzN(c[1]) + ',' + jzN(c[2]) + ',1]'; }
// expression header: jzTH (DUR IN OS OD SD, easings, PO, K) + ioc / wr + t (local time after the settle delay), V (started)
var DB1_FNS = 'function ioc(x){x=cl(x);return x<0.5?4*x*x*x:1-Math.pow(-2*x+2,3)/2;}function wr(v,lo,sp){return lo+((v-lo)%sp+sp)%sp;}\n';
function db1_H(ctx, st) { return jzTH(ctx) + DB1_FNS + 'var ST=' + jzN(st || 0) + ',t=time-ST,V=t<0?0:1;\n'; }
// layer opacity = value \u00D7 started \u00D7 exit fade \u00D7 extra factor
function db1_op(ctx, L, st, ex) { jzSetExpr(jzXf(L, 'ADBE Opacity'), db1_H(ctx, st) + 'value*V*K' + (ex ? '*(' + ex + ')' : '')); }
function db1_sub(g, name) { var s = jzVecs(g).addProperty('ADBE Vector Group'); if (name) s.name = name; return s; }
function db1_gx(ctx, g, mn, st, ex) { jzSetExpr(jzGX(g).property(mn), db1_H(ctx, st) + ex); }
function db1_gop(ctx, g, st, ex, pre) { db1_gx(ctx, g, 'ADBE Vector Group Opacity', st, (pre || '') + 'value*(' + ex + ')'); }
function db1_st(g, col, w, op, cap, join) {
    var s = jzAddStroke(g, col, w, op);
    try { if (cap) s.property('ADBE Vector Stroke Line Cap').setValue(cap); if (join) s.property('ADBE Vector Stroke Line Join').setValue(join); } catch (e) {}
    return s;
}
// each dash entry is set right after it is added (adding the gap would invalidate a held reference to the dash);
// the preview model has fixed Dashes children (addProperty refused) -> fetch by matchName instead
function db1_dash(s, on, off) {
    var D = s.property('ADBE Vector Stroke Dashes'), mn = ['ADBE Vector Stroke Dash 1', 'ADBE Vector Stroke Gap 1'], v = [on, off], i, p;
    for (i = 0; i < 2; i++) {
        p = null;
        try { p = D.addProperty(mn[i]); } catch (e) { p = null; }
        if (!p) { try { p = D.property(mn[i]); } catch (e1) { p = null; } }
        try { if (p) p.setValue(v[i]); } catch (e2) {}
    }
}
// trim paths with expressions (end / start / offset), all in one header
function db1_trim(ctx, g, st, endEx, startEx, offEx, pre) {
    var tr = jzVecs(g).addProperty('ADBE Vector Filter - Trim'), H = db1_H(ctx, st) + (pre || '');
    if (endEx != null) jzSetExpr(tr.property('ADBE Vector Trim End'), H + 'Math.max(0,Math.min(100,' + endEx + '))');
    if (startEx != null) jzSetExpr(tr.property('ADBE Vector Trim Start'), H + 'Math.max(0,Math.min(100,' + startEx + '))');
    if (offEx != null) jzSetExpr(tr.property('ADBE Vector Trim Offset'), H + offEx);
    return tr;
}
// circle arc: ellipse r around (x, y) starting at a0 (deg, 0 = right, clockwise) and sweeping sweepEx degrees
function db1_arcTrim(ctx, g, r, x, y, a0, st, sweepEx) {
    jzAddEllipse(g, r * 2, r * 2, x, y);
    var tr = db1_trim(ctx, g, st, '(' + sweepEx + ')/3.6');
    tr.property('ADBE Vector Trim Offset').setValue(a0 + 90);
    return tr;
}
function db1_rep(g, n, px, py, rot, off) {
    var rp = jzVecs(g).addProperty('ADBE Vector Filter - Repeater');
    rp.property('ADBE Vector Repeater Copies').setValue(n);
    if (off) rp.property('ADBE Vector Repeater Offset').setValue(off);
    var rt = rp.property('ADBE Vector Repeater Transform');
    rt.property('ADBE Vector Repeater Position').setValue([px || 0, py || 0]);
    if (rot) rt.property('ADBE Vector Repeater Rotation').setValue(rot);
    return rp;
}
// point lists
function db1_arc(cx, cy, r, a0, a1, n) { var o = [], i; n = n || 48; for (i = 0; i <= n; i++) { var a = (a0 + (a1 - a0) * i / n) * DB1_D; o.push([cx + Math.cos(a) * r, cy + Math.sin(a) * r]); } return o; }
function db1_ell(cx, cy, rx, ry, rot, a0, a1, n) { var o = [], c = Math.cos(rot), s = Math.sin(rot), i; for (i = 0; i <= n; i++) { var a = (a0 + (a1 - a0) * i / n) * DB1_D, x = Math.cos(a) * rx, y = Math.sin(a) * ry; o.push([cx + x * c - y * s, cy + x * s + y * c]); } return o; }
function db1_xf(pts, cx, cy, ang, s) { var c = Math.cos(ang || 0), sn = Math.sin(ang || 0), o = [], i; s = s == null ? 1 : s; for (i = 0; i < pts.length; i++) o.push([cx + (pts[i][0] * c - pts[i][1] * sn) * s, cy + (pts[i][0] * sn + pts[i][1] * c) * s]); return o; }
function db1_bez(p0, p1, p2, p3, n) { var o = [], i; for (i = 0; i <= n; i++) { var t = i / n, m = 1 - t; o.push([m * m * m * p0[0] + 3 * m * m * t * p1[0] + 3 * m * t * t * p2[0] + t * t * t * p3[0], m * m * m * p0[1] + 3 * m * m * t * p1[1] + 3 * m * t * t * p2[1] + t * t * t * p3[1]]); } return o; }
function db1_off(pts, dx, dy) { var o = [], i; for (i = 0; i < pts.length; i++) o.push([pts[i][0] + dx, pts[i][1] + dy]); return o; }
function db1_ellShape(cx, cy, rx, ry) {
    var kx = 0.5523 * rx, ky = 0.5523 * ry, sh = new Shape();
    sh.vertices = [[cx, cy - ry], [cx + rx, cy], [cx, cy + ry], [cx - rx, cy]];
    sh.inTangents = [[-kx, 0], [0, -ky], [kx, 0], [0, ky]];
    sh.outTangents = [[kx, 0], [0, ky], [-kx, 0], [0, -ky]];
    sh.closed = true; return sh;
}
function db1_rectShape(x0, y0, x1, y1) { var sh = new Shape(); sh.vertices = [[x1, y0], [x1, y1], [x0, y1], [x0, y0]]; sh.closed = true; return sh; }
function db1_mask(L, sh, mode, feather) {
    var m = L.property('ADBE Mask Parade').addProperty('ADBE Mask Atom');
    m.property('ADBE Mask Shape').setValue(sh);
    if (mode) m.maskMode = mode;
    if (feather) m.property('ADBE Mask Feather').setValue([feather, feather]);
    return m;
}
// small secondary text (mono, sub colour, left aligned, like the browser's label())
function db1_label(ctx, text, x, y, o) {
    o = o || {};
    return jzText(ctx, String(text), { font: o.font || jzMonoF(ctx), size: o.size || db1_fs(ctx), color: o.color || ctx.sc.sub, x: x, y: y,
        align: o.align || 'left', track: o.track == null ? 0.08 : o.track, rot: o.rot, opacity: o.alpha == null ? 1 : o.alpha, name: o.name });
}
// a text colour animator switched on by an amount expression (0..100)
function db1_txtCol(ctx, L, hex, st, amtEx) { return jzAnimator(L, 'JZ Colour', [['ADBE Text Fill Color', jzHex(hex)]], db1_H(ctx, st) + amtEx); }
// source text driven by an expression
function db1_srcText(ctx, L, st, ex) { try { L.property('ADBE Text Properties').property('ADBE Text Document').expression = db1_H(ctx, st) + ex; } catch (e) { jzWarn('db1 source text: ' + e.toString()); } }

// lyric bbox clamped like the browser's getBB
function db1_bb(ctx, bb) {
    var W = ctx.W, H = ctx.H;
    var b = { x0: Math.max(bb.x0, -W * 0.1), x1: Math.min(bb.x1, W * 1.1), y0: Math.max(bb.y0, -H * 0.1), y1: Math.min(bb.y1, H * 1.1) };
    if (b.x1 <= b.x0) { b.x0 = bb.x0; b.x1 = bb.x1; }
    if (b.y1 <= b.y0) { b.y0 = bb.y0; b.y1 = bb.y1; }
    b.cx = bb.cx != null ? bb.cx : (b.x0 + b.x1) / 2; b.cy = bb.cy != null ? bb.cy : (b.y0 + b.y1) / 2;
    return b;
}
function db1_bw(bb) { return bb.x1 - bb.x0; }
function db1_bh(bb) { return bb.y1 - bb.y0; }
function db1_isVert(bb) { return db1_bh(bb) > db1_bw(bb) * 1.25; }
function db1_hit(x0, y0, x1, y1, bb, pad) { pad = pad || 0; return !(x1 < bb.x0 - pad || x0 > bb.x1 + pad || y1 < bb.y0 - pad || y0 > bb.y1 + pad); }
// a screen corner (inside the margin) that keeps clear of the lyric
function db1_corner(ctx, bb, w, h, P, mk) {
    var W = ctx.W, H = ctx.H, m = db1_mg(ctx) * (mk || 1), sx0 = P.right ? 1 : -1, sy0 = P.low ? 1 : -1;
    var order = [[sx0, sy0], [-sx0, sy0], [sx0, -sy0], [-sx0, -sy0]], best = null, bestA = 1e18, i;
    for (i = 0; i < 4; i++) {
        var sx = order[i][0], sy = order[i][1], X = sx > 0 ? W - m - w : m, Y = sy > 0 ? H - m - h : m;
        if (!db1_hit(X, Y, X + w, Y + h, bb, 8)) return { x: X, y: Y, cx: X + w / 2, cy: Y + h / 2, ok: true, sx: sx, sy: sy };
        var ov = Math.max(0, Math.min(X + w, bb.x1) - Math.max(X, bb.x0)) * Math.max(0, Math.min(Y + h, bb.y1) - Math.max(Y, bb.y0));
        if (ov < bestA) { bestA = ov; best = { x: X, y: Y, cx: X + w / 2, cy: Y + h / 2, ok: false, sx: sx, sy: sy }; }
    }
    return best;
}
// place a w\u00D7h box next to the lyric (outside it, inside the safe margin)
function db1_near(ctx, bb, w, h, P, gap) {
    var W = ctx.W, H = ctx.H, m = db1_mg(ctx) * 0.8, sx0 = P.right ? 1 : -1, sy0 = P.low ? 1 : -1, i;
    var order = P.corner ? [[sx0, sy0], [-sx0, sy0], [sx0, -sy0], [-sx0, -sy0]] : [[sx0, sy0], [sx0, -sy0], [-sx0, sy0], [-sx0, -sy0]], tries = [];
    for (i = 0; i < 4; i++) {
        var sx = order[i][0], sy = order[i][1];
        var ax = sx > 0 ? bb.x1 - w : bb.x0, ox = sx > 0 ? bb.x1 + gap : bb.x0 - gap - w;
        var ay = sy > 0 ? bb.y1 + gap : bb.y0 - gap - h, iy = sy > 0 ? bb.y1 - h : bb.y0;
        if ((P.v | 0) % 2) { tries.push([ox, iy, sx, sy]); tries.push([ax, ay, sx, sy]); tries.push([ox, ay, sx, sy]); }
        else { tries.push([ax, ay, sx, sy]); tries.push([ox, iy, sx, sy]); tries.push([ox, ay, sx, sy]); }
    }
    for (i = 0; i < tries.length; i++) {
        var X = jzClamp(tries[i][0], m, Math.max(m, W - m - w)), Y = jzClamp(tries[i][1], m, Math.max(m, H - m - h));
        if (!db1_hit(X, Y, X + w, Y + h, bb, gap * 0.4)) return { x: X, y: Y, cx: X + w / 2, cy: Y + h / 2, ok: true, sx: tries[i][2], sy: tries[i][3] };
    }
    return db1_corner(ctx, bb, w, h, P);
}
function db1_spot(ctx, bb, w, h, P, gap) { return P.corner ? db1_corner(ctx, bb, w, h, P) : db1_near(ctx, bb, w, h, P, gap); }
// free rectangles around the lyric, biggest usable first
function db1_free(ctx, bb, gap) {
    var W = ctx.W, H = ctx.H, m = db1_mg(ctx) * 0.8, i, j;
    var all = [{ x: m, y: m, w: W - 2 * m, h: bb.y0 - gap - m }, { x: m, y: bb.y1 + gap, w: W - 2 * m, h: H - m - bb.y1 - gap },
        { x: m, y: m, w: bb.x0 - gap - m, h: H - 2 * m }, { x: bb.x1 + gap, y: m, w: W - m - bb.x1 - gap, h: H - 2 * m }], out = [];
    for (i = 0; i < all.length; i++) if (all[i].w > 30 && all[i].h > 30) out.push(all[i]);
    for (i = 1; i < out.length; i++) for (j = i; j > 0 && Math.min(out[j].w, out[j].h * 1.5) > Math.min(out[j - 1].w, out[j - 1].h * 1.5); j--) { var tmp = out[j]; out[j] = out[j - 1]; out[j - 1] = tmp; }
    return out;
}
// a character from the lyric: prefer kanji, then kana / latin
function db1_lyricChar(ctx, salt) {
    var c = ctx.cut, s = jzChars(String(c.text || c.lineText || '')), arr = [], kan = [], kana = [], i;
    for (i = 0; i < s.length; i++) if (jzTrim(s[i]) && !jzIsPunct(s[i])) arr.push(s[i]);
    if (!arr.length) return '';
    for (i = 0; i < arr.length; i++) { if (jzIsKanji(arr[i])) kan.push(arr[i]); else if (jzIsKata(arr[i]) || jzIsHira(arr[i]) || jzIsLatin(arr[i])) kana.push(arr[i]); }
    var p = kan.length ? kan : (kana.length ? kana : arr);
    return p[db1_h(c.seed, salt | 0, 5) % p.length];
}
// register an entry; all its layers are main-pass only in the browser -> keep them out of the ghosts
function db1_reg(k, back, fn) {
    jzReg('decor', k, { back: !!back, build: function (ctx, bb, d) {
        var n0 = ctx.comp.numLayers, i;
        fn(ctx, db1_bb(ctx, bb), d);
        var added = ctx.comp.numLayers - n0;
        for (i = 1; i <= added; i++) jzNoGhost(ctx.comp.layer(i));
    } });
}

// ================================================================ Japanese motifs

/* ---- kamon \u2014 \u5BB6\u7D0B: a family crest drawn on inside a double ring, its name set small beneath */
var DB1_KAMON = ['\u4E38\u306B\u4E09\u3064\u5DF4', '\u4E38\u306B\u4E03\u5B9D', '\u4E38\u306B\u6885\u9262', '\u4E38\u306B\u8F2A\u9055\u3044', '\u4E38\u306B\u4E09\u3064\u8F2A'];
function db1_kamonPaths(kind) {
    var out = [], k, i;
    if (kind === 0) {
        for (k = 0; k < 3; k++) {
            var th0 = (-90 + k * 120) * DB1_D, sw = 150 * DB1_D, h0 = 0.27, M = 30, outer = [], inner = [], cap = [];
            for (i = 0; i <= M; i++) {
                var t = i / M, th = th0 + t * sw, rc = 0.38 + 0.46 * t, w = h0 * Math.pow(1 - t, 0.85);
                outer.push([Math.cos(th) * (rc + w), Math.sin(th) * (rc + w)]);
                inner.push([Math.cos(th) * (rc - w), Math.sin(th) * (rc - w)]);
            }
            var hc = [Math.cos(th0) * 0.38, Math.sin(th0) * 0.38], rh = [Math.cos(th0), Math.sin(th0)], tn = [-Math.sin(th0), Math.cos(th0)];
            for (i = 1; i < 16; i++) { var f = i / 16 * Math.PI; cap.push([hc[0] + h0 * (-rh[0] * Math.cos(f) - tn[0] * Math.sin(f)), hc[1] + h0 * (-rh[1] * Math.cos(f) - tn[1] * Math.sin(f))]); }
            inner.reverse();
            out.push(outer.concat(inner, cap, [outer[0]]));
        }
    } else if (kind === 1) {
        for (k = 0; k < 4; k++) { var a1 = k * 90; out.push(db1_arc(Math.cos(a1 * DB1_D) * 0.5, Math.sin(a1 * DB1_D) * 0.5, 0.5, a1 + 180, a1 + 540, 40)); }
        out.push(db1_arc(0, 0, 0.13, -90, 270, 20));
    } else if (kind === 2) {
        for (k = 0; k < 5; k++) { var a2 = -90 + k * 72; out.push(db1_arc(Math.cos(a2 * DB1_D) * 0.6, Math.sin(a2 * DB1_D) * 0.6, 0.3, a2 + 180, a2 + 540, 32)); }
        out.push(db1_arc(0, 0, 0.17, -90, 270, 20));
    } else if (kind === 3) {
        out.push(db1_arc(-0.3, 0, 0.55, 0, 360, 48)); out.push(db1_arc(0.3, 0, 0.55, 180, 540, 48));
    } else {
        for (k = 0; k < 3; k++) { var a3 = -90 + k * 120; out.push(db1_arc(Math.cos(a3 * DB1_D) * 0.4, Math.sin(a3 * DB1_D) * 0.4, 0.46, a3 + 180, a3 + 540, 40)); }
    }
    return out;
}
db1_reg('kamon', false, function (ctx, bb, d) {
    var sc = ctx.sc, u = db1_u(ctx), ST = db1_settle(ctx), i;
    var R = jzClamp(jzU(ctx) * 0.075, 48 * u, 96 * u), fs = db1_fs(ctx) * 0.85;
    var sp = db1_spot(ctx, bb, R * 2.3, R * 2.3 + fs * 2.2, d, 24 * u);
    var cx = sp.cx, cy = sp.y + R * 1.15, a = sp.ok ? 1 : 0.35, kind = (d.v | 0) % 5, lw = Math.max(1, 1.5 * u);
    var S = jzShapeLayer(ctx, 'kamon', cx, cy);
    var gd = jzGrp(S, 'centre'); jzAddEllipse(gd, 4.4 * u, 4.4 * u); jzAddFill(gd, db1_acc(sc));
    db1_gop(ctx, gd, ST, 'oe((t-0.5)/0.3)');
    // motif: settles from -70\u00B0 (the tomoe / mitsuwa keep turning slowly)
    var gm = jzGrp(S, 'motif'), paths = db1_kamonPaths(kind), s = R * 0.8, col = d.accent ? db1_acc(sc) : sc.fg;
    var spin = (kind === 0 || kind === 4) ? '+t*' + (d.right ? 8 : -8) : '';
    db1_gx(ctx, gm, 'ADBE Vector Rotation', ST, '(1-oe(t/0.9))*-70' + spin + '+' + jzN(kind === 0 ? (d.r || 0) * 120 : 0));
    for (i = paths.length - 1; i >= 0; i--) {
        var gp = db1_sub(gm, 'part ' + i);
        jzAddPath(gp, db1_xf(paths[i], 0, 0, 0, s), false);
        db1_trim(ctx, gp, ST, '100*ioc((t-' + jzN(0.12 + i * 0.07) + ')/0.55)');
        db1_st(gp, col, lw * 1.1, null, 2, 2);
    }
    var g2 = jzGrp(S, 'inner ring'); db1_arcTrim(ctx, g2, R * 0.9, 0, 0, 90, ST, '360*ioc(t/0.6)'); db1_st(g2, sc.fg, Math.max(1, 0.8 * u), 55);
    var g1 = jzGrp(S, 'ring'); db1_arcTrim(ctx, g1, R, 0, 0, -90, ST, '360*ioc(t/0.6)'); db1_st(g1, sc.fg, lw);
    jzXf(S, 'ADBE Opacity').setValue(a * 100); db1_op(ctx, S, ST);
    var T = db1_label(ctx, DB1_KAMON[kind], cx, cy + R + fs * 1.35, { font: jzSerifF(ctx), size: fs, align: 'center', track: 0.32, alpha: a });
    db1_op(ctx, T, ST, 'oc((t-0.45)/0.4)');
});

/* ---- seigaiha \u2014 \u9752\u6D77\u6CE2: overlapping concentric-arc waves spreading from a screen corner, behind the lyric (Repeater grid) */
db1_reg('seigaiha', true, function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, u = db1_u(ctx), k;
    var r = jzClamp(jzU(ctx) * 0.05, 34 * u, 60 * u), sx = d.right ? 1 : -1, sy = d.low ? 1 : -1, ox = sx > 0 ? W : 0, oy = sy > 0 ? H : 0;
    var pw = W * jzLerp(0.42, 0.62, db1_r(d.seed, 1)), ph = H * jzLerp(0.45, 0.65, db1_r(d.seed, 2));
    var NR = 4, gam = [];
    for (k = 0; k < NR; k++) { var rho = r * (1 - k / NR), ca = (rho * rho + 1.25 * r * r - r * r) / (2 * rho * 1.118 * r); gam.push(Math.max(0, Math.acos(jzClamp(ca, -1, 1)) - 26.565 * DB1_D) / DB1_D); }
    var rows = Math.ceil(ph / (r / 2)) + 2, cols = Math.ceil(pw / (2 * r)) + 2;
    var y0 = sy > 0 ? H - ph : -r, x0 = sx > 0 ? W - pw - 2 * r : -2 * r;
    var col = d.accent ? db1_acc(sc) : sc.sub, base = (d.accent ? 0.5 : 0.46) * (db1_dark(sc) ? 1 : 0.8), lw = Math.max(1, 1.3 * u);
    var S = jzShapeLayer(ctx, 'seigaiha', 0, 0), gP = jzGrp(S, 'pattern');
    db1_gx(ctx, gP, 'ADBE Vector Position', 0, '[value[0]-(' + sx + ')*((t*' + jzN(7 * u) + ')%' + jzN(2 * r) + '),value[1]]');
    var gU = db1_sub(gP, 'scale pair');
    for (k = 0; k < NR; k++) {
        var rk = r * (1 - k / NR);
        jzAddPath(gU, db1_arc(x0, y0, rk, 180 + gam[k], 360 - gam[k], 16), false);
        jzAddPath(gU, db1_arc(x0 + r, y0 + r / 2, rk, 180 + gam[k], 360 - gam[k], 16), false);
    }
    db1_st(gU, col, lw, base * 100);
    db1_rep(gP, cols + 1, 2 * r, 0);
    db1_rep(gP, Math.ceil(rows / 2) + 1, 0, r);
    // soft elliptical falloff from the corner, growing out over ~1 s
    var m = db1_mask(S, db1_ellShape(ox, oy, pw * 1.02, ph * 1.02), null, Math.min(pw, ph) * 0.6);
    jzSetExpr(m.property('ADBE Mask Offset'), db1_H(ctx, 0) + '-(1-oc(t/0.8))*' + jzN(Math.max(pw, ph) * 1.3));
    db1_op(ctx, S, 0);
});

/* ---- asanoha \u2014 \u9EBB\u306E\u8449: the hemp-leaf lattice seen through a round (or square) window that irises open behind the lyric */
db1_reg('asanoha', true, function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, u = db1_u(ctx), M = jzU(ctx);
    var R0 = M * jzLerp(0.27, 0.36, db1_r(d.seed, 1)), portrait = H > W * 1.1;
    var cx = portrait ? W * (0.5 + (d.right ? 0.14 : -0.14)) : W * (0.5 + (d.right ? 0.22 : -0.22)) + db1_rs(d.seed, 2) * W * 0.04;
    var cy = portrait ? H * (d.low ? 0.66 : 0.34) : H * (0.5 + (d.low ? 0.07 : -0.07));
    var sq = !!d.corner, s = R0 / 3.2, h3 = s * Math.sqrt(3) / 2, col = d.accent ? db1_acc(sc) : sc.sub, al = db1_dark(sc) ? 0.3 : 0.34, lw = Math.max(1, 1.1 * u);
    var Rx = '(' + jzN(R0) + '*oc(t/0.8)*(1+0.06*ic(PO)))';
    var S = jzShapeLayer(ctx, 'asanoha', cx, cy), gL = jzGrp(S, 'lattice');
    db1_gx(ctx, gL, 'ADBE Vector Rotation', 0, jzN((d.r || 0) * 60) + '+t*2.5');
    // star (top) is finished before the grid group is added next to it (a new sibling invalidates the star reference)
    var gC = db1_sub(gL, 'cell'), gS = db1_sub(gC, 'star');
    var A = [0, 0], B = [s, 0], C = [s / 2, h3], D = [s * 1.5, h3], tris = [[A, B, C], [B, D, C]], i, j;
    for (i = 0; i < 2; i++) {
        var tr = tris[i], g = [(tr[0][0] + tr[1][0] + tr[2][0]) / 3, (tr[0][1] + tr[1][1] + tr[2][1]) / 3];
        for (j = 0; j < 3; j++) jzAddPath(gS, [g, tr[j]], false);
    }
    db1_trim(ctx, gS, 0, '100*oc((t-0.15)/0.8)');
    db1_st(gS, col, lw, al * 100);
    var gG = db1_sub(gC, 'grid');
    jzAddPath(gG, [A, B], false); jzAddPath(gG, [A, C], false); jzAddPath(gG, [B, C], false);
    db1_st(gG, col, lw, al * 80);
    var n = sq ? 7 : 6;
    db1_rep(gL, 2 * n + 1, s, 0, 0, -n);
    db1_rep(gL, 2 * n + 1, s / 2, h3, 0, -n);
    var mR = R0 * 1.06, m = db1_mask(S, sq ? db1_rectShape(-mR, -mR, mR, mR) : jzCircleShape(0, 0, mR));
    jzSetExpr(m.property('ADBE Mask Offset'), db1_H(ctx, 0) + Rx + '-' + jzN(mR));
    db1_op(ctx, S, 0);
    // window rim (+ an outer arc on the round window)
    var F = jzShapeLayer(ctx, 'asanoha rim', cx, cy), gr = jzGrp(F, 'rim');
    if (sq) {
        var rc = jzAddRect(gr, R0 * 2, R0 * 2, 6 * u);
        jzSetExpr(rc.property('ADBE Vector Rect Size'), db1_H(ctx, 0) + 'var R=' + Rx + ';[2*R,2*R]');
        db1_trim(ctx, gr, 0, '100*ioc(t/0.9)');
        db1_st(gr, col, lw * 1.3, Math.min(100, al * 160));
    } else {
        var el = jzAddEllipse(gr, R0 * 2, R0 * 2);
        jzSetExpr(el.property('ADBE Vector Ellipse Size'), db1_H(ctx, 0) + 'var R=' + Rx + ';[2*R,2*R]');
        db1_trim(ctx, gr, 0, '100*ioc(t/0.9)');
        db1_st(gr, col, lw * 1.3, Math.min(100, al * 160));     // before the outer-arc group is added (that invalidates gr)
        var go = jzGrp(F, 'outer arc'), e2 = jzAddEllipse(go, R0 * 2, R0 * 2);
        jzSetExpr(e2.property('ADBE Vector Ellipse Size'), db1_H(ctx, 0) + 'var R=' + Rx + '+' + jzN(7 * u) + ';[2*R,2*R]');
        var t2 = db1_trim(ctx, go, 0, '300*ioc(t/0.9)/3.6'); t2.property('ADBE Vector Trim Offset').setValue(180);
        db1_st(go, col, Math.max(1, 0.8 * u), al * 90);
    }
    db1_op(ctx, F, 0);
});

/* ---- hanabi \u2014 \u82B1\u706B: firework shells bursting in the free space around the lyric (chrysanthemum / peony / willow).
   Each shell is one shape layer that re-launches every 2.2 s at a new pre-computed spot; rays are Repeaters with Trim Paths. */
db1_reg('hanabi', false, function (ctx, bb, d) {
    var sc = ctx.sc, u = db1_u(ctx), ST = db1_settle(ctx), c = ctx.cut;
    var fr = db1_free(ctx, bb, 26 * u); if (!fr.length) return;
    var nB = 2 + ((d.n | 0) % 2), TT = 2.2, style = (d.v | 0) % 3, cols = [db1_acc(sc), db1_acc2(sc), sc.fg];
    var colEx = 'var CC=[' + db1_cx(cols[0]) + ',' + db1_cx(cols[1]) + ',' + db1_cx(cols[2]) + '];';
    var life = style === 2 ? 1.75 : 1.4, gk = style === 2 ? 0.75 * 0.55 : 0.28, pad = 8 * u, soft = 30 * u, k, cy;
    var ncyc = Math.ceil((c.dur + 1) / TT) + 1;
    var rings = [[style === 1 ? 32 : 28, 1, 0, 1.4 * u], [style === 2 ? 0 : 16, 0.52, 1, 1.2 * u]];
    // head position along a ray (fraction of R*rk) and the trail length (s)
    var fEx = style === 2 ? 'function F(x){return x<=0?0:1-Math.exp(-2.6*x);}' : 'function F(x){return oc(x/0.85);}';
    var trail = style === 2 ? 0.6 : (style === 1 ? 0.05 : 0.2), hr = style === 1 ? 2.6 * u : (style === 2 ? 1.6 * u : 2 * u);
    for (k = 0; k < nB; k++) {
        var Q = [];
        for (cy = 0; cy < ncyc; cy++) {
            var reg = fr[(k + cy) % Math.min(2, fr.length)];
            var R = jzClamp(Math.min(reg.w * 0.5, reg.h) * 0.5, 44 * u, 190 * u) * (0.75 + 0.3 * db1_r(d.seed, k, cy, 3));
            var x = reg.x + R * 0.9 + Math.max(0, reg.w - R * 1.8) * db1_r(d.seed, k, cy, 1), y = reg.y + R * 0.8 + Math.max(0, reg.h - R * 1.7) * db1_r(d.seed, k, cy, 2);
            Q.push('[' + jzN(x) + ',' + jzN(y) + ',' + jzN(R) + ',' + ((k + cy) % 3) + ',' + ((k + cy + 1) % 3) + ']');
        }
        var HD = colEx + fEx + 'var A=t-' + jzN(0.02 + k * 0.6) + ',cy=Math.max(0,Math.floor(A/' + TT + ')),tau=A-cy*' + TT + ',QQ=[' + Q.join(',') + '],q=QQ[Math.min(cy,QQ.length-1)],X=q[0],Y=q[1],R=q[2],tb=tau-0.3,LF=' + life +
            ',fade=1-cl((tb-LF*0.4)/(LF*0.6)),on=(A>=0&&tb>=0&&tb<=LF)?1:0,DY=R*' + jzN(gk) + '*tb*tb;\n';
        var S = jzShapeLayer(ctx, 'hanabi ' + (k + 1), 0, 0), ri;
        for (ri = 0; ri < 2; ri++) {
            var M = rings[ri][0], rk = rings[ri][1], ci = rings[ri][2], lwr = rings[ri][3];
            if (!M) continue;
            var gR = jzGrp(S, 'burst ' + (ri + 1));
            db1_gx(ctx, gR, 'ADBE Vector Position', ST, HD + '[X,Y+DY]');
            db1_gop(ctx, gR, ST, 'on*fade', HD);
            // heads
            var gH = db1_sub(gR, 'heads'), gh = db1_sub(gH, 'head'), step = 360 / M;
            var e1 = jzAddEllipse(gh, hr * 2, hr * 2);
            jzSetExpr(e1.property('ADBE Vector Ellipse Position'), db1_H(ctx, ST) + HD + 'var r=R*' + rk + '*F(tb);[r,0]');
            var e2 = jzAddEllipse(gh, hr * 2, hr * 2);      // added only after e1 is configured (it invalidates e1)
            jzSetExpr(e2.property('ADBE Vector Ellipse Position'), db1_H(ctx, ST) + HD + 'var r=R*' + jzN(rk * 0.9) + '*F(tb);[r*' + jzN(Math.cos(step * DB1_D)) + ',r*' + jzN(Math.sin(step * DB1_D)) + ']');
            db1_rep(gH, M / 2, 0, 0, step * 2);
            var fl = jzAddFill(gH, cols[ci]);
            jzSetExpr(fl.property('ADBE Vector Fill Color'), db1_H(ctx, ST) + HD + 'CC[q[' + (3 + ci) + ']]');
            // rays (unit length 100, scaled to R) with a moving trim window = the trails
            var gT = db1_sub(gR, 'trails'), gs = db1_sub(gT, 'rays'), gr = db1_sub(gs, 'ray');
            jzAddPath(gr, [[0, 0], [100, 0]], false);
            jzAddPath(gr, [[0, 0], [90 * Math.cos(step * DB1_D), 90 * Math.sin(step * DB1_D)]], false);
            db1_rep(gs, M / 2, 0, 0, step * 2);
            db1_gx(ctx, gs, 'ADBE Vector Scale', ST, HD + '[R*' + rk + ',R*' + rk + ']');
            db1_trim(ctx, gT, ST, '100*F(tb)', '100*F(tb-' + trail + ')', null, HD);
            var sk = db1_st(gT, cols[ci], lwr, style === 1 ? 35 : 80, 2);
            jzSetExpr(sk.property('ADBE Vector Stroke Color'), db1_H(ctx, ST) + HD + 'CC[q[' + (3 + ci) + ']]');
        }
        // burst flash
        var gF = jzGrp(S, 'flash'), ef = jzAddEllipse(gF, 10, 10);
        jzSetExpr(ef.property('ADBE Vector Ellipse Size'), db1_H(ctx, ST) + HD + 'var r=R*0.16*(1-cl(tb/0.16))+' + jzN(2 * u) + ';[2*r,2*r]');
        jzSetExpr(ef.property('ADBE Vector Ellipse Position'), db1_H(ctx, ST) + HD + '[X,Y]');
        jzAddFill(gF, sc.fg, 60);
        db1_gop(ctx, gF, ST, '(A>=0&&tb>=0&&tb<0.16)?1:0', HD);
        // rising shell with a short tail
        var gU = jzGrp(S, 'shell');
        db1_gx(ctx, gU, 'ADBE Vector Position', ST, HD + 'var qq=oc(tau/0.3);[X,Y+R*1.1*(1-qq)]');
        db1_gop(ctx, gU, ST, '(A>=0&&tau<0.3)?1:0', HD);
        var gUd = db1_sub(gU, 'dot'); jzAddEllipse(gUd, 4.4 * u, 4.4 * u); var fd = jzAddFill(gUd, cols[0]);
        jzSetExpr(fd.property('ADBE Vector Fill Color'), db1_H(ctx, ST) + HD + 'CC[q[3]]');
        var gUt = db1_sub(gU, 'tail'); jzAddPath(gUt, [[0, 0], [0, 34 * u]], false);
        db1_trim(ctx, gUt, ST, '100*(1-0.7*oc(tau/0.3))', null, null, HD);
        var su = db1_st(gUt, cols[0], 1.4 * u, 70, 2);
        jzSetExpr(su.property('ADBE Vector Stroke Color'), db1_H(ctx, ST) + HD + 'CC[q[3]]');
        // keep clear of the lyric (the browser's clearOf): feathered subtract mask
        db1_mask(S, db1_rectShape(bb.x0 - pad - soft / 2, bb.y0 - pad - soft / 2, bb.x1 + pad + soft / 2, bb.y1 + pad + soft / 2), MaskMode.SUBTRACT, soft);
        db1_op(ctx, S, ST);
        // falling sparks leave drooping trails (gravity) - the browser draws them from earlier positions
        if (style !== 1) { var ec = jzEffect(S, 'ADBE Echo', 'JZ Trails'); jzEP(ec, 1, -0.04); jzEP(ec, 2, 5); jzEP(ec, 3, 0.8); jzEP(ec, 4, 0.72); jzEP(ec, 5, 5); }
    }
});

/* ---- chochin \u2014 \u63D0\u706F: paper lanterns dropping in on strings from the top edge, swinging, each bearing a character of the lyric */
db1_reg('chochin', false, function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, u = db1_u(ctx), m = db1_mg(ctx), ST = db1_settle(ctx), i, j;
    var lw0 = jzClamp(jzU(ctx) * 0.066, 42 * u, 86 * u), lh = lw0 * 1.32, gap = 24 * u;
    var K = Math.max(5, Math.floor((W - 2 * m) / (lw0 * 1.9))), cands = [];
    for (i = 0; i < K; i++) {
        var x = m + lw0 * 0.7 + (W - 2 * m - lw0 * 1.4) * i / Math.max(1, K - 1);
        var over = x + lw0 * 0.75 > bb.x0 - gap && x - lw0 * 0.75 < bb.x1 + gap, floor = over ? bb.y0 - gap : H * 0.62, room = floor - lh * 1.3 - 10 * u;
        if (room >= 16 * u) cands.push({ x: x, room: room, i: i });
    }
    var want = Math.min(K, 3 + (d.n | 0) % 3), pick = [];
    for (j = 0; j < want; j++) {
        var ideal = Math.round((j + 0.5) * K / want - 0.5 + db1_rs(d.seed, j, 4) * 0.4), best = null, bd = 1e9, q;
        for (i = 0; i < cands.length; i++) {
            var cc = cands[i], ok = true;
            for (q = 0; q < pick.length; q++) if (Math.abs(pick[q].x - cc.x) <= lw0 * 1.6) ok = false;
            if (ok && Math.abs(cc.i - ideal) < bd) { bd = Math.abs(cc.i - ideal); best = cc; }
        }
        if (best && Math.abs(best.i - ideal) <= Math.max(1, K / want / 2)) pick.push(best);
    }
    for (i = 1; i < pick.length; i++) for (j = i; j > 0 && pick[j].x < pick[j - 1].x; j--) { var tp = pick[j]; pick[j] = pick[j - 1]; pick[j - 1] = tp; }
    var body = db1_acc(sc), rib = jzMixHex(body, sc.bg, 0.45), cap = db1_dark(sc) ? sc.fg : (sc.ink || sc.fg), txtC = jzLum(body) > 0.55 ? '#000000' : sc.bg;
    var shape = [], rl = [], ts = [];
    for (i = 0; i <= 32; i++) {
        var th = i / 32 * Math.PI * 2, cs = Math.cos(th), sn = Math.sin(th);
        shape.push([(cs < 0 ? -1 : 1) * Math.pow(Math.abs(cs), 0.8) * lw0 / 2, (sn < 0 ? -1 : 1) * Math.pow(Math.abs(sn), 0.9) * lh * 0.42 + lh / 2]);
    }
    for (i = 1; i < 8; i++) {
        var yy = -lh * 0.42 + lh * 0.84 * i / 8, sy = Math.pow(Math.abs(yy) / (lh * 0.42), 1 / 0.9), f = Math.pow(Math.sqrt(Math.max(0, 1 - sy * sy)), 0.8) * lw0 / 2 - 1.5 * u;
        if (f > 2 * u) rl.push([[-f, lh / 2 + yy], [f, lh / 2 + yy]]);
    }
    for (i = -2; i <= 2; i++) ts.push([[i * 2.4 * u, lh], [i * 3.2 * u, lh + lh * 0.24]]);
    var font = jzSerifF(ctx) === 'mincho_light' ? 'mincho_bold' : jzSerifF(ctx);
    for (j = 0; j < pick.length; j++) {
        var p = pick[j], Lf = Math.min(p.room, p.room * (0.25 + 0.6 * db1_r(d.seed, p.i, 2)) + 20 * u);
        var sg = db1_r(d.seed, j, 3) < 0.5 ? 1 : -1;
        var LX = 'var qd=cl((t-' + jzN(0.03 + j * 0.08) + ')/0.6),LL=' + jzN(-lh * 1.6) + '+(' + jzN(Lf + lh * 1.6) + ')*(qd<=0?0:ob(qd,1.4));';
        var S = jzShapeLayer(ctx, 'chochin ' + (j + 1), p.x, 0);
        jzSetExpr(jzXf(S, 'ADBE Rotate Z'), db1_H(ctx, ST) + '(2.5+8*Math.exp(-Math.max(0,t)*1.5))*' + sg + '*Math.sin(t*' + jzN(1.7 + j * 0.23) + '+' + jzN(j * 1.3) + ')');
        var gB = jzGrp(S, 'lantern');
        db1_gx(ctx, gB, 'ADBE Vector Position', ST, LX + '[0,LL]');
        var gt = db1_sub(gB, 'tassel'); for (i = 0; i < ts.length; i++) jzAddPath(gt, ts[i], false); db1_st(gt, body, Math.max(1, u), 85);
        var gc = db1_sub(gB, 'caps'); jzAddRect(gc, lw0 * 0.54, lh * 0.1, 0, 0, lh * 0.05); jzAddRect(gc, lw0 * 0.54, lh * 0.1, 0, 0, lh * 0.95); jzAddFill(gc, cap);
        var gr = db1_sub(gB, 'ribs'); for (i = 0; i < rl.length; i++) jzAddPath(gr, rl[i], false); db1_st(gr, rib, Math.max(1, 1.1 * u), 70);
        var gb = db1_sub(gB, 'body'); jzAddPath(gb, shape, true); jzAddFill(gb, body, 94);
        if (db1_dark(sc)) { var gg = db1_sub(gB, 'glow'); jzAddEllipse(gg, lw0 * 1.56, lw0 * 1.56, 0, lh / 2); jzAddFill(gg, body, 8); var gg2 = db1_sub(gB, 'glow 2'); jzAddEllipse(gg2, lw0 * 2.1, lw0 * 2.1, 0, lh / 2); jzAddFill(gg2, body, 6); }
        var gS = jzGrp(S, 'string'), rs = jzAddRect(gS, Math.max(1, u), 10);
        jzSetExpr(rs.property('ADBE Vector Rect Size'), db1_H(ctx, ST) + LX + '[' + jzN(Math.max(1, u)) + ',Math.max(0,LL)]');
        jzSetExpr(rs.property('ADBE Vector Rect Position'), db1_H(ctx, ST) + LX + '[0,Math.max(0,LL)/2]');
        jzAddFill(gS, sc.sub, 80);
        db1_op(ctx, S, ST);
        var ch = db1_lyricChar(ctx, p.i * 7 + 1);
        if (ch) {
            var T = jzText(ctx, ch, { font: font, size: lw0 * 0.5, color: txtC, x: p.x, y: Lf + lh / 2 });
            T.parent = S;
            jzXf(T, 'ADBE Position').setValue([0, Lf + lh / 2 + lw0 * 0.02]);
            jzSetExpr(jzXf(T, 'ADBE Position'), db1_H(ctx, ST) + LX + '[value[0],LL+' + jzN(lh / 2 + lw0 * 0.02) + ']');
            db1_op(ctx, T, ST, 'cl((cl((t-' + jzN(0.03 + j * 0.08) + ')/0.6)-0.35)/0.4)');
        }
    }
});

/* ---- shimenawa \u2014 \u6CE8\u9023\u7E04: a twisted sacred rope sagging across the free band, with zigzag shide papers and straw tassels */
db1_reg('shimenawa', false, function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, u = db1_u(ctx), m = db1_mg(ctx), ST = db1_settle(ctx), i, s;
    var gap = 30 * u, need0 = 210 * u, topRoom = bb.y0 - gap - m * 0.3, botRoom = H - m * 0.4 - bb.y1 - gap;
    var top = d.low ? !(botRoom >= need0 * 0.7 || botRoom > topRoom) : (topRoom >= need0 * 0.7 || topRoom >= botRoom);
    var room = top ? topRoom : botRoom; if (room < 60 * u) return;
    var k = jzClamp(room / need0, 0.4, 1.15), T0 = 17 * u * k, sag = 46 * u * k, shL = 112 * u * k, shW = shL * 0.17;
    var ya = top ? m * 0.3 + T0 * 0.6 : bb.y1 + gap + T0 * 0.6;
    function Y(x) { return ya + sag * 4 * (x / W) * (1 - x / W); }
    function TK(x) { return T0 * (0.45 + 0.55 * Math.sin(Math.PI * jzClamp(x / W, 0, 1))); }
    var col = db1_dark(sc) ? sc.fg : (sc.ink || sc.fg), lw = Math.max(1, 1.1 * u), step = T0 * 1.05, x;
    // rope: slanted twisted bundles (drifting by one bundle step), revealed from the centre by a mask
    // edges (top group) are finished before the twist group is added next to them
    var S = jzShapeLayer(ctx, 'shimenawa rope', 0, 0), gE = jzGrp(S, 'edges');
    var tp = [], bt = [];
    for (x = -12; x <= W + 12.1; x += 8 * u) { tp.push([x, Y(x) - TK(x) / 2 - 1.5 * u]); bt.push([x, Y(x) + TK(x) / 2 + 1.5 * u]); }
    jzAddPath(gE, tp, false); jzAddPath(gE, bt, false); db1_st(gE, col, lw, 45);
    var gT = jzGrp(S, 'twist');
    for (x = -2 * step; x < W + step; x += step) {
        var x0 = x, x1 = x + step * 0.82, sl = TK(x) * 0.7;
        jzAddPath(gT, [[x0, Y(x0) - TK(x0) / 2], [x1, Y(x1) - TK(x1) / 2], [x1 + sl, Y(x1) + TK(x1) / 2], [x0 + sl, Y(x0) + TK(x0) / 2]], true);
    }
    jzAddFill(gT, col, 82);
    db1_gx(ctx, gT, 'ADBE Vector Position', ST, '[value[0]+(Math.max(0,t)*' + jzN(5 * u) + ')%' + jzN(step) + ',value[1]]');
    var mk = db1_mask(S, db1_rectShape(-12, -2 * H, W + 12, 3 * H));
    jzSetExpr(mk.property('ADBE Mask Offset'), db1_H(ctx, ST) + '-(1-ioc(t/0.8))*' + jzN(W / 2 + 12));
    db1_op(ctx, S, ST);
    // shide papers + tassels
    var ns = 3 + ((d.n | 0) % 2 ? 1 : 0), paperC = db1_dark(sc) ? sc.fg : sc.bg, P2 = jzShapeLayer(ctx, 'shimenawa shide', 0, 0);
    for (i = 0; i < ns - 1; i++) {
        var tx = W * (i + 1.5) / (ns + 1), ty = Y(tx) + TK(tx) / 2, tl = shL * 0.36, gq = jzGrp(P2, 'tassel ' + (i + 1));
        for (s = -4; s <= 4; s++) jzAddPath(gq, [[s * 1.4 * u, 0], [s * 2 * u, tl * (1 - Math.abs(s) * 0.05)]], false);
        db1_st(gq, sc.sub, Math.max(1, u), 85);
        jzGX(gq).property('ADBE Vector Position').setValue([tx, ty]);
        db1_gx(ctx, gq, 'ADBE Vector Scale', ST, 'var f=oc((t-0.4)/0.4);[100,100*f]');
        db1_gx(ctx, gq, 'ADBE Vector Rotation', ST, 'Math.sin(t*1.2+' + i + ')*1.5');
    }
    for (i = 0; i < ns; i++) {
        var px = W * (i + 1) / (ns + 1), ex = Math.abs(px - W / 2) / (W / 2 + 12), py = Y(px) + TK(px) / 2, h = shL / 4, gp = jzGrp(P2, 'shide ' + (i + 1));
        for (s = 0; s < 4; s++) {
            var dx = (s % 2 ? shW * 0.62 : 0) - shW / 2, sk = (s % 2 ? -1 : 1) * shW * 0.18;
            jzAddPath(gp, [[dx, s * h], [dx + shW, s * h + sk * 0.3], [dx + shW + sk, (s + 1) * h + 0.5], [dx + sk, (s + 1) * h + 0.5 - sk * 0.3]], true);
        }
        if (!db1_dark(sc)) db1_st(gp, sc.fg, Math.max(1, u), 85);
        jzAddFill(gp, paperC, 94);
        jzGX(gp).property('ADBE Vector Position').setValue([px, py]);
        db1_gx(ctx, gp, 'ADBE Vector Scale', ST, 'var q=ioc(t/0.8)<' + jzN(ex) + '?0:cl((t-' + jzN(0.2 + ex * 0.5) + ')/0.45);var s=q<=0?0:ob(q,1.6);[100,100*s]');
        db1_gx(ctx, gp, 'ADBE Vector Rotation', ST, 'Math.sin(t*1.4+' + jzN(i * 1.7) + ')*3');
    }
    db1_op(ctx, P2, ST);
});

/* ---- sensu \u2014 \u6247: a folding fan that opens with pleated ribs (plain, with a red sun, or painted wave bands) and folds shut on exit */
db1_reg('sensu', false, function (ctx, bb, d) {
    var sc = ctx.sc, u = db1_u(ctx), ST = db1_settle(ctx), i;
    var R = jzClamp(jzU(ctx) * 0.105, 62 * u, 124 * u), r0 = R * 0.36;
    var sp = db1_spot(ctx, bb, R * 2.05, R * 1.2, d, 22 * u), a = sp.ok ? 1 : 0.35;
    var tilt = (d.right ? 1 : -1) * (5 + (d.r || 0) * 9), px = sp.cx, py = sp.y + R * 1.12, N = 14, v = (d.v | 0) % 3, ac = db1_acc(sc);
    var OP = 'var op=ob(cl((t-0.05)/0.65),1.1)*(1-0.9*ic(PO)),SP=12+140*op;';
    function angEx(i) { return OP + jzN(-90 + tilt) + '-SP/2+SP*' + jzN(i / N); }
    function sweep(g) { return db1_trim(ctx, g, ST, 'SP/3.6', null, jzN(tilt) + '-SP/2', OP); }
    var S = jzShapeLayer(ctx, 'sensu', px, py);
    var gv = jzGrp(S, 'pivot'); jzAddEllipse(gv, 3.2 * u, 3.2 * u); jzAddFill(gv, ac);
    var gv2 = jzGrp(S, 'pivot ring'); jzAddEllipse(gv2, 8 * u, 8 * u); db1_st(gv2, sc.fg, Math.max(1, u));
    for (i = 0; i <= N; i += N) { var ge = jzGrp(S, 'guard ' + i); jzAddPath(ge, [[0, 0], [R, 0]], false); db1_st(ge, sc.fg, Math.max(1, 1.6 * u)); db1_gx(ctx, ge, 'ADBE Vector Rotation', ST, angEx(i)); }
    var go = jzGrp(S, 'outer edge'); jzAddEllipse(go, R * 2, R * 2); sweep(go); db1_st(go, sc.fg, Math.max(1, 1.3 * u), 95, 1, 2);
    var gi = jzGrp(S, 'inner edge'); jzAddEllipse(gi, r0 * 2, r0 * 2); sweep(gi); db1_st(gi, sc.fg, Math.max(1, 1.1 * u), 80);
    for (i = 1; i < N; i++) {
        var gk = jzGrp(S, 'rib ' + i), g1 = db1_sub(gk, 'rib');
        jzAddPath(g1, [[R * 0.06, 0], [r0, 0]], false); db1_st(g1, sc.fg, Math.max(1, 1.1 * u), 80);
        var g2 = db1_sub(gk, 'fold');
        jzAddPath(g2, [[r0, 0], [R * (i % 2 ? 0.965 : 1), 0]], false); db1_st(g2, sc.fg, Math.max(1, 0.8 * u), 45);
        db1_gx(ctx, gk, 'ADBE Vector Rotation', ST, angEx(i));
    }
    if (v === 1) {
        var an = (-90 + tilt) * DB1_D, rc = (r0 + R) / 2, gh = jzGrp(S, 'hinomaru'), eh = jzAddEllipse(gh, 10, 10, Math.cos(an) * rc, Math.sin(an) * rc);
        jzSetExpr(eh.property('ADBE Vector Ellipse Size'), db1_H(ctx, ST) + OP + 'var r=' + jzN((R - r0) * 0.28) + '*cl((op-0.6)/0.4);[2*r,2*r]');
        jzAddFill(gh, ac, 90);
    } else if (v === 2) {
        var fb = [0.55, 0.72];
        for (i = 0; i < 2; i++) {
            var gw = jzGrp(S, 'wave band ' + (i + 1)), rr = jzLerp(r0, R, fb[i]);
            jzAddEllipse(gw, rr * 2, rr * 2);
            sweep(gw); db1_st(gw, ac, 1.6 * u, 80);
        }
    }
    var gl = jzGrp(S, 'leaf'); jzAddEllipse(gl, R + r0, R + r0); sweep(gl); db1_st(gl, ac, R - r0, 13, 1);
    jzXf(S, 'ADBE Opacity').setValue(a * 100); db1_op(ctx, S, ST);
});

/* ---- tsukiKumo \u2014 \u6708\u306B\u96F2: a moon (full or crescent) with a haloed ring, crossed by a curling Japanese cloud that drifts past */
function db1_kumo(x0, yb, L, hc) {
    var bumps = [[0.22, 0.5], [0.48, 0.78], [0.74, 0.55], [0.9, 0.32]], top = [], curl = [], i, k;
    for (i = 0; i <= 44; i++) {
        var x = x0 + L * i / 44, y = yb;
        for (k = 0; k < bumps.length; k++) { var dx = x - (x0 + L * bumps[k][0]), R = bumps[k][1] * hc; if (Math.abs(dx) < R) y = Math.min(y, yb - Math.sqrt(R * R - dx * dx)); }
        top.push([x, y]);
    }
    var c = [x0 - hc * 0.05, yb - hc * 0.3];
    for (i = 0; i <= 30; i++) { var t = i / 30, an = (90 + t * 400) * DB1_D, r = hc * 0.3 * (1 - t * 0.78); curl.push([c[0] - Math.cos(an) * r * 1.1, c[1] + Math.sin(an) * r]); }
    return { outline: top.concat([[x0 + L, yb], [x0, yb]]), curl: curl };
}
db1_reg('tsukiKumo', false, function (ctx, bb, d) {
    var sc = ctx.sc, u = db1_u(ctx), ST = db1_settle(ctx), i;
    var Rm = jzClamp(jzU(ctx) * 0.064, 40 * u, 84 * u), sp = db1_spot(ctx, bb, Rm * 4.6, Rm * 2.9, d, 24 * u), a = sp.ok ? 1 : 0.35;
    var flip = sp.cx < ctx.W / 2 ? -1 : 1, mx = sp.cx + flip * Rm * 0.9, my = sp.y + Rm * 1.3;
    var moonC = db1_dark(sc) ? sc.fg : db1_acc(sc), cres = (d.v | 0) % 2 === 1;
    var DR = 'var e=oc(t/0.6),dr=Math.sin(t*0.5+' + jzN((d.r || 0) * 6) + ')*' + jzN(Rm * 0.35) + '-(' + flip + ')*(1-e)*' + jzN(Rm * 1.4) + ';';
    var S = jzShapeLayer(ctx, 'tsuki kumo', mx, my);
    // small cloud above (line only)
    var L = Rm * 3.3, hc = Rm * 0.7, L2c = L * 0.55, c2 = db1_kumo(flip * Rm * 0.2 - L2c / 2, -Rm * 0.9, L2c, hc * 0.55);
    var g2 = jzGrp(S, 'cloud 2');
    jzAddPath(g2, c2.outline.slice(0, c2.outline.length - 1), false); jzAddPath(g2, c2.curl, false);
    db1_trim(ctx, g2, ST, '100*ioc((t-0.35)/0.7)'); db1_st(g2, sc.sub, Math.max(1, 1.1 * u), 70, 2, 2);
    db1_gx(ctx, g2, 'ADBE Vector Position', ST, DR + '[-dr*0.6,0]');
    // main cloud across the lower half of the moon (filled with the background so it passes in front)
    var c1 = db1_kumo(-L * 0.55, Rm * 0.62, L, hc), gC = jzGrp(S, 'cloud');
    db1_gx(ctx, gC, 'ADBE Vector Position', ST, DR + '[dr,0]');
    var gl = db1_sub(gC, 'lines'); jzAddPath(gl, c1.outline.slice(0, c1.outline.length - 1), false); jzAddPath(gl, c1.curl, false);
    db1_trim(ctx, gl, ST, '100*ioc((t-0.2)/0.7)'); db1_st(gl, sc.fg, Math.max(1, 1.4 * u), 90, 2, 2);
    var gi = db1_sub(gC, 'inner'); jzAddPath(gi, db1_off(c1.outline.slice(6, 40), 0, hc * 0.28), false);
    db1_trim(ctx, gi, ST, '100*ioc((t-0.2)/0.7)'); db1_st(gi, sc.sub, Math.max(1, u), 50);
    var gf = db1_sub(gC, 'fill'); jzAddPath(gf, c1.outline, true); jzAddFill(gf, sc.bg);
    db1_gop(ctx, gf, ST, 'cl(ioc((t-0.2)/0.7)*2)');
    // halo rings
    var gh1 = jzGrp(S, 'halo'); db1_arcTrim(ctx, gh1, Rm * 1.28, 0, 0, -90, ST, '360*ioc((t-0.1)/0.7)'); db1_st(gh1, sc.sub, Math.max(1, u), 50);
    var gh2 = jzGrp(S, 'halo 2'); db1_arcTrim(ctx, gh2, Rm * 1.5, 0, 0, 90, ST, '200*ioc((t-0.1)/0.7)'); db1_st(gh2, sc.sub, Math.max(1, 0.8 * u), 30);
    // moon
    var gm = jzGrp(S, 'moon');
    if (cres) {
        var pts = db1_arc(0, 0, Rm, 60, 300, 40), inn = [];
        for (i = 0; i <= 40; i++) { var an = (300 - 240 * i / 40) * DB1_D; inn.push([Rm * 0.42 + Math.cos(an) * Rm * 0.62 * 1.12, Math.sin(an) * Rm * 0.93]); }
        jzAddPath(gm, pts.concat(inn), true);
    } else jzAddEllipse(gm, Rm * 2, Rm * 2);
    jzAddFill(gm, moonC, 95);
    db1_gx(ctx, gm, 'ADBE Vector Scale', ST, 'var e=oc(t/0.6),s=85+15*e;[s,s]');
    db1_gop(ctx, gm, ST, 'oc(t/0.6)');
    jzXf(S, 'ADBE Opacity').setValue(a * 100); db1_op(ctx, S, ST);
});

/* ---- momiji \u2014 \u7D05\u8449: a few maple leaves falling in a pendulum sway (tilting with each swing), fading near the lyric */
var DB1_MAPLE = (function () {
    var lobes = [[-125, 0.36], [-82, 0.66], [-42, 0.9], [0, 1], [42, 0.9], [82, 0.66], [125, 0.36]], pts = [], i;
    function P2(a, r) { return [Math.sin(a * DB1_D) * r, -Math.cos(a * DB1_D) * r]; }
    pts.push(P2(180, 0.1));
    for (i = 0; i < lobes.length; i++) {
        var a = lobes[i][0], l = lobes[i][1];
        pts.push(P2(a - 17, l * 0.6), P2(a - 11, l * 0.64), P2(a - 6, l * 0.84), P2(a, l), P2(a + 6, l * 0.84), P2(a + 11, l * 0.64), P2(a + 17, l * 0.6));
        var nx = i < lobes.length - 1 ? lobes[i + 1][0] : 180;
        pts.push(P2((a + nx) / 2, i < lobes.length - 1 ? 0.3 : 0.1));
    }
    return pts;
})();
db1_reg('momiji', false, function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, u = db1_u(ctx), i, j;
    var N = 4 + (d.n | 0) + (d.big ? 1 : 0), ac = db1_acc(sc), cols = [ac, jzMixHex(ac, sc.fg, 0.3), jzMixHex(ac, sc.bg, 0.3)];
    var veins = [[0, 0.82], [-42, 0.7], [42, 0.7], [-82, 0.5], [82, 0.5]];
    for (i = 0; i < N; i++) {
        var r1 = db1_r(d.seed, i, 1), L = (40 + db1_r(d.seed, i, 2) * 26) * u * (i === 0 ? 1.35 : 1), v = (38 + db1_r(d.seed, i, 3) * 30) * u;
        var om = 1.3 + db1_r(d.seed, i, 4) * 0.9, ph = db1_r(d.seed, i, 5) * Math.PI * 2, span = H + L * 4;
        var x0 = W * (i + 0.5) / N + db1_rs(d.seed, i, 7) * W * 0.4 / N, A = (26 + db1_r(d.seed, i, 8) * 34) * u, rot0 = db1_rs(d.seed, i, 9) * 40;
        var pad = 12 * u + L * 0.5, soft = 30 * u;
        var MV = 'var s=Math.sin(t*' + jzN(om) + '+' + jzN(ph) + '),x=' + jzN(x0) + '+' + jzN(A) + '*s,y=wr(' + jzN(db1_r(d.seed, i, 6) * span) + '+' + jzN(v) + '*t,' + jzN(-L * 2) + ',' + jzN(span) + ')-Math.abs(Math.cos(t*' + jzN(om) + '+' + jzN(ph) + '))*' + jzN(A * 0.25) + ';';
        var S = jzShapeLayer(ctx, 'momiji ' + (i + 1), x0, 0), col = cols[i % 3];
        var gs = jzGrp(S, 'stem'); jzAddPath(gs, [[0, 0.05 * L], [0.06 * L, 0.42 * L]], false); db1_st(gs, col, Math.max(1, 1.4 * u), null, 2);
        var gv = jzGrp(S, 'veins'); for (j = 0; j < veins.length; j++) jzAddPath(gv, [[0, 0], [Math.sin(veins[j][0] * DB1_D) * veins[j][1] * L, -Math.cos(veins[j][0] * DB1_D) * veins[j][1] * L]], false);
        db1_st(gv, sc.bg, Math.max(1, 0.9 * u), 45);
        var gl = jzGrp(S, 'leaf'); jzAddPath(gl, db1_xf(DB1_MAPLE, 0, 0, 0, L), true); jzAddFill(gl, col);
        jzSetExpr(jzXf(S, 'ADBE Position'), db1_H(ctx, 0) + MV + '[x,y]');
        jzSetExpr(jzXf(S, 'ADBE Rotate Z'), db1_H(ctx, 0) + MV + jzN(rot0) + '+s*38');
        jzSetExpr(jzXf(S, 'ADBE Scale'), db1_H(ctx, 0) + 'var q=oc((t-' + jzN(r1 * 0.35) + ')/0.45),fx=0.55+0.45*Math.abs(Math.cos(t*' + jzN(om * 0.5) + '+' + jzN(ph) + '));[100*fx*q,100*q]');
        jzXf(S, 'ADBE Opacity').setValue(92);
        db1_op(ctx, S, 0, '(function(){' + MV + 'var dx=Math.max(' + jzN(bb.x0 - pad) + '-x,0,x-' + jzN(bb.x1 + pad) + '),dy=Math.max(' + jzN(bb.y0 - pad) + '-y,0,y-' + jzN(bb.y1 + pad) + ');return cl(Math.sqrt(dx*dx+dy*dy)/' + jzN(soft) + ');})()');
    }
});

/* ---- namiGashira \u2014 \u6CE2\u982D: a row of curling wave crests (line art with water lines and spray) rising along the bottom band */
function db1_crest(x, yb, w, h) {
    var back = db1_bez([x - w / 2, yb], [x - w * 0.18, yb], [x - w * 0.28, yb - h], [x, yb - h], 16), r0 = h * 0.4, cx = x, cy = yb - h + r0, curl = [], i;
    for (i = 1; i <= 30; i++) { var deg = i / 30 * 430, an = (-90 + deg) * DB1_D, rr = r0 * (deg < 90 ? 1 : 1 - 0.8 * (deg - 90) / 340); curl.push([cx + Math.cos(an) * rr, cy + Math.sin(an) * rr]); }
    var face = db1_bez([cx + r0, cy], [cx + r0 * 1.05, cy + h * 0.35], [x + w * 0.3, yb], [x + w / 2, yb], 12);
    var inner = [], ks = [0.7, 0.44];
    for (i = 0; i < 2; i++) { var k = ks[i]; inner.push(db1_bez([x - w / 2 + w * 0.12 * (1 - k), yb], [x - w * 0.2, yb], [x - w * 0.24, yb - h * k], [x - w * 0.02, yb - h * k + h * 0.08], 12)); }
    return { crest: back.concat(curl), face: face, inner: inner, top: [x + r0 * 0.9, yb - h] };
}
db1_reg('namiGashira', false, function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, u = db1_u(ctx), m = db1_mg(ctx), ST = db1_settle(ctx), i, k;
    var room = H - m * 0.6 - (bb.y1 + 26 * u); if (room < 34 * u) return;
    var h = Math.min(jzU(ctx) * 0.085, room * 0.72), w = h * 2.3, dir = d.right ? 1 : -1, n = Math.ceil(W / w) + 2, lw = Math.max(1, 1.5 * u), ac = db1_acc(sc);
    var S = jzShapeLayer(ctx, 'nami gashira', 0, H - m * 0.6);
    jzSetExpr(jzXf(S, 'ADBE Position'), db1_H(ctx, ST) + 'var e=oc(t/0.6);[value[0],value[1]+(1-e)*' + jzN(h * 1.2) + '+ic(PO)*' + jzN(h * 0.8) + ']');
    var gb = jzGrp(S, 'water line'); jzAddPath(gb, [[m * 0.5, 3 * u], [W - m * 0.5, 3 * u]], false); db1_st(gb, sc.sub, Math.max(1, 0.8 * u), 50);
    db1_gop(ctx, gb, ST, 'ioc((t-0.05)/0.8)');
    var gR = jzGrp(S, 'crests');
    db1_gx(ctx, gR, 'ADBE Vector Position', ST, '[wr(t*' + jzN(16 * u * dir) + ',0,' + jzN(w) + '),0]');
    for (i = -1; i < n; i++) {
        var hh = h * (0.82 + 0.18 * Math.sin(i * 1.7 + (d.r || 0) * 6)), wc = db1_crest(0, 0, w, hh), gc = db1_sub(gR, 'crest ' + (i + 2));
        jzGX(gc).property('ADBE Vector Position').setValue([i * w - w * 0.5, 0]);
        db1_gx(ctx, gc, 'ADBE Vector Position', ST, '[value[0],Math.sin(t*1.6+' + jzN(i * 0.9) + ')*' + jzN(h * 0.04) + ']');
        var gsp = db1_sub(gc, 'spray');
        for (k = 0; k < 4; k++) { var rr = (1.8 + 0.6 * Math.sin(i * 3 + k * 2)) * u; jzAddEllipse(gsp, rr * 2, rr * 2, wc.top[0] + (k * 0.12 + 0.06) * w * 0.45, wc.top[1] - (k % 2 ? 0.1 : 0.2) * hh - k * 1.5 * u); }
        var fs = jzAddFill(gsp, ac, 90);
        jzSetExpr(fs.property('ADBE Vector Fill Opacity'), db1_H(ctx, ST) + 'value*cl((ioc((t-0.05)/0.8)-0.8)/0.2)*(0.75+0.25*Math.sin(t*5+' + (i * 3) + '))');
        var gi = db1_sub(gc, 'water'); jzAddPath(gi, wc.inner[0], false); jzAddPath(gi, wc.inner[1], false); db1_st(gi, sc.sub, Math.max(1, u), 60, 2);
        var gl = db1_sub(gc, 'crest'); jzAddPath(gl, wc.crest, false); jzAddPath(gl, wc.face, false); db1_st(gl, sc.fg, lw, 90, 2, 2);
    }
    db1_trim(ctx, gR, ST, '100*ioc((t-0.05)/0.8)');
    db1_op(ctx, S, ST);
});

/* ---- kasumi \u2014 \u971E: layered suyari-gasumi mist bands (rounded stepped bars) drifting behind the lyric, edged with a gold hairline */
db1_reg('kasumi', true, function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, u = db1_u(ctx), k;
    var n = 2 + ((d.n | 0) % 2), hb = jzClamp(jzU(ctx) * 0.046, 28 * u, 58 * u), line = db1_acc(sc), fa = db1_dark(sc) ? 0.9 : 0.75, lw = Math.max(1, 1.2 * u);
    for (k = 0; k < n; k++) {
        var dir = (k + (d.right ? 1 : 0)) % 2 ? 1 : -1, dl = k * 0.12;
        var Lm = W * (0.34 + db1_r(d.seed, k, 1) * 0.22), yc = H * ((k + 0.5) / n) + (db1_r(d.seed, k, 2) - 0.5) * H * 0.16;
        var xc = W * (0.5 + dir * (0.2 + db1_r(d.seed, k, 3) * 0.18));
        var Lu = Lm * (0.45 + db1_r(d.seed, k, 4) * 0.2), ou = (db1_r(d.seed, k, 5) < 0.5 ? -1 : 1) * Lm * 0.22, hu = hb * 0.8, Ld = Lm * (0.3 + db1_r(d.seed, k, 6) * 0.2), od = -ou * 0.8;
        var S = jzShapeLayer(ctx, 'kasumi ' + (k + 1), xc, yc);
        jzSetExpr(jzXf(S, 'ADBE Position'), db1_H(ctx, 0) + 'var e=oc((t-' + jzN(dl) + ')/0.8);[value[0]+(' + dir + ')*((1-e)*' + jzN(W * 0.25) + '+t*' + jzN(7 * u) + '),value[1]]');
        var g1 = jzGrp(S, 'hairline'); jzAddPath(g1, [[ou - Lu / 2 + hu / 2, -hb / 2 - hu + 1], [ou + Lu / 2 - hu / 2, -hb / 2 - hu + 1]], false);
        db1_trim(ctx, g1, 0, '100*ioc((t-' + jzN(0.3 + dl) + ')/0.6)'); db1_st(g1, line, lw, 75);
        var g2 = jzGrp(S, 'hairline 2'); jzAddPath(g2, [[Lm / 2 - hb / 2, hb / 2], [-Lm / 2 + hb / 2, hb / 2]], false);
        db1_trim(ctx, g2, 0, '100*ioc((t-' + jzN(0.4 + dl) + ')/0.6)'); db1_st(g2, line, lw, 52);
        var gf = jzGrp(S, 'mist');
        jzAddRect(gf, Lm, hb, hb / 2);
        jzAddRect(gf, Lu, hu + 2, hu / 2, ou, -hb / 2 - hu / 2);
        if (db1_r(d.seed, k, 7) < 0.6) jzAddRect(gf, Ld, hu * 0.85 + 2, hu * 0.42, od, hb / 2 + (hu * 0.85) / 2);
        jzAddFill(gf, sc.dim, fa * 100);
        db1_op(ctx, S, 0, 'oc((t-' + jzN(dl) + ')/0.8)');
    }
});

// ================================================================ sci-fi / HUD

/* ---- hexGrid \u2014 \u516D\u89D2\u683C\u5B50: a honeycomb patch whose cells ripple in, a scan band (track-matted bright copy) and a few lit cells */
db1_reg('hexGrid', false, function (ctx, bb, d) {
    var sc = ctx.sc, u = db1_u(ctx), ST = db1_settle(ctx), i, j, k;
    var rc = jzClamp(jzU(ctx) * 0.021, 13 * u, 24 * u), cols = 5 + (d.n | 0) + (d.big ? 1 : 0), rows = 3 + ((d.v | 0) % 2);
    var dx = rc * 1.5, dy = rc * Math.sqrt(3), fs = db1_fs(ctx) * 0.72, w = (cols - 1) * dx + rc * 2, h = rows * dy + dy / 2 + fs * 1.8;
    var sp = db1_spot(ctx, bb, w, h, d, 24 * u), a = sp.ok ? 1 : 0.35, x0 = sp.x + rc, y0 = sp.y + fs * 1.8 + dy / 2;
    var sc0 = Math.floor((d.r || 0) * cols), sc1 = Math.floor(db1_r(d.seed, 2) * rows), lw = Math.max(1, u), hex = [], cells = [];
    for (k = 0; k <= 6; k++) hex.push([Math.cos(k * 60 * DB1_D) * rc * 0.88, Math.sin(k * 60 * DB1_D) * rc * 0.88]);
    for (i = 0; i < cols; i++) for (j = 0; j < rows; j++) cells.push([x0 + i * dx, y0 + j * dy + (i % 2 ? dy / 2 : 0), Math.sqrt((i - sc0) * (i - sc0) + (j - sc1) * (j - sc1))]);
    function grid(name, col, w0, op) {
        var S = jzShapeLayer(ctx, name, 0, 0), q;
        for (q = 0; q < cells.length; q++) {
            var g = jzGrp(S, 'cell ' + (q + 1)); jzAddPath(g, hex, true);
            jzGX(g).property('ADBE Vector Position').setValue([cells[q][0], cells[q][1]]);
            db1_gx(ctx, g, 'ADBE Vector Scale', ST, 'var q=cl((t-' + jzN(cells[q][2] * 0.045) + ')/0.3),s=q<=0?0:ob(q,1.6);[100*s,100*s]');
        }
        // one stroke at the root, below every cell group: strokes all of them, width unaffected by the cells' scale
        var st = S.property('ADBE Root Vectors Group').addProperty('ADBE Vector Graphic - Stroke');
        st.property('ADBE Vector Stroke Color').setValue(jzHex(col)); st.property('ADBE Vector Stroke Width').setValue(w0); st.property('ADBE Vector Stroke Opacity').setValue(op);
        jzXf(S, 'ADBE Opacity').setValue(a * 100); db1_op(ctx, S, ST);
        return S;
    }
    // lit cells: re-picked 2.5\u00D7 per second
    var Lt = jzShapeLayer(ctx, 'hex lit', 0, 0), nl = 3 + (d.n | 0), CE = [];
    for (k = 0; k < cells.length; k++) CE.push('[' + jzN(cells[k][0]) + ',' + jzN(cells[k][1]) + ']');
    for (k = 0; k < nl; k++) {
        var gk = jzGrp(Lt, 'lit ' + (k + 1)); jzAddPath(gk, db1_xf(hex, 0, 0, 0, 0.62), true); jzAddFill(gk, k % 2 ? db1_acc(sc) : sc.fg, k % 2 ? 90 : 50);
        db1_gx(ctx, gk, 'ADBE Vector Position', ST, 'var C=[' + CE.join(',') + '],tk=Math.floor(Math.max(0,t)*2.5);C[Math.floor(hh(tk*31.7+' + (k * 7 + 3) + '+SD*0.013)*C.length)%C.length]');
    }
    jzXf(Lt, 'ADBE Opacity').setValue(a * 100); db1_op(ctx, Lt, ST, 't>0.5?1:0');
    grid('hex grid', sc.sub, lw, 60);
    var hot = grid('hex scan', sc.fg, lw * 1.3, 95);
    var Mt = jzShapeLayer(ctx, 'hex scan band', sp.x, sp.y + h / 2), gm = jzGrp(Mt, 'band'); jzAddRect(gm, rc * 2.4, h + dy); jzAddFill(gm, '#FFFFFF');
    jzSetExpr(jzXf(Mt, 'ADBE Position'), db1_H(ctx, ST) + '[value[0]+((t*0.45+' + jzN(d.r || 0) + ')%1.4)*' + jzN(w + 40 * u) + '-' + jzN(20 * u) + ',value[1]]');
    hot.trackMatteType = TrackMatteType.ALPHA; Mt.enabled = false;
    // header
    var le = 'oe((t-0.35)/0.3)';
    var T1 = db1_label(ctx, 'SECTOR ' + db1_pad((ctx.cut.line | 0) + 1), sp.x, sp.y + fs * 0.5, { size: fs, track: 0.2, alpha: a }); db1_op(ctx, T1, ST, le);
    var T2 = db1_label(ctx, db1_pad(nl) + '/' + cols * rows, sp.x + w, sp.y + fs * 0.5, { size: fs, align: 'right', color: sc.fg, alpha: a }); db1_op(ctx, T2, ST, le);
    db1_srcText(ctx, T2, ST, 'var tk=Math.floor(Math.max(0,t)*2.5),NC=' + cells.length + ',seen={},c=0;for(var k=0;k<' + nl + ';k++){var ix=Math.floor(hh(tk*31.7+k*7+3+SD*0.013)*NC)%NC;if(!seen[ix]){seen[ix]=1;c++;}}(c<10?"0":"")+c+"/' + cells.length + '"');
    var Ln = jzShapeLayer(ctx, 'hex rule', sp.x, sp.y + fs * 1.15), gn = jzGrp(Ln, 'rule'); jzAddPath(gn, [[0, 0], [w, 0]], false);
    db1_trim(ctx, gn, ST, '100*' + le); db1_st(gn, sc.sub, lw, 50); jzXf(Ln, 'ADBE Opacity').setValue(a * 100); db1_op(ctx, Ln, ST);
});

/* ---- spectrumRing \u2014 \u5186\u5F62\u30B9\u30DA\u30AF\u30C8\u30E9\u30E0: a radial spectrum analyser (mirrored bars round a small ring) with falling peak dots */
db1_reg('spectrumRing', false, function (ctx, bb, d) {
    var sc = ctx.sc, u = db1_u(ctx), ST = db1_settle(ctx), k;
    var R0 = jzClamp(jzU(ctx) * 0.052, 34 * u, 66 * u), Lm = R0 * 0.95, sp = db1_spot(ctx, bb, (R0 + Lm) * 2.1, (R0 + Lm) * 2.1, d, 18 * u), a = sp.ok ? 1 : 0.35;
    var N = 60, ac = db1_acc(sc), bl = Math.max(1.4, Math.min(3.2 * u, Math.PI * 2 * R0 / N * 0.55)), rin = R0 + 3 * u;
    // value noise (the browser's J.noise1) + the level of bar pair k
    var NZ = 'function nz(x){var i=Math.floor(x),f=x-i,s=f*f*(3-2*f),p=hh(i*0.917+' + jzN((d.seed % 997) * 0.37) + ')*2-1,q=hh((i+1)*0.917+' + jzN((d.seed % 997) * 0.37) + ')*2-1;return p+(q-p)*s;}' +
        'var e=oe(t/0.5),bt=0.5+0.5*Math.sin(t*7);function lev(k,tt){return cl((0.55+0.45*nz(k*0.5+tt*5.5))*(1-k/30*0.55)*(0.75+0.35*bt));}\n';
    var S = jzShapeLayer(ctx, 'spectrum ring', sp.cx, sp.cy);
    var gi = jzGrp(S, 'inner ring'); db1_arcTrim(ctx, gi, R0 * 0.72, 0, 0, 90, ST, '360*oe((t-0.15)/0.5)'); db1_st(gi, sc.sub, Math.max(1, 0.8 * u), 40);
    var gr = jzGrp(S, 'ring'); db1_arcTrim(ctx, gr, R0, 0, 0, -90, ST, '360*oe(t/0.5)'); db1_st(gr, sc.sub, Math.max(1, u), 70);
    var gB = jzGrp(S, 'bars');
    db1_gx(ctx, gB, 'ADBE Vector Rotation', ST, jzN((d.r || 0) * 360) + '+t*6');
    for (k = 0; k < N / 2; k++) {
        // 'bars' is finished before 'peak' is added next to it (a new sibling invalidates the bars reference)
        var i1 = k, i2 = N - 1 - k, ie = k % 2 === 0 ? i1 : i2, g = db1_sub(gB, 'band ' + (k + 1)), gb = db1_sub(g, 'bars'), an, j;
        for (j = 0; j < 2; j++) { an = (j ? i2 : i1) / N * 360 * DB1_D; jzAddPath(gb, [[Math.cos(an) * rin, Math.sin(an) * rin], [Math.cos(an) * (rin + Lm), Math.sin(an) * (rin + Lm)]], false); }
        db1_trim(ctx, gb, ST, '(' + jzN(k / N) + '<=e?100*lev(' + k + ',t)*e:0)', null, null, NZ);
        var st = db1_st(gb, sc.fg, bl, 90);
        jzSetExpr(st.property('ADBE Vector Stroke Color'), db1_H(ctx, ST) + NZ + '(lev(' + k + ',t)>0.78?' + db1_cx(ac) + ':' + db1_cx(sc.fg) + ')');
        an = ie / N * 360 * DB1_D;
        var gp = db1_sub(g, 'peak'), ep = jzAddEllipse(gp, 2.6 * u, 2.6 * u);
        jzSetExpr(ep.property('ADBE Vector Ellipse Position'), db1_H(ctx, ST) + NZ + 'var r1=' + jzN(rin) + '+' + jzN(Lm) + '*lev(' + k + ',t)*e,pk=' + jzN(rin) + '+' + jzN(Lm) + '*cl(0.25+0.7*(0.55+0.45*nz(' + jzN(k * 0.5) + '+(t-0.25)*5.5))*(1-' + jzN(k / 30 * 0.55) + '))*e+' + jzN(5 * u) +
            ',r=Math.max(pk,r1+' + jzN(4 * u) + ');[' + jzN(Math.cos(an)) + '*r,' + jzN(Math.sin(an)) + '*r]');
        jzAddFill(gp, sc.sub, 80);
        db1_gop(ctx, gp, ST, '(' + jzN(ie / N) + '<=oe(t/0.5)?1:0)');
    }
    jzXf(S, 'ADBE Opacity').setValue(a * 100); db1_op(ctx, S, ST);
    var fs = R0 * 0.42;
    var T1 = db1_label(ctx, db1_pad((ctx.cut.line | 0) + 1), sp.cx, sp.cy - fs * 0.1, { size: fs, align: 'center', color: sc.fg, track: 0.04, alpha: a }); db1_op(ctx, T1, ST, 'oe((t-0.3)/0.3)');
    var T2 = db1_label(ctx, 'Hz', sp.cx, sp.cy + fs * 0.75, { size: fs * 0.42, align: 'center', track: 0.2, alpha: a }); db1_op(ctx, T2, ST, 'oe((t-0.4)/0.3)');
});

/* ---- dataColumns \u2014 \u30C7\u30FC\u30BF\u5217: a memory-dump panel \u2014 address / bytes / code-point rows scrolling up (one text layer, Source Text
   expression + a Position animator for the step scroll, masked to its window), newest row flagged */
db1_reg('dataColumns', false, function (ctx, bb, d) {
    var sc = ctx.sc, u = db1_u(ctx), ST = db1_settle(ctx), c = ctx.cut, i;
    var fs = db1_fs(ctx) * 0.9, rh = fs * 1.55, nR = 6 + (d.n | 0), font = jzMonoF(ctx), ac = db1_acc(sc);
    var chs = jzChars(String(c.text || '')), cps = [], lines = [];
    for (i = 0; i < chs.length; i++) if (jzTrim(chs[i])) cps.push(chs[i].charCodeAt(0));
    if (!cps.length) cps.push(65);
    for (i = 0; i < nR + 2; i++) lines.push('0x0000  00 00 00 00  U+0000');
    var T = jzText(ctx, lines.join('\r'), { font: font, size: fs, color: sc.sub, x: 0, y: 0, align: 'left', track: 0.06, leading: rh });
    var tw = jzSize(T)[0], w = tw + 22 * u, hwin = nR * rh, h = hwin + fs * 2.2;
    var sp = db1_spot(ctx, bb, w, h, d, 24 * u), a = sp.ok ? 1 : 0.35, x0 = sp.x + 14 * u, yTop = sp.y + fs * 2.2;
    // anchor on the first row's baseline (origin of a left-aligned text layer): row k sits at yTop + (k + 1) * rh - rh / 2
    var anc = [0, 0], pos = [x0, yTop - rh / 2 + fs * 0.36];
    jzXf(T, 'ADBE Anchor Point').setValue(anc); jzXf(T, 'ADBE Position').setValue(pos);
    var H7 = db1_h(d.seed, 7) & 0xff;
    db1_srcText(ctx, T, ST, 'function hx(n,k){var s=(n>>>0).toString(16).toUpperCase();while(s.length<k)s="0"+s;return s.substr(s.length-k);}' +
        'var CP=[' + cps.join(',') + '],n0=Math.floor(t*2.6),o=[];for(var k=-1;k<=' + nR + ';k++){var n=n0+k,b=[];for(var j=0;j<4;j++)b.push(hx(Math.floor(hh(n*7.31+j*1.93+' + jzN((d.seed % 9973) * 0.011) + ')*256),2));' +
        'var ci=((n%CP.length)+CP.length)%CP.length;o.push("0x"+hx(' + H7 * 256 + '+n*16,4)+"  "+b.join(" ")+"  U+"+hx(CP[ci],4));}o.join("\\r")');
    // step scroll (one row per 1/2.6 s, eased), row fade by age, newest row in fg
    jzAnimator(T, 'JZ Scroll', [['ADBE Text Position 3D', [0, -rh, 0]]], db1_H(ctx, ST) + 'var f=t*2.6-Math.floor(t*2.6);100*ioc(f*2.5)');
    jzAnimator(T, 'JZ Age', [['ADBE Text Opacity', 0]], 'var L=Math.floor((textIndex-1)/27)-1,NR=' + nR + ';L===NR-1?0:100*(1-(0.3+0.6*(1-Math.max(0,NR-1-L)/NR)))');
    jzAnimator(T, 'JZ Newest', [['ADBE Text Fill Color', jzHex(sc.fg)]], 'Math.floor((textIndex-1)/27)-1===' + (nR - 1) + '?100:0');
    db1_mask(T, db1_rectShape(sp.x - 2 * u - pos[0] + anc[0], yTop - pos[1] + anc[1], sp.x + w + 2 * u - pos[0] + anc[0], yTop + hwin - pos[1] + anc[1]));
    jzXf(T, 'ADBE Opacity').setValue(a * 100); db1_op(ctx, T, ST, 'oe(t/0.5)');
    // header + rules + newest-row flag
    var T1 = db1_label(ctx, 'MEM', sp.x, sp.y + fs * 0.6, { size: fs, color: ac, track: 0.2, alpha: a }); db1_op(ctx, T1, ST, 'oe(t/0.5)');
    var T2 = db1_label(ctx, 'LYRIC_' + db1_pad((c.line | 0) + 1), sp.x + w, sp.y + fs * 0.6, { size: fs, align: 'right', track: 0.1, alpha: a }); db1_op(ctx, T2, ST, 'oe(t/0.5)');
    var S = jzShapeLayer(ctx, 'data rules', 0, 0);
    var gf = jzGrp(S, 'flag'); jzAddPath(gf, [[sp.x + 3 * u, -4 * u], [sp.x + 9 * u, 0], [sp.x + 3 * u, 4 * u]], true); jzAddFill(gf, ac);
    db1_gx(ctx, gf, 'ADBE Vector Position', ST, 'var f=t*2.6-Math.floor(t*2.6);[0,' + jzN(yTop + nR * rh - rh * 0.5) + '-ioc(f*2.5)*' + jzN(rh) + ']');
    var g1 = jzGrp(S, 'rule'); jzAddPath(g1, [[sp.x, sp.y + fs * 1.4], [sp.x + w, sp.y + fs * 1.4]], false); db1_trim(ctx, g1, ST, '100*oe(t/0.5)'); db1_st(g1, sc.sub, Math.max(1, u), 70);
    var g2 = jzGrp(S, 'side'); jzAddPath(g2, [[sp.x, yTop], [sp.x, yTop + hwin]], false); db1_trim(ctx, g2, ST, '100*oe(t/0.5)'); db1_st(g2, sc.sub, Math.max(1, u), 50);
    jzXf(S, 'ADBE Opacity').setValue(a * 100); db1_op(ctx, S, ST);
});

/* ---- spinner \u2014 \u8AAD\u307F\u8FBC\u307F: a loading spinner (segments, twin arcs or orbiting dots) that resolves into a check mark */
db1_reg('spinner', false, function (ctx, bb, d) {
    var sc = ctx.sc, u = db1_u(ctx), ST = db1_settle(ctx), c = ctx.cut, i;
    var R = jzClamp(jzU(ctx) * 0.036, 22 * u, 44 * u), fs = db1_fs(ctx) * 0.95, font = jzMonoF(ctx), ac = db1_acc(sc);
    var T = db1_label(ctx, 'LOADING...', 0, 0, { size: fs, font: font, color: sc.fg, track: 0.16 }), tw = jzSize(T)[0];
    var w = R * 2 + 14 * u + tw, h = R * 2 + 4 * u, sp = db1_spot(ctx, bb, w, h, d, 22 * u), a = sp.ok ? 1 : 0.35;
    var left = sp.cx < ctx.W / 2 || !sp.ok, cx = left ? sp.x + R : sp.x + w - R, cy = sp.cy;
    var tDone = jzClamp(c.dur * 0.55, 0.9, 2.4), v = (d.v | 0) % 3, lw = Math.max(1.4, 2.2 * u), DN = 'var dn=cl((t-' + jzN(tDone) + ')/0.35);';
    var S = jzShapeLayer(ctx, 'spinner', cx, cy);
    var gk = jzGrp(S, 'check');
    var gc = db1_sub(gk, 'tick'); jzAddPath(gc, [[-R * 0.42, R * 0.02], [-R * 0.1, R * 0.32], [R * 0.45, -R * 0.3]], false);
    db1_trim(ctx, gc, ST, '100*ioc((t-' + jzN(tDone + 0.15) + ')/0.3)'); db1_st(gc, ac, lw, null, 2, 2);
    var ga = db1_sub(gk, 'circle'); db1_arcTrim(ctx, ga, R, 0, 0, -90, ST, '360*oc(cl((t-' + jzN(tDone) + ')/0.35))'); db1_st(ga, ac, lw);
    db1_gop(ctx, gk, ST, 't>=' + jzN(tDone) + '?1:0');
    var gS = jzGrp(S, 'spin');
    db1_gx(ctx, gS, 'ADBE Vector Scale', ST, 'var e=ob(cl(t/0.35),1.6);[100*e,100*e]');
    db1_gop(ctx, gS, ST, '1-dn', DN);
    if (v === 0) {
        var gg = db1_sub(gS, 'segments'), g1 = db1_sub(gg, 'segment'); jzAddPath(g1, [[R * 0.5, 0], [R, 0]], false); db1_st(g1, sc.fg, lw, null, 2);
        var rp = db1_rep(gg, 12, 0, 0, -30);
        try { var rt = rp.property('ADBE Vector Repeater Transform'); rt.property('ADBE Vector Repeater Opacity 1').setValue(100); rt.property('ADBE Vector Repeater Opacity 2').setValue(16); } catch (e1) {}
        db1_gx(ctx, gg, 'ADBE Vector Rotation', ST, 'Math.floor(Math.max(0,t)*12)*30');
    } else if (v === 1) {
        var g0 = db1_sub(gS, 'track'); jzAddEllipse(g0, R * 2, R * 2); db1_st(g0, sc.sub, Math.max(1, 0.8 * u), 25);
        var gA = db1_sub(gS, 'arc'); jzAddEllipse(gA, R * 2, R * 2); db1_trim(ctx, gA, ST, '(90+60*Math.sin(t*4))/3.6', null, 't*300+90'); db1_st(gA, sc.fg, lw);
        var gB = db1_sub(gS, 'arc 2'); jzAddEllipse(gB, R * 1.24, R * 1.24); db1_trim(ctx, gB, ST, '(90+60*Math.sin(t*4))*0.8/3.6', null, '-t*200+90'); db1_st(gB, ac, lw);
    } else {
        var gd = db1_sub(gS, 'dots');
        for (i = 0; i < 8; i++) { var an = -i * 26 * DB1_D, r = (3.4 - i * 0.35) * u, gi = db1_sub(gd, 'dot ' + i); jzAddEllipse(gi, r * 2, r * 2, Math.cos(an) * R * 0.8, Math.sin(an) * R * 0.8); jzAddFill(gi, i ? sc.fg : ac, i ? 70 : 100); }
        db1_gx(ctx, gd, 'ADBE Vector Rotation', ST, 't*280');
    }
    jzXf(S, 'ADBE Opacity').setValue(a * 100); db1_op(ctx, S, ST);
    var lx = left ? cx + R + 12 * u : cx - R - 12 * u - tw;
    jzXf(T, 'ADBE Position').setValue([lx, cy - fs * 0.35]); jzXf(T, 'ADBE Opacity').setValue(a * 100);
    db1_srcText(ctx, T, ST, DN + 'var k=Math.floor(Math.max(0,t)*3)%4,s="LOADING";for(var i=0;i<k;i++)s+=".";dn>0.5?"COMPLETE":s');
    db1_txtCol(ctx, T, ac, ST, DN + 'dn>0.5?100:0');
    db1_op(ctx, T, ST, 'oe((t-0.1)/0.3)');
    var T2 = db1_label(ctx, '000%  00000ms', lx, cy + fs * 0.75, { size: fs * 0.72, font: font, track: 0.1, alpha: a * 0.8 });
    db1_srcText(ctx, T2, ST, DN + 'function pd(n,k){var s=String(Math.max(0,Math.floor(n)));while(s.length<k)s="0"+s;return s;}var pr=dn>0?100:Math.min(99,Math.floor(100*(1-Math.exp(-t/' + jzN(tDone) + '*2.2))));pd(pr,3)+"%  "+pd(t*1000,5)+"ms"');
    db1_op(ctx, T2, ST, 'oe((t-0.2)/0.3)');
});

/* ---- headingTape \u2014 \u65B9\u4F4D\u30C6\u30FC\u30D7: a scrolling compass heading tape (Repeater ticks under a feathered window mask), fixed pointer, boxed readout */
db1_reg('headingTape', false, function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, u = db1_u(ctx), m = db1_mg(ctx), ST = db1_settle(ctx), j;
    var fs = db1_fs(ctx) * 0.85, font = jzMonoF(ctx), gap = 22 * u, ht = fs * 1.4 + 16 * u + fs * 2.2, ac = db1_acc(sc);
    var aboveRoom = bb.y0 - gap - m * 0.6, belowRoom = H - m * 0.6 - bb.y1 - gap, above = !d.low;
    if ((above ? aboveRoom : belowRoom) < ht && (above ? belowRoom : aboveRoom) > (above ? aboveRoom : belowRoom)) above = !above;
    var room = above ? aboveRoom : belowRoom, compact = room < ht; if (compact) ht = fs * 1.4 + 18 * u;
    var yT = above ? Math.max(m * 0.6, bb.y0 - gap - ht - Math.max(0, (room - ht) * 0.35)) : bb.y1 + gap + Math.max(0, (room - ht) * 0.35);
    var a = room >= ht * 0.8 ? 1 : 0.35, Wt = Math.min(W * 0.56, 620 * u, W - 2 * m), cx = jzClamp((bb.x0 + bb.x1) / 2, m + Wt / 2, W - m - Wt / 2);
    var k = Wt / 100, base = yT + fs * 1.4 + 14 * u, lw = Math.max(1, u), pw = 6 * u, py = base + 3 * u;
    var HD = 'var HG=((' + jzN((d.r || 0) * 360) + '+t*' + (d.right ? 7 : -7) + '+18*Math.sin(t*0.6+' + jzN((d.r || 0) * 5) + '))%360+360)%360,E=oe(t/0.55)*(1-0.7*ic(PO));';
    // ticks: a 15\u00B0 unit (long + 2 short) repeated, scrolled by the heading, windowed by a feathered mask that opens from the centre
    var S = jzShapeLayer(ctx, 'heading ticks', 0, 0), gt = jzGrp(S, 'tape'), gu = db1_sub(gt, 'unit');
    jzAddPath(gu, [[0, base], [0, base - 12 * u]], false); jzAddPath(gu, [[5 * k, base], [5 * k, base - 6 * u]], false); jzAddPath(gu, [[10 * k, base], [10 * k, base - 6 * u]], false);
    db1_st(gu, sc.fg, lw, 90);
    db1_rep(gt, 9, 15 * k, 0);
    db1_gx(ctx, gt, 'ADBE Vector Position', ST, HD + '[' + jzN(cx) + '+(Math.floor((HG-60)/15)*15-HG)*' + jzN(k) + ',0]');
    var mk = db1_mask(S, db1_rectShape(cx - Wt / 2, base - 20 * u - Wt / 2, cx + Wt / 2, base + 2 * u + Wt / 2));
    mk.property('ADBE Mask Feather').setValue([Wt * 0.3, 0]);
    jzSetExpr(mk.property('ADBE Mask Offset'), db1_H(ctx, ST) + HD + '-(1-E)*' + jzN(Wt / 2));
    jzXf(S, 'ADBE Opacity').setValue(a * 100); db1_op(ctx, S, ST);
    // degree labels: 7 slots riding on the tape
    for (j = 0; j < 7; j++) {
        var DG = HD + 'var dg=Math.ceil((HG-50)/15)*15+' + (j * 15) + ',dd=((dg%360)+360)%360,x=' + jzN(cx) + '+(dg-HG)*' + jzN(k) + ',f=Math.abs(x-' + jzN(cx) + ')/' + jzN(Wt / 2) + ';';
        var TL = db1_label(ctx, '000', cx, base - 12 * u - fs * 0.75, { size: fs, font: font, align: 'center', track: 0.04, alpha: a });
        db1_srcText(ctx, TL, ST, DG + 'dd===0?"N":dd===90?"E":dd===180?"S":dd===270?"W":(dd<10?"00":dd<100?"0":"")+dd');
        jzSetExpr(jzXf(TL, 'ADBE Position'), db1_H(ctx, ST) + DG + '[x,value[1]]');
        db1_txtCol(ctx, TL, ac, ST, DG + '(dd%90===0)?100:0');
        db1_op(ctx, TL, ST, '(function(){' + DG + 'return (f<0.92&&f<=E)?cl((1-f)*1.2):0;})()');
    }
    // base line, pointer and the boxed readout
    var tr = db1_label(ctx, '000\u00B0', 0, 0, { size: fs * 1.1, font: font, align: 'center', color: sc.fg, track: 0.08 }), tw = jzSize(tr)[0] + 14 * u, bh0 = fs * 1.6;
    var bx = compact ? cx + Wt / 2 + 12 * u : cx - tw / 2, by = compact ? base - 6 * u - bh0 / 2 : py + pw * 1.3 + 4 * u;
    var F = jzShapeLayer(ctx, 'heading hud', 0, 0);
    var gbx = jzGrp(F, 'box'), rb = jzAddRect(gbx, tw, bh0, 2 * u, bx + tw / 2, by + bh0 / 2); db1_st(gbx, sc.fg, lw);
    jzGX(gbx).property('ADBE Vector Anchor').setValue([bx, by]); jzGX(gbx).property('ADBE Vector Position').setValue([bx, by]);
    db1_gx(ctx, gbx, 'ADBE Vector Scale', ST, '[100,100*oe((t-0.25)/0.3)]');
    var gpt = jzGrp(F, 'pointer'); jzAddPath(gpt, [[cx, py], [cx - pw, py + pw * 1.3], [cx + pw, py + pw * 1.3]], true); jzAddFill(gpt, ac);
    var gl = jzGrp(F, 'base line'); jzAddPath(gl, [[cx - Wt / 2, base], [cx + Wt / 2, base]], false); db1_st(gl, sc.sub, lw, 60);
    jzGX(gl).property('ADBE Vector Anchor').setValue([cx, base]); jzGX(gl).property('ADBE Vector Position').setValue([cx, base]);
    db1_gx(ctx, gl, 'ADBE Vector Scale', ST, HD + '[100*E,100]');
    jzXf(F, 'ADBE Opacity').setValue(a * 100); db1_op(ctx, F, ST);
    jzXf(tr, 'ADBE Position').setValue([bx + tw / 2, by + bh0 / 2]); jzXf(tr, 'ADBE Opacity').setValue(a * 100);
    db1_srcText(ctx, tr, ST, HD + 'var h=Math.floor(HG);(h<10?"00":h<100?"0":"")+h+"\u00B0"');
    db1_op(ctx, tr, ST, 'cl((oe((t-0.25)/0.3)-0.6)/0.4)');
    var th = db1_label(ctx, 'HDG', compact ? cx - Wt / 2 - 10 * u : bx - 8 * u, by + bh0 / 2, { size: fs * 0.72, font: font, align: 'right', track: 0.2, alpha: a });
    db1_op(ctx, th, ST, 'oe((t-0.25)/0.3)');
});

/* ---- glyphLock \u2014 \u6587\u5B57\u30ED\u30C3\u30AF: HUD lock-on corners hopping from glyph to glyph along the lyric's outer edges, leaving index ticks.
   AE has no per-glyph boxes for the lyric: the glyph cells are estimated from the bbox (line count from its shape). */
function db1_q(s) { return '"' + String(s).replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"'; }
db1_reg('glyphLock', false, function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, u = db1_u(ctx), m = db1_mg(ctx), ST = db1_settle(ctx), i, j;
    var vert = db1_isVert(bb), G = jzGlyphs(jzStrip(ctx.cut.text || '')), n = Math.max(1, Math.min(12, G.length || 1));
    var a0 = vert ? bb.y0 : bb.x0, a1 = vert ? bb.y1 : bb.x1, along = a1 - a0, across = vert ? db1_bw(bb) : db1_bh(bb);
    var NL = Math.max(1, Math.min(n, Math.round(Math.sqrt(n * across / (1.15 * Math.max(1, along)))))), per = Math.ceil(n / NL), cs = along / per, nb = n - (NL - 1) * per;
    var CA = [], CB = [], gw = cs * 0.9;
    for (i = 0; i < per; i++) CA.push(a0 + (i + 0.5) * cs);
    for (i = 0; i < nb; i++) CB.push((a0 + a1) / 2 - nb * cs / 2 + (i + 0.5) * cs);
    if (NL === 1) CB = CA;
    var pad = 12 * u + across * 0.07, lw = Math.max(1.2, 1.9 * u), ac = db1_acc(sc), OFF = db1_h(d.seed, 1) % 3;
    function arr(A) { var o = []; for (var q = 0; q < A.length; q++) o.push(jzN(A[q])); return '[' + o.join(',') + ']'; }
    var HOP = 'var tt=Math.max(0,t-0.15),k=Math.floor(tt/0.34),f=oe((tt-k*0.34)/0.16);function ix(i,n){return ((i+' + OFF + ')%n+n)%n;}' +
        'function at(C,i){var p=C[ix(i-1,C.length)],q=C[ix(i,C.length)];return k===0?q:p+(q-p)*f;}var CA=' + arr(CA) + ',CB=' + arr(CB) + ';';
    var S = jzShapeLayer(ctx, 'glyph lock', 0, 0);
    // corners: side -1 = top / right edge (accent, the active glyph), +1 = bottom / left edge (fg, two glyphs ahead)
    var sides = [[-1, 'CA', 'k', ac, 100], [1, 'CB', 'k+2', sc.fg, 80]], sd;
    for (sd = 0; sd < 2; sd++) {
        var side = sides[sd][0], Lh = Math.min(gw * 0.36, 22 * u), Lv = pad * 0.8, h0 = gw / 2 + 3 * u, g = jzGrp(S, side < 0 ? 'lock A' : 'lock B'), q;
        var fix = vert ? (side < 0 ? bb.x1 + pad : bb.x0 - pad) : (side < 0 ? bb.y0 - pad : bb.y1 + pad), dn = vert ? (side < 0 ? -1 : 1) : (side < 0 ? 1 : -1);
        db1_gx(ctx, g, 'ADBE Vector Position', ST, HOP + 'var c=at(' + sides[sd][1] + ',' + sides[sd][2] + ');' + (vert ? '[' + jzN(fix) + ',c]' : '[c,' + jzN(fix) + ']'));
        for (q = -1; q <= 1; q += 2) {
            var gc = db1_sub(g, q < 0 ? 'lo' : 'hi'), pts;
            if (!vert) pts = [[q * h0, dn * Lv], [q * h0, 0], [q * h0 - q * Lh, 0]];
            else pts = [[dn * Lv, q * h0], [0, q * h0], [0, q * h0 - q * Lh]];
            jzAddPath(gc, pts, false);
            var sp = '(1-oe(t/0.3))*' + jzN(10 * u);
            db1_gx(ctx, gc, 'ADBE Vector Position', ST, vert ? '[' + (-dn) + '*' + sp + ',' + q + '*' + sp + ']' : '[' + q + '*' + sp + ',' + (-dn) + '*' + sp + ']');
        }
        db1_st(g, sides[sd][3], lw, sides[sd][4]);
        db1_gop(ctx, g, ST, 'oe(t/0.3)');
    }
    // visited ticks along edge A
    var gT = jzGrp(S, 'ticks');
    for (j = 0; j < CA.length; j++) {
        var c = CA[(j + OFF) % CA.length], gt = db1_sub(gT, 'tick ' + (j + 1));
        if (!vert) jzAddPath(gt, [[c, bb.y0 - pad * 0.45], [c, bb.y0 - pad * 0.45 - 5 * u]], false);
        else jzAddPath(gt, [[bb.x1 + pad * 0.45, c], [bb.x1 + pad * 0.45 + 5 * u, c]], false);
        db1_st(gt, sc.sub, Math.max(1, u), 60);
        db1_gop(ctx, gt, ST, 'Math.floor(Math.max(0,t-0.15)/0.34)>=' + j + '?1:0');
    }
    // target label
    var fs = db1_fs(ctx) * 0.9, le = 'oe((t-0.2)/0.25)', GS = [], TX = HOP + 'var ii=ix(k,CA.length);';
    for (i = 0; i < G.length; i++) GS.push(db1_q(G[i]));
    var txt = 'TGT 01/' + db1_pad(CA.length);
    if (!vert) {
        var up = bb.y0 - pad - 14 * u - fs > m * 0.4, ly = up ? bb.y0 - pad - 10 * u - fs * 0.6 : bb.y1 + pad + 14 * u + fs * 0.6;
        var gL = jzGrp(S, 'leader'); jzAddPath(gL, [[0, up ? bb.y0 - pad - 2 * u : bb.y1 + pad + 2 * u], [0, up ? ly + fs * 0.6 : ly - fs * 0.6]], false); db1_st(gL, ac, Math.max(1, u));
        var LX = HOP + 'var lx=Math.max(' + m + ',Math.min(' + (W - m) + ',at(CA,k)));';
        db1_gx(ctx, gL, 'ADBE Vector Position', ST, LX + '[lx,0]'); db1_gop(ctx, gL, ST, le);
        var T1 = db1_label(ctx, txt, 0, ly, { size: fs, color: sc.fg, track: 0.12 });
        jzSetExpr(jzXf(T1, 'ADBE Position'), db1_H(ctx, ST) + LX + '[lx+' + jzN(6 * u) + ',value[1]]');
        db1_srcText(ctx, T1, ST, TX + 'var s=String(ii+1);"TGT "+(s.length<2?"0":"")+s+"/' + db1_pad(CA.length) + '"'); db1_op(ctx, T1, ST, le);
        if (G.length) {
            var T2 = db1_label(ctx, G[0], 0, ly, { size: fs * 1.1, font: jzBodyF(ctx), align: 'right', color: ac });
            jzSetExpr(jzXf(T2, 'ADBE Position'), db1_H(ctx, ST) + LX + '[lx-' + jzN(6 * u) + ',value[1]]');
            db1_srcText(ctx, T2, ST, TX + 'var G=[' + GS.join(',') + '];G[ii]||""'); db1_op(ctx, T2, ST, le);
        }
    } else {
        var right = bb.x1 + pad + 16 * u + fs * 0.7 * txt.length < W - m * 0.4, lx = right ? bb.x1 + pad + 12 * u : bb.x0 - pad - 12 * u;
        var T3 = db1_label(ctx, txt, lx, 0, { size: fs, color: sc.fg, align: right ? 'left' : 'right', track: 0.12 });
        jzSetExpr(jzXf(T3, 'ADBE Position'), db1_H(ctx, ST) + HOP + '[value[0],Math.max(' + m + ',Math.min(' + (H - m) + ',at(CA,k)))]');
        db1_srcText(ctx, T3, ST, TX + 'var s=String(ii+1);"TGT "+(s.length<2?"0":"")+s+"/' + db1_pad(CA.length) + '"'); db1_op(ctx, T3, ST, le);
    }
    db1_op(ctx, S, ST);
});

/* ---- atomOrbit \u2014 \u539F\u5B50\u8ECC\u9053: three tilted orbit ellipses round a small nucleus, electrons with comet trails (dimmer when behind) */
db1_reg('atomOrbit', false, function (ctx, bb, d) {
    var sc = ctx.sc, u = db1_u(ctx), ST = db1_settle(ctx), k, i;
    var R = jzClamp(jzU(ctx) * 0.072, 46 * u, 92 * u), fs = db1_fs(ctx) * 0.75, sp = db1_spot(ctx, bb, R * 2.3, R * 2.3 + fs * 1.6, d, 22 * u), a = sp.ok ? 1 : 0.35;
    var cx = sp.cx, cy = sp.y + R * 1.15, ac = db1_acc(sc), lw = Math.max(1, 1.1 * u), ry = R * 0.34, dir = d.right ? 1 : -1;
    // arc-length fraction of the ellipse path (AE starts it at the top, clockwise) every 10\u00B0 of the parametric angle
    var F = [0], tot = 0, acc = [], px = 0, py = -ry;
    for (i = 1; i <= 360; i++) { var an = (-90 + i) * DB1_D, x = Math.cos(an) * R, y = Math.sin(an) * ry; tot += Math.sqrt((x - px) * (x - px) + (y - py) * (y - py)); acc.push(tot); px = x; py = y; }
    for (i = 1; i <= 36; i++) F.push(jzN(acc[i * 10 - 1] / tot));
    var FR = 'var FF=[' + F.join(',') + '];function fr(a){var x=((a+90)%360+360)%360/10,i=Math.floor(x),g=x-i;return FF[i]+(FF[Math.min(36,i+1)]-FF[i])*g;}';
    var N = jzShapeLayer(ctx, 'atom nucleus', cx, cy), gn = jzGrp(N, 'nucleus');
    var gr = db1_sub(gn, 'ring'); jzAddEllipse(gr, 26 * u, 26 * u); db1_st(gr, sc.sub, Math.max(1, 0.8 * u), 50);
    var gf = db1_sub(gn, 'fg'); jzAddEllipse(gf, 10 * u, 10 * u, 2.5 * u, -2.75 * u); jzAddEllipse(gf, 10 * u, 10 * u, -2.5 * u, 2.5 * u); jzAddFill(gf, sc.fg);
    var ga = db1_sub(gn, 'acc'); jzAddEllipse(ga, 10 * u, 10 * u, -3 * u, -1.5 * u); jzAddEllipse(ga, 10 * u, 10 * u, 3 * u, 1.75 * u); jzAddFill(ga, ac);
    db1_gx(ctx, gn, 'ADBE Vector Scale', ST, 'var q=cl((t-0.1)/0.35),s=(q<=0?0:ob(q,2))*(1+0.06*Math.sin(t*6));[100*s,100*s]');
    jzXf(N, 'ADBE Opacity').setValue(a * 100); db1_op(ctx, N, ST);
    var S = jzShapeLayer(ctx, 'atom orbits', cx, cy);
    for (k = 2; k >= 0; k--) {
        var w = (1.4 + k * 0.35) * (k % 2 ? -1 : 1), ph = db1_r(d.seed, k) * 360, span = 0.28 * Math.abs(w) * 90, col = k === 0 ? ac : sc.fg;
        var AN = 'var an=' + jzN(ph) + '+t*' + jzN(w * 90) + ',z=Math.sin(an*Math.PI/180);';
        var g = jzGrp(S, 'orbit ' + (k + 1));
        db1_gx(ctx, g, 'ADBE Vector Rotation', ST, jzN((d.r || 0) * 180 + k * 60) + '+t*' + (5 * dir));
        var ge = db1_sub(g, 'electron'), el = jzAddEllipse(ge, 7.6 * u, 7.6 * u);
        jzSetExpr(el.property('ADBE Vector Ellipse Position'), db1_H(ctx, ST) + AN + '[' + jzN(R) + '*Math.cos(an*Math.PI/180),' + jzN(ry) + '*z]');
        jzSetExpr(el.property('ADBE Vector Ellipse Size'), db1_H(ctx, ST) + AN + 'var r=z<0?' + jzN(2.6 * u) + ':' + jzN(3.8 * u) + ';[2*r,2*r]');
        jzAddFill(ge, col);
        db1_gop(ctx, ge, ST, '(ioc((t-' + jzN(k * 0.07) + ')/0.45)>=0.9?1:0)*(z<0?0.45:1)', AN);     // before 'trail' is added (it invalidates ge)
        var gt = db1_sub(g, 'trail'); jzAddEllipse(gt, R * 2, ry * 2);
        var lo = w > 0 ? 'an-' + jzN(span) : 'an', hi = w > 0 ? 'an' : 'an+' + jzN(span);
        db1_trim(ctx, gt, ST, '100*(DD<0?DD+1:DD)', null, '360*fr(' + lo + ')', AN + FR + 'var DD=fr(' + hi + ')-fr(' + lo + ');');
        db1_st(gt, col, 1.8 * u, 45, 2);
        db1_gop(ctx, gt, ST, '(ioc((t-' + jzN(k * 0.07) + ')/0.45)>=0.9?1:0)*(z<0?0.45:1)', AN);
        var go = db1_sub(g, 'orbit'); jzAddEllipse(go, R * 2, ry * 2);
        var to = db1_trim(ctx, go, ST, '100*ioc((t-' + jzN(k * 0.07) + ')/0.45)'); to.property('ADBE Vector Trim Offset').setValue(90);
        db1_st(go, sc.fg, lw, 55);
    }
    jzXf(S, 'ADBE Opacity').setValue(a * 100); db1_op(ctx, S, ST);
    var T = db1_label(ctx, 'ORB-3  e\u207B3', cx, cy + R * 1.15 + fs * 0.4, { size: fs, align: 'center', track: 0.2, alpha: a }); db1_op(ctx, T, ST, 'oe((t-0.5)/0.3)');
});

/* ---- sonarArcs \u2014 \u97F3\u6CE2: sound-wave arcs radiating outward from both sides of the lyric (above / below for vertical text) */
db1_reg('sonarArcs', false, function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, u = db1_u(ctx), m = db1_mg(ctx), ST = db1_settle(ctx), i, k;
    var cx = (bb.x0 + bb.x1) / 2, cy = (bb.y0 + bb.y1) / 2, roomH = Math.min(bb.x0, W - bb.x1) - m * 0.5, roomV = Math.min(bb.y0, H - bb.y1) - m * 0.5;
    var vert = db1_isVert(bb);
    if ((vert ? db1_bh(bb) / H : db1_bw(bb) / W) > 0.66 && (vert ? roomH : roomV) > 60 * u) vert = !vert;
    var g0 = 16 * u + (vert ? db1_bw(bb) : db1_bh(bb)) * 0.04, ac = db1_acc(sc), span = vert ? 40 : 34;
    var sides = vert ? [[cx, bb.y0 - g0, -90, bb.y0 - g0 - m * 0.5], [cx, bb.y1 + g0, 90, H - m * 0.5 - bb.y1 - g0]]
        : [[bb.x0 - g0, cy, 180, bb.x0 - g0 - m * 0.5], [bb.x1 + g0, cy, 0, W - m * 0.5 - bb.x1 - g0]];
    var S = jzShapeLayer(ctx, 'sonar arcs', 0, 0);
    for (i = 0; i < 2; i++) {
        var ex = sides[i][0], ey = sides[i][1], dr = sides[i][2], room = sides[i][3];
        if (room < 30 * u) continue;
        var rM = Math.min(room, 190 * u), r0 = 8 * u, g = jzGrp(S, 'side ' + (i + 1));
        jzGX(g).property('ADBE Vector Position').setValue([ex, ey]);
        var gd = db1_sub(g, 'dot'), ed = jzAddEllipse(gd, 4.4 * u, 4.4 * u);
        jzSetExpr(ed.property('ADBE Vector Ellipse Size'), db1_H(ctx, ST) + 'var r=' + jzN(2.2 * u) + '*oc(t/0.4);[2*r,2*r]');
        jzAddFill(gd, ac);     // after the ellipse is configured (the fill invalidates ed)
        var gs = db1_sub(g, 'speaker');
        for (k = 1; k <= 2; k++) jzAddPath(gs, db1_arc(0, 0, k * 7 * u, dr - span * 1.2, dr + span * 1.2, 12), false);
        db1_st(gs, sc.sub, Math.max(1, 1.2 * u), 80, 2);
        db1_gx(ctx, gs, 'ADBE Vector Scale', ST, 'var e=oc(t/0.4);[100*e,100*e]');
        for (k = 0; k < 4; k++) {
            var P = 'var LN=0.62,sn=((t%LN)+LN)%LN,p=(sn+' + k + '*LN)/(LN*4),al=Math.pow(1-p,1.3),r=(' + jzN(r0) + '+' + jzN(rM - r0) + '*oc(p))*oc(t/0.4);';
            var gw = db1_sub(g, 'wave ' + (k + 1)), ew = jzAddEllipse(gw, 20, 20);
            jzSetExpr(ew.property('ADBE Vector Ellipse Size'), db1_H(ctx, ST) + P + '[2*r,2*r]');
            var tr = db1_trim(ctx, gw, ST, jzN(2 * span / 3.6)); tr.property('ADBE Vector Trim Offset').setValue(dr - span + 90);
            var st = db1_st(gw, sc.fg, 1.6 * u, 60, 2);
            jzSetExpr(st.property('ADBE Vector Stroke Color'), db1_H(ctx, ST) + P + '(al>0.66?' + db1_cx(ac) + ':' + db1_cx(sc.fg) + ')');
            jzSetExpr(st.property('ADBE Vector Stroke Width'), db1_H(ctx, ST) + P + '(al>0.66?' + jzN(2.4 * u) + ':al>0.33?' + jzN(1.6 * u) + ':' + jzN(1.1 * u) + ')');
            jzSetExpr(st.property('ADBE Vector Stroke Opacity'), db1_H(ctx, ST) + P + '(al>0.66?95:al>0.33?60:30)');
        }
    }
    db1_op(ctx, S, ST);
});

/* ---- circuit \u2014 \u56DE\u8DEF: circuit traces routed with 45\u00B0 bends from a screen edge toward the lyric, pads, vias and travelling pulses */
db1_reg('circuit', false, function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, u = db1_u(ctx), ST = db1_settle(ctx), i, j;
    var gap = 18 * u + Math.min(db1_bw(bb), db1_bh(bb)) * 0.05, rooms = { l: bb.x0 - gap, r: W - bb.x1 - gap, t: bb.y0 - gap, b: H - bb.y1 - gap };
    var order = ['l', 'r', 't', 'b'];
    for (i = 1; i < order.length; i++) for (j = i; j > 0 && rooms[order[j]] > rooms[order[j - 1]]; j--) { var tp = order[j]; order[j] = order[j - 1]; order[j - 1] = tp; }
    var pref = d.right ? 'r' : 'l';
    if (rooms[pref] > 140 * u) { var o2 = [pref]; for (i = 0; i < order.length; i++) if (order[i] !== pref) o2.push(order[i]); order = o2; }
    var sides = [order[0]]; if (d.big && rooms[order[1]] > 140 * u) sides.push(order[1]);
    var ac = db1_acc(sc), lw = Math.max(1, 1.3 * u), S = jzShapeLayer(ctx, 'circuit', 0, 0), si;
    for (si = 0; si < sides.length; si++) {
        var side = sides[si], sEnd = rooms[side]; if (sEnd < 70 * u) continue;
        var horiz = side === 'l' || side === 'r';
        var tc = horiz ? jzClamp((bb.y0 + bb.y1) / 2, 60 * u, H - 60 * u) : jzClamp((bb.x0 + bb.x1) / 2, 60 * u, W - 60 * u);
        var across = horiz ? db1_bh(bb) : db1_bw(bb), n = 4 + ((d.n | 0) % 3), pe = jzClamp(across / n, 10 * u, 22 * u), ps = pe * (1.6 + (d.r || 0) * 0.8);
        var Mp = function (s, t) { return side === 'l' ? [s, t] : side === 'r' ? [W - s, t] : side === 't' ? [t, s] : [t, H - s]; };
        // routes first; then each parent group (pads, pulses, traces \u2014 same stacking as before) is filled completely before the
        // next one is added to the contents (a new sibling group invalidates references to the earlier ones)
        var RT = [];
        for (i = 0; i < n; i++) {
            var c = i - (n - 1) / 2, t0 = tc + c * ps + db1_rs(d.seed, i, 1) * 3 * u, t1 = tc + c * pe, dd = Math.abs(t1 - t0);
            var sm = sEnd * (0.45 + 0.15 * db1_rs(d.seed, si, 2)), sa = Math.max(10 * u, sm - dd / 2);
            var TT = 1.3 + db1_r(d.seed, i, 5) * 0.8;
            RT.push({ pts: [Mp(-6 * u, t0), Mp(sa, t0), Mp(sa + dd, t1), Mp(sEnd - (i % 2 ? 14 * u : 0), t1)], dd: dd, TT: TT, D0: 0.7 + db1_r(d.seed, i, 6) * TT });
        }
        var gP = jzGrp(S, 'pads ' + (si + 1));
        for (i = 0; i < n; i++) {
            var gd = db1_sub(gP, 'pad ' + (i + 1)), gd1 = db1_sub(gd, 'dot');
            jzAddEllipse(gd1, 3.2 * u, 3.2 * u); jzAddFill(gd1, ac);
            var gd2 = db1_sub(gd, 'ring');
            jzAddEllipse(gd2, 8 * u, 8 * u); db1_st(gd2, sc.fg, Math.max(1, 1.2 * u));
            jzGX(gd).property('ADBE Vector Position').setValue(RT[i].pts[3]);
            db1_gx(ctx, gd, 'ADBE Vector Scale', ST, 'var e=oe((t-0.55)/0.3);[100*e,100*e]');
            if (RT[i].dd > 3 * u) {
                var gv = db1_sub(gP, 'via ' + (i + 1)); jzAddEllipse(gv, 5.2 * u, 5.2 * u); db1_st(gv, sc.sub, Math.max(1, u), 80);
                jzGX(gv).property('ADBE Vector Position').setValue(RT[i].pts[1]);
                db1_gx(ctx, gv, 'ADBE Vector Scale', ST, 'var e=oe((t-0.55)/0.3);[100*e,100*e]');
            }
        }
        var gX = jzGrp(S, 'pulses ' + (si + 1));
        for (i = 0; i < n; i++) {
            var gp = db1_sub(gX, 'pulse ' + (i + 1)); jzAddPath(gp, RT[i].pts, false);
            db1_trim(ctx, gp, ST, '100*ph', '100*Math.max(0,ph-0.1)', null, 'var x=(t-' + jzN(RT[i].D0) + ')/' + jzN(RT[i].TT) + ',ph=(t<0.7||x<0)?0:x-Math.floor(x);');
            db1_st(gp, ac, 2.4 * u, null, 2);
        }
        var gR = jzGrp(S, 'traces ' + (si + 1));
        for (i = 0; i < n; i++) {
            var gt = db1_sub(gR, 'trace ' + (i + 1)); jzAddPath(gt, RT[i].pts, false);
            db1_trim(ctx, gt, ST, '100*ioc((t-' + jzN(i * 0.06 + si * 0.1) + ')/0.6)');
        }
        db1_st(gR, sc.sub, lw, 85, 1, 1);
    }
    db1_op(ctx, S, ST);
});

// ================================================================ print & stationery

/* ---- swatches \u2014 \u8272\u898B\u672C: a printer's colour-control strip built from the scheme: solids over 50 % tints, with codes and a register mark */
db1_reg('swatches', false, function (ctx, bb, d) {
    var sc = ctx.sc, u = db1_u(ctx), ST = db1_settle(ctx), i;
    var all = [[sc.accent, 'ACC'], [sc.accent2, 'AC2'], [sc.fg, 'FG'], [sc.sub, 'SUB'], [sc.ink, 'INK'], [sc.dim, 'DIM']], seen = {}, cols = [];
    for (i = 0; i < all.length; i++) { if (!all[i][0]) continue; var hx = String(all[i][0]).toUpperCase(); if (seen[hx]) continue; seen[hx] = 1; cols.push(all[i]); }
    var sq = jzClamp(jzU(ctx) * 0.037, 24 * u, 44 * u), g = 2 * u, fs = db1_fs(ctx) * 0.7, n = Math.min(6, cols.length), w = n * (sq + g) + sq * 1.4, h = sq * 1.7 + fs * 3.4;
    var P2 = jzCopy(d); P2.low = d.low || d.corner;
    var sp = db1_spot(ctx, bb, w, h, P2, 24 * u), a = sp.ok ? 1 : 0.35, x0 = sp.x, y0 = sp.y + fs * 1.6, lw = Math.max(1, u);
    var T0 = db1_label(ctx, 'COLOR BAR  ' + db1_pad(n), x0, sp.y + fs * 0.6, { size: fs, track: 0.2, alpha: a }); db1_op(ctx, T0, ST, 'oe(t/0.3)');
    var S = jzShapeLayer(ctx, 'swatches', 0, 0);
    var rx = x0 + n * (sq + g) + sq * 0.7, ry = y0 + sq * 0.85, rr = sq * 0.42;
    var gm = jzGrp(S, 'register'), gm1 = db1_sub(gm, 'cross');
    jzAddPath(gm1, [[-rr * 1.5, 0], [rr * 1.5, 0]], false); jzAddPath(gm1, [[0, -rr * 1.5], [0, rr * 1.5]], false); db1_st(gm1, sc.fg, lw);
    db1_gx(ctx, gm1, 'ADBE Vector Scale', ST, 'var e=oe((t-0.3)/0.4);[100*e,100*e]');
    var gm2 = db1_sub(gm, 'circle');     // added after the cross is finished (it invalidates gm1)
    db1_arcTrim(ctx, gm2, rr, 0, 0, -90, ST, '360*oe((t-0.3)/0.4)'); db1_st(gm2, sc.fg, lw);
    jzGX(gm).property('ADBE Vector Position').setValue([rx, ry]);
    for (i = 0; i < n; i++) {
        var c = cols[i][0], x = x0 + i * (sq + g), gs = jzGrp(S, cols[i][1]);
        if (jzContrast(c, sc.bg) < 1.3) { var go = db1_sub(gs, 'outline'); jzAddRect(go, sq, sq, 0, sq / 2, sq / 2); db1_st(go, sc.sub, lw, 60); db1_gx(ctx, go, 'ADBE Vector Scale', ST, '[100,100*oe((t-' + jzN(0.05 + i * 0.06) + ')/0.35)]'); }
        var g1 = db1_sub(gs, 'solid'); jzAddRect(g1, sq, sq, 0, sq / 2, sq / 2); jzAddFill(g1, c);
        db1_gx(ctx, g1, 'ADBE Vector Scale', ST, '[100,100*oe((t-' + jzN(0.05 + i * 0.06) + ')/0.35)]');
        var g2 = db1_sub(gs, 'tint'); jzAddRect(g2, sq, sq * 0.7, 0, sq / 2, sq * 0.35); jzAddFill(g2, jzMixHex(sc.bg, c, 0.5));
        jzGX(g2).property('ADBE Vector Position').setValue([0, sq + g]);
        db1_gx(ctx, g2, 'ADBE Vector Scale', ST, '[100,100*oe((t-' + jzN(0.25 + i * 0.06) + ')/0.35)]');
        jzGX(gs).property('ADBE Vector Position').setValue([x, y0]);
        // code + hex in one two-line text (second line smaller, in fg)
        var key = cols[i][1], hexs = String(c).replace('#', '').toUpperCase().substr(0, 6);
        var T = jzText(ctx, key + '\r' + hexs, { font: jzMonoF(ctx), size: fs, color: sc.sub, x: x + sq / 2, y: y0 + sq * 1.7 + g + fs * 1.5, track: 0.05, leading: fs * 1.2, opacity: a });
        jzAnimator(T, 'JZ Hex', [['ADBE Text Scale 3D', [80, 80, 100]], ['ADBE Text Fill Color', jzHex(sc.fg)], ['ADBE Text Opacity', 80]], 'textIndex>' + key.length + '?100:0');
        db1_op(ctx, T, ST, 'oe((t-' + jzN(0.25 + i * 0.06) + ')/0.35)');
    }
    jzXf(S, 'ADBE Opacity').setValue(a * 100); db1_op(ctx, S, ST);
});

/* ---- ruledLines \u2014 \u7F6B\u7DDA\u30CE\u30FC\u30C8: faint notebook rules drawn on across the screen behind the lyric, a red margin line and a date header */
db1_reg('ruledLines', true, function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, u = db1_u(ctx), i;
    var s = jzClamp(jzU(ctx) * 0.078, 44 * u, 96 * u), dk = db1_dark(sc), lw = Math.max(1, 1.2 * u), al = dk ? 0.36 : 0.42, dotted = (d.v | 0) % 2 === 1;
    var y0 = s * 1.6 + (db1_r(d.seed, 1) - 0.5) * s * 0.4, rows = Math.floor((H - y0) / s);
    var S = jzShapeLayer(ctx, 'ruled lines', 0, 0);
    var mx = d.right ? W - W * 0.09 : W * 0.09, mc = db1_acc(sc), ma = dk ? 0.55 : 0.6;
    var gm = jzGrp(S, 'margin'); jzAddPath(gm, [[mx, 0], [mx, H]], false);
    if ((d.v | 0) % 3 === 2) { var gm2 = db1_sub(gm, 'margin 2'); jzAddPath(gm2, [[mx + (d.right ? -5 : 5) * u, 0], [mx + (d.right ? -5 : 5) * u, H]], false); db1_st(gm2, mc, lw, ma * 70); }
    db1_trim(ctx, gm, 0, '100*ioc((t-0.1)/0.7)'); db1_st(gm, mc, lw, ma * 100);
    for (i = 0; i <= rows && i < 40; i++) {
        var y = y0 + i * s, g = jzGrp(S, 'rule ' + (i + 1)); jzAddPath(g, [[0, y], [W, y]], false);
        db1_trim(ctx, g, 0, '100*ioc((t-' + jzN(i * 0.025) + ')/0.6)');
        var st = db1_st(g, sc.sub, dotted ? lw * 1.4 : lw, al * 100);
        if (dotted) db1_dash(st, 2 * u, 7 * u);
    }
    db1_op(ctx, S, 0);
    var fs = db1_fs(ctx) * 0.95, dd = 1 + db1_h(d.seed, 3) % 28, mm = 1 + db1_h(d.seed, 4) % 12;
    var T = db1_label(ctx, 'No.  ' + db1_pad((ctx.cut.line | 0) + 1) + '      Date   ' + db1_pad(mm) + ' . ' + db1_pad(dd), d.right ? W * 0.06 : W - W * 0.06, y0 - s * 0.35,
        { size: fs, align: d.right ? 'left' : 'right', track: 0.1, alpha: 0.6 });
    db1_op(ctx, T, 0, 'oe((t-0.4)/0.4)');
});

/* ---- registration \u2014 \u898B\u5F53\u5408\u308F\u305B: a registration target printed in three offset colours (screen / multiply) that slide into register */
db1_reg('registration', false, function (ctx, bb, d) {
    var sc = ctx.sc, u = db1_u(ctx), ST = db1_settle(ctx), k, i;
    var R = jzClamp(jzU(ctx) * 0.045, 28 * u, 56 * u), fs = db1_fs(ctx) * 0.72, sp = db1_spot(ctx, bb, R * 3.2, R * 3.2 + fs * 2, d, 22 * u), a = sp.ok ? 1 : 0.35;
    var cx = sp.cx, cy = sp.y + R * 1.6, cols = [db1_acc(sc), db1_acc2(sc), sc.fg], dirs = [[-1, -0.6], [0.9, -0.5], [0.1, 1]], lw = Math.max(1, 1.3 * u);
    var DX = 'var lk=oe((t-0.05)/0.9),jt=(t>1.2&&Math.floor(t/1.7)!==Math.floor((t-0.08)/1.7))?1:0,D=' + jzN(18 * u) + '*(1-lk)+jt*' + jzN(2.5 * u) + ';';
    function pie(a0) { var p = [[0, 0]], j; for (j = 0; j <= 8; j++) { var an = (a0 + j * 90 / 8) * DB1_D; p.push([Math.cos(an) * R * 0.32, Math.sin(an) * R * 0.32]); } return p; }
    for (k = 0; k < 3; k++) {
        var S = jzShapeLayer(ctx, 'registration ' + (k + 1), cx, cy), gp = jzGrp(S, 'pies');
        jzAddPath(gp, pie(-90), true); jzAddPath(gp, pie(90), true); jzAddFill(gp, cols[k], 90);
        var gl = jzGrp(S, 'lines');     // added after the pies are finished (it invalidates gp)
        jzAddEllipse(gl, R * 1.96, R * 1.96); jzAddEllipse(gl, R * 1.24, R * 1.24);
        jzAddPath(gl, [[-R * 1.45, 0], [R * 1.45, 0]], false); jzAddPath(gl, [[0, -R * 1.45], [0, R * 1.45]], false);
        db1_st(gl, cols[k], lw, 90);
        jzSetExpr(jzXf(S, 'ADBE Position'), db1_H(ctx, ST) + DX + '[value[0]+' + jzN(dirs[k][0]) + '*D,value[1]+' + jzN(dirs[k][1]) + '*D]');
        jzSetExpr(jzXf(S, 'ADBE Scale'), db1_H(ctx, ST) + 'var e=ob(cl(t/0.35),1.4);[100*e,100*e]');
        S.blendingMode = db1_dark(sc) ? BlendingMode.SCREEN : BlendingMode.MULTIPLY;
        jzXf(S, 'ADBE Opacity').setValue(a * 100); db1_op(ctx, S, ST);
    }
    var ly = cy + R * 1.6 + fs * 0.5, OK = DX + 'var ok=lk>0.985&&!jt;';
    var T1 = db1_label(ctx, 'REG  OK', cx - R * 1.5, ly, { size: fs, track: 0.2, alpha: a });
    db1_srcText(ctx, T1, ST, OK + 'ok?"REG  OK":"REG"'); db1_txtCol(ctx, T1, db1_acc(sc), ST, OK + 'ok?100:0'); db1_op(ctx, T1, ST, 'oe((t-0.1)/0.3)');
    var T2 = db1_label(ctx, '\u00B10.00mm', cx + R * 1.5, ly, { size: fs, align: 'right', color: sc.fg, track: 0.06, alpha: a });
    db1_srcText(ctx, T2, ST, OK + 'var dx=D/' + jzN(u) + '*0.1;ok?"\u00B10.00mm":"\u0394X "+(-dx).toFixed(2)+"  \u0394Y "+(-0.6*dx).toFixed(2)');
    db1_op(ctx, T2, ST, 'oe((t-0.1)/0.3)');
});

/* ---- punchHoles \u2014 \u30D1\u30F3\u30C1\u7A74: binder punch holes along a free screen edge, punched in one by one, reinforcement rings and a dimension */
db1_reg('punchHoles', false, function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, u = db1_u(ctx), m = db1_mg(ctx), i, k;
    var r = jzClamp(jzU(ctx) * 0.016, 10 * u, 19 * u), n = (d.v | 0) % 3 === 2 ? 3 : 2;
    var edges = (d.right ? ['r', 'l'] : ['l', 'r']).concat(d.low ? ['b', 't'] : ['t', 'b']), pick = null;
    for (i = 0; i < edges.length && !pick; i++) {
        var ed = edges[i], vE = ed === 'l' || ed === 'r', LL = vE ? H : W, spc = Math.min(LL * 0.3, 260 * u) * (n === 3 ? 0.8 : 1);
        var c = ed === 'l' ? m * 0.9 + r : ed === 'r' ? W - m * 0.9 - r : ed === 't' ? m * 0.9 + r : H - m * 0.9 - r;
        var span = spc * (n - 1) / 2 + r * 3, mid = vE ? H / 2 : W / 2;
        var box = vE ? [c - r * 5, mid - span, c + r * 5, mid + span] : [mid - span, c - r * 5, mid + span, c + r * 5];
        if (!db1_hit(box[0], box[1], box[2], box[3], bb, 6 * u)) pick = { ed: ed, vE: vE, spc: spc, c: c, mid: mid };
    }
    if (!pick) return;
    var vert = pick.vE, inward = pick.ed === 'r' || pick.ed === 'b' ? -1 : 1, lw = Math.max(1, 1.2 * u), T0 = jzN(Math.min(ctx.cut.inDur || 0, 0.6) * 0.9);
    function Pt(al, off) { return vert ? [pick.c + inward * off, al] : [al, pick.c + inward * off]; }
    var pos = []; for (i = 0; i < n; i++) pos.push(pick.mid + (i - (n - 1) / 2) * pick.spc);
    var LT = 'var lt=t-' + T0 + ';';
    var S = jzShapeLayer(ctx, 'punch holes', 0, 0);
    // dimension line + label
    var fs = db1_fs(ctx) * 0.72, a0 = pos[0], a1 = pos[n - 1], am = (a0 + a1) / 2, gp = fs * 2.4, off = r * 2.3, DE = 'oe((lt-0.4)/0.4)';
    var gD = jzGrp(S, 'dimension'), gD1 = db1_sub(gD, 'arms');
    jzAddPath(gD1, [Pt(am - gp, off), Pt(a0, off)], false); jzAddPath(gD1, [Pt(am + gp, off), Pt(a1, off)], false);
    db1_trim(ctx, gD1, 0, '100*' + DE, null, null, LT);
    var gD2 = db1_sub(gD, 'ends'); jzAddPath(gD2, [Pt(a0, off - 3 * u), Pt(a0, off + 3 * u)], false); jzAddPath(gD2, [Pt(a1, off - 3 * u), Pt(a1, off + 3 * u)], false);
    db1_st(gD, sc.sub, lw, 85); db1_gop(ctx, gD, 0, 'lt>0.4?1:0', LT);
    var lp = Pt(am, off), TL = db1_label(ctx, (n === 2 ? 80 : 108) + 'mm', lp[0], lp[1], { size: fs, align: 'center', rot: vert ? -90 : 0, track: 0.1 });
    db1_op(ctx, TL, 0, '(function(){' + LT + 'return ' + DE + ';})()');
    // holes, punched one by one
    for (i = n - 1; i >= 0; i--) {
        var P = Pt(pos[i], 0), Q = LT + 'var q=cl((lt-' + jzN(0.08 + i * 0.14) + ')/0.22);', gh = jzGrp(S, 'hole ' + (i + 1));
        jzGX(gh).property('ADBE Vector Position').setValue(P);
        var gb = db1_sub(gh, 'burst');
        for (k = 0; k < 6; k++) { var an = k * 60 * DB1_D + 0.3; jzAddPath(gb, [[Math.cos(an) * r * 1.3, Math.sin(an) * r * 1.3], [Math.cos(an) * (r * 1.3 + 5 * u), Math.sin(an) * (r * 1.3 + 5 * u)]], false); }
        db1_st(gb, db1_acc(sc), lw);
        db1_gx(ctx, gb, 'ADBE Vector Position', 0, Q + 'var dd=q*' + jzN(8 * u) + ';[0,0]');
        db1_gx(ctx, gb, 'ADBE Vector Scale', 0, Q + 'var s=1+q*' + jzN(8 * u / (r * 1.3)) + ';[100*s,100*s]');
        db1_gop(ctx, gb, 0, '(q>0&&q<1)?1-q:0', Q);
        var gc = db1_sub(gh, 'hole');
        var gs = db1_sub(gc, 'shade'); jzAddPath(gs, db1_arc(0, 0, r * 0.76, 200, 290, 12), false); db1_st(gs, sc.sub, 2 * u, 60);
        var go = db1_sub(gc, 'edge'); jzAddEllipse(go, r * 2, r * 2); db1_st(go, sc.fg, lw, 90);
        var gf = db1_sub(gc, 'fill'); jzAddEllipse(gf, r * 2, r * 2); jzAddFill(gf, sc.dim, 95);
        if ((d.v | 0) % 2 === 0) { var gr = db1_sub(gc, 'ring'); jzAddEllipse(gr, r * 3.7, r * 3.7); db1_st(gr, sc.sub, r * 0.75, 18); }
        db1_gx(ctx, gc, 'ADBE Vector Scale', 0, Q + 'var s=1+0.35*(1-oc(q));[100*s,100*s]');
        db1_gop(ctx, gc, 0, 'q', Q);
    }
    // dashed guide through the holes
    var g0 = Pt(0, r * 3.4), g1 = Pt(vert ? H : W, r * 3.4), gg = jzGrp(S, 'guide'); jzAddPath(gg, [g0, g1], false);
    db1_trim(ctx, gg, 0, '50+50*ioc(lt/0.7)', '50-50*ioc(lt/0.7)', null, LT);
    db1_dash(db1_st(gg, sc.sub, lw, 50), 6 * u, 6 * u);
    db1_op(ctx, S, 0, 't>' + T0 + '?1:0');
});

/* virtual "sheet" around the lyric: its corner lines (two offset sheets) \u2014 used by staple / paper clip; grows from the corner outward */
function db1_sheetCorner(ctx, S, X, Y, sx, sy, L, st, dur) {
    var u = db1_u(ctx), lw = Math.max(1, u), o = 5 * u, E = 'ioc(t/' + jzN(dur) + ')';
    var g2 = jzGrp(S, 'sheet 2'); jzAddPath(g2, [[X + sx * L * 0.8 + sx * o, Y + sy * o], [X + sx * o, Y + sy * o], [X + sx * o, Y + sy * L * 0.8 + sy * o]], false);
    db1_trim(ctx, g2, st, '50+50*' + E, '50-50*' + E); db1_st(g2, ctx.sc.sub, lw, 35);
    var g1 = jzGrp(S, 'sheet'); jzAddPath(g1, [[X + sx * L, Y], [X, Y], [X, Y + sy * L]], false);
    db1_trim(ctx, g1, st, '50+50*' + E, '50-50*' + E); db1_st(g1, ctx.sc.sub, lw, 80);
}

/* ---- staple \u2014 \u30DB\u30C1\u30AD\u30B9: the lyric treated as a stapled sheet: paper-corner lines, a dog-ear, and a staple that snaps in diagonally */
db1_reg('staple', false, function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, u = db1_u(ctx), m = db1_mg(ctx), ST = db1_settle(ctx), i;
    var Ls = jzClamp(jzU(ctx) * 0.058, 38 * u, 70 * u), padP = Ls * 0.7 + 14 * u + db1_bh(bb) * 0.04, sx = d.right ? 1 : -1, sy = d.low ? 1 : -1;
    var X = jzClamp(sx < 0 ? bb.x0 - padP : bb.x1 + padP, m * 0.5, W - m * 0.5), Y = jzClamp(sy < 0 ? bb.y0 - padP : bb.y1 + padP, m * 0.5, H - m * 0.5);
    var L = Math.min(Ls * 3.4, Math.max(db1_bw(bb), db1_bh(bb)) * 0.45);
    var F = jzShapeLayer(ctx, 'staple sheet', 0, 0);
    var dd = Ls * 0.3, cx = X - sx * dd, cy = Y - sy * dd, ang = sx * sy > 0 ? -45 : 45, Q = 'var q=cl((t-0.3)/0.18);';
    var gk = jzGrp(F, 'impact');
    for (i = 0; i < 4; i++) { var an = (ang + 90 + (i < 2 ? 0 : 180) + (i % 2 ? 28 : -28)) * DB1_D; jzAddPath(gk, [[Math.cos(an) * 9 * u, Math.sin(an) * 9 * u], [Math.cos(an) * 16 * u, Math.sin(an) * 16 * u]], false); }
    db1_st(gk, db1_acc(sc), Math.max(1, 1.4 * u), null, 2);
    jzGX(gk).property('ADBE Vector Position').setValue([cx, cy]);
    db1_gx(ctx, gk, 'ADBE Vector Scale', ST, Q + 'var s=1+q*' + jzN(12 / 9) + ';[100*s,100*s]');
    db1_gop(ctx, gk, ST, '(q>0&&q<1)?1-q:0', Q);
    if ((d.v | 0) % 2 === 1) {
        var X2 = sx < 0 ? bb.x1 + padP * 0.6 : bb.x0 - padP * 0.6, Y2 = sy < 0 ? bb.y1 + padP * 0.6 : bb.y0 - padP * 0.6, f = Ls * 0.7;
        if (X2 > m * 0.5 && X2 < W - m * 0.5 && Y2 > m * 0.5 && Y2 < H - m * 0.5) {
            var gd = jzGrp(F, 'dog-ear'), gd1 = db1_sub(gd, 'fold');
            jzAddPath(gd1, [[X2 + sx * f, Y2], [X2 + sx * f, Y2 + sy * f], [X2, Y2 + sy * f]], false); db1_st(gd1, sc.sub, Math.max(1, u), 55);
            var gd2 = db1_sub(gd, 'edge');     // added after the fold is finished (it invalidates gd1)
            jzAddPath(gd2, [[X2 + sx * L * 0.7, Y2], [X2 + sx * f, Y2], [X2, Y2 + sy * f], [X2, Y2 + sy * L * 0.7]], false); db1_st(gd2, sc.sub, Math.max(1, u), 80);
            db1_trim(ctx, gd2, ST, '50+50*ioc(t/0.5)', '50-50*ioc(t/0.5)');
            db1_gop(ctx, gd, ST, 'oe((t-0.3)/0.4)');
        }
    }
    db1_sheetCorner(ctx, F, X, Y, -sx, -sy, L, ST, 0.5);
    db1_op(ctx, F, ST);
    // the staple
    var S = jzShapeLayer(ctx, 'staple', cx, cy), hl = Ls / 2, tt = 2.6 * u;
    var gl = jzGrp(S, 'legs'); jzAddRect(gl, 3 * u, tt * 3.2, 0, -hl, 0); jzAddRect(gl, 3 * u, tt * 3.2, 0, hl, 0); jzAddFill(gl, sc.fg, 80);
    var gh = jzGrp(S, 'highlight'); jzAddPath(gh, [[-hl + tt * 1.5, -tt * 0.3], [hl - tt * 1.5, -tt * 0.3]], false); db1_st(gh, sc.bg, Math.max(1, 0.8 * u), 45);
    var gb = jzGrp(S, 'body'); jzAddRect(gb, Ls, tt * 2, tt); jzAddFill(gb, sc.fg, 95);
    var gs = jzGrp(S, 'shadow'); jzAddRect(gs, Ls, tt * 2, tt, 2 * u, 2.5 * u); jzAddFill(gs, sc.sub, 30);
    jzSetExpr(jzXf(S, 'ADBE Rotate Z'), db1_H(ctx, ST) + Q + jzN(ang) + '+(1-q)*14');
    jzSetExpr(jzXf(S, 'ADBE Scale'), db1_H(ctx, ST) + Q + 'var s=1+0.6*(1-oc(q));[100*s,100*s]');
    db1_op(ctx, S, ST, 'cl(cl((t-0.3)/0.18)*3)');
});

/* ---- paperClip \u2014 \u30AF\u30EA\u30C3\u30D7: a gem paper clip drawn on in one wire stroke, clipping the top edge of the lyric's "sheet" */
var DB1_CLIP = (function () {
    var p = [], i;
    function seg(a, b) { for (var j = 0; j <= 6; j++) p.push([a[0] + (b[0] - a[0]) * j / 6, a[1] + (b[1] - a[1]) * j / 6]); }
    function arc(cx, cy, r, a0, a1) { for (var j = 0; j <= 14; j++) { var an = (a0 + (a1 - a0) * j / 14) * DB1_D; p.push([cx + Math.cos(an) * r, cy + Math.sin(an) * r]); } }
    seg([-0.2, 0.95], [-0.2, 2.45]); arc(0.05, 2.45, 0.25, 180, 0);
    seg([0.3, 2.45], [0.3, 0.42]); arc(-0.02, 0.42, 0.32, 0, -180);
    seg([-0.34, 0.42], [-0.34, 2.62]); arc(0.06, 2.62, 0.4, 180, 0);
    seg([0.46, 2.62], [0.46, 0.85]);
    for (i = 0; i < p.length; i++) p[i] = [p[i][0], p[i][1] - 1.5];
    return p;
})();
db1_reg('paperClip', false, function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, u = db1_u(ctx), m = db1_mg(ctx), ST = db1_settle(ctx), i;
    var S0 = jzClamp(jzU(ctx) * 0.035, 22 * u, 40 * u), hc = S0 * 3.3, low = !!d.low && H - bb.y1 > hc * 1.2 + m;
    var padP = hc * 0.42 + 10 * u + db1_bh(bb) * 0.03, Y = low ? bb.y1 + padP : Math.max(m * 0.5 + hc * 0.55, bb.y0 - padP), sy = low ? 1 : -1, sx = d.right ? 1 : -1;
    var Xc = jzClamp(sx < 0 ? bb.x0 - 26 * u - db1_bw(bb) * 0.02 : bb.x1 + 26 * u + db1_bw(bb) * 0.02, m * 0.5, W - m * 0.5), L = Math.min(S0 * 7, Math.max(db1_bw(bb) * 0.45, S0 * 4));
    var F = jzShapeLayer(ctx, 'clip sheet', 0, 0); db1_sheetCorner(ctx, F, Xc, Y, -sx, -sy, L, ST, 0.45); db1_op(ctx, F, ST);
    var cx = jzClamp(Xc - sx * L * 0.42, m, W - m), tilt = sx * 7 + db1_rs(d.seed, 1) * 5, cy = Y + sy * hc * 0.12, pts = [];
    for (i = 0; i < DB1_CLIP.length; i++) pts.push([DB1_CLIP[i][0] * S0, (low ? -DB1_CLIP[i][1] : DB1_CLIP[i][1]) * S0]);
    var S = jzShapeLayer(ctx, 'paper clip', cx, cy);
    jzXf(S, 'ADBE Rotate Z').setValue(tilt);
    jzSetExpr(jzXf(S, 'ADBE Position'), db1_H(ctx, ST) + '[value[0],value[1]-(' + sy + ')*(1-oc((t-0.1)/0.45))*' + jzN(24 * u) + ']');
    var gh = jzGrp(S, 'highlight'); jzAddPath(gh, db1_off(pts, -0.8 * u, -0.8 * u), false);
    db1_trim(ctx, gh, ST, '100*ioc((t-0.1)/0.6)'); db1_st(gh, sc.bg, Math.max(1, 0.7 * u), 35, 2, 2);
    var gw = jzGrp(S, 'wire'); jzAddPath(gw, pts, false);
    db1_trim(ctx, gw, ST, '100*ioc((t-0.1)/0.6)'); db1_st(gw, sc.fg, Math.max(1.4, 2.1 * u), 95, 2, 2);
    db1_op(ctx, S, ST);
});

/* ---- indexTabs \u2014 \u898B\u51FA\u3057\u30BF\u30D6: binder index tabs sliding out of a free screen edge; the current line's tab sticks out in the accent */
db1_reg('indexTabs', false, function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, u = db1_u(ctx), i;
    var n = 5, th = jzClamp(jzU(ctx) * 0.066, 42 * u, 76 * u), tw = th * 0.8, g = 6 * u, fs = db1_fs(ctx) * 1.05, span = n * th + (n - 1) * g;
    var edges = (d.right ? ['r', 'l'] : ['l', 'r']).concat(['t', 'b']), pick = null;
    for (i = 0; i < edges.length && !pick; i++) {
        var ed = edges[i], vE = ed === 'l' || ed === 'r', mid = vE ? H * (d.low ? 0.58 : 0.42) : W / 2, depth = tw + 22 * u;
        var box = ed === 'l' ? [0, mid - span / 2, depth, mid + span / 2] : ed === 'r' ? [W - depth, mid - span / 2, W, mid + span / 2] : ed === 't' ? [mid - span / 2, 0, mid + span / 2, depth] : [mid - span / 2, H - depth, mid + span / 2, H];
        if ((vE ? H : W) > span + 40 * u && !db1_hit(box[0], box[1], box[2], box[3], bb, 26 * u)) pick = { ed: ed, vE: vE, mid: mid };
    }
    if (!pick) return;
    var E = pick.ed, cur = (((ctx.cut.line | 0) % n) + n) % n, T0 = jzN(Math.min(ctx.cut.inDur || 0, 0.6) * 0.9), ac = db1_acc(sc);
    var labs = [['01', '02', '03', '04', '05'], ['\u3042', '\u304B', '\u3055', '\u305F', '\u306A'], ['A', 'B', 'C', 'D', 'E']][(d.v | 0) % 3];
    var S = jzShapeLayer(ctx, 'index tabs', 0, 0), sg = E === 'l' || E === 't' ? 1 : -1;
    for (i = n - 1; i >= 0; i--) {
        var isC = i === cur, Dm = tw + (isC ? 16 * u : 0), along = pick.mid - span / 2 + i * (th + g), ext = Dm + 12 * u;
        var Q = 'var lt=t-' + T0 + ',q=cl((lt-' + jzN(i * 0.06) + ')/0.35),D=(q<=0?0:ob(q,1.3))*' + jzN(Dm) + ';';
        // the tab is a fixed rounded rect that slides out: its inner edge sits at depth D from the screen edge
        var gt = jzGrp(S, 'tab ' + (i + 1)), col = isC ? ac : sc.dim;
        if (pick.vE) jzAddRect(gt, ext, th, 6 * u, 0, 0); else jzAddRect(gt, th, ext, 6 * u, 0, 0);
        if (!isC) db1_st(gt, sc.sub, Math.max(1, u), 70);
        jzAddFill(gt, col, isC ? 95 : 90);
        var base = E === 'l' ? 'D-' + jzN(ext / 2) : E === 'r' ? jzN(W) + '-D+' + jzN(ext / 2) : E === 't' ? 'D-' + jzN(ext / 2) : jzN(H) + '-D+' + jzN(ext / 2);
        db1_gx(ctx, gt, 'ADBE Vector Position', 0, Q + (pick.vE ? '[' + base + ',' + jzN(along + th / 2) + ']' : '[' + jzN(along + th / 2) + ',' + base + ']'));
        var tc = isC ? (jzLum(col) > 0.55 ? '#000000' : sc.bg) : sc.fg, lb = labs[i];
        var T = jzText(ctx, lb, { font: lb.length > 1 ? jzMonoF(ctx) : jzBodyF(ctx), size: fs * (lb.length > 1 ? 1 : 1.15), color: tc, x: pick.vE ? 0 : along + th / 2, y: pick.vE ? along + th / 2 : 0, track: 0.04 });
        var tb = E === 'l' ? 'D-' + jzN(tw * 0.5) : E === 'r' ? jzN(W) + '-D+' + jzN(tw * 0.5) : E === 't' ? 'D-' + jzN(tw * 0.5) : jzN(H) + '-D+' + jzN(tw * 0.5);
        jzSetExpr(jzXf(T, 'ADBE Position'), db1_H(ctx, 0) + Q + (pick.vE ? '[' + tb + ',value[1]]' : '[value[0],' + tb + ']'));
        db1_op(ctx, T, 0, '(function(){' + Q + 'return cl((q<=0?0:ob(q,1.3)-0.5)*2);})()');
    }
    db1_op(ctx, S, 0, 't>' + T0 + '?1:0');
});

// ================================================================ decor B part 2 (AE port of 28 decor entries in src/11p_decorB.js)
// nature / atmosphere (vines \u2026 fireflies), graphic shapes (memphis \u2026 tally), UI widgets (cursorClick \u2026 musicNotes).
// decor.build(ctx, bb, d): layers live in the CONTENT comp (time 0 = cut start). Every entry here is drawn by the browser on the
// main pass only, so all its layers are kept out of the tinted ghosts (jzNoGhost, done once in db2_reg).
// Entries the browser wraps in withSettle() start after the lyric's entrance has settled: expressions use t = time - ST.
// Particles are one shape layer with a group per particle; per-cycle random values are pre-computed into small arrays.

// ---------------------------------------------------------------- shared helpers (prefix db2_)
// the browser's deterministic hash J.h / J.r / J.rs / J.rr and value noise J.noise1 (so seeded placements match the web frame)
function db2_h(a, b, c, d, e) {
    var h = 0x9e3779b9 ^ (a | 0);
    h = jzImul(h ^ (h >>> 16), 0x85ebca6b);
    h = (h + jzImul((b | 0) + 0x632be5ab, 0xc2b2ae35)) | 0;
    h = jzImul(h ^ (h >>> 13), 0xc2b2ae35);
    h = (h + jzImul((c | 0) + 0x5bd1e995, 0x27d4eb2f)) | 0;
    h = jzImul(h ^ (h >>> 15), 0x165667b1);
    h = (h + jzImul((d | 0) + 0x1b873593, 0x85ebca6b)) | 0;
    h = jzImul(h ^ (h >>> 16), 0x27d4eb2f);
    h = (h + jzImul((e | 0) + 0x68e31da4, 0x9e3779b1)) | 0;
    h ^= h >>> 15; h = jzImul(h, 0x2c1b3c6d); h ^= h >>> 12; h = jzImul(h, 0x297a2d39); h ^= h >>> 15;
    return h >>> 0;
}
function db2_r(a, b, c, d, e) { return db2_h(a, b, c, d, e) / 4294967296; }
function db2_rs(a, b, c, d, e) { return db2_r(a, b, c, d, e) * 2 - 1; }
function db2_rr(lo, hi, a, b, c, d, e) { return lo + (hi - lo) * db2_r(a, b, c, d, e); }
function db2_noise1(x, seed) { var i = Math.floor(x), f = x - i, u = f * f * (3 - 2 * f); return jzLerp(db2_rs(seed, i), db2_rs(seed, i + 1), u); }
// lattice values of J.noise1(x, seed) for x in [x0, x1] as an expression fragment "I,[a,b,\u2026]" (use with nz(x, I, A))
function db2_ntab(seed, x0, x1) { var I = Math.floor(x0) - 1, n = Math.floor(x1) - I + 3, o = [], k; for (k = 0; k < n; k++) o.push(jzN(db2_rs(seed, I + k))); return I + ',[' + o.join(',') + ']'; }
var DB2_D = Math.PI / 180;
function db2_u(ctx) { return jzU(ctx) / 1080; }                       // browser U(env) at comp scale
function db2_mg(ctx) { return Math.round(jzU(ctx) * 0.05); }          // safe margin
function db2_fs(ctx) { return Math.max(12, 16 * db2_u(ctx)); }       // small label size
function db2_dark(sc) { return jzLum(sc.bg) < 0.5; }
function db2_vis(sc, c, mn) { return (c && jzContrast(c, sc.bg) >= (mn || 1.5)) ? c : sc.fg; }
function db2_acc(sc) { return db2_vis(sc, sc.accent); }
function db2_acc2(sc) { return db2_vis(sc, sc.accent2 || sc.accent); }
function db2_dispF(ctx) { var f = ctx.st && ctx.st.fonts && ctx.st.fonts.display; return (f && f[0]) || 'gothic_black'; }
function db2_settle(ctx) { return jzClamp((ctx.cut.inDur || 0) * 0.6, 0, 0.35); }
function db2_pad(n, k) { return jzPad(Math.max(0, Math.floor(n)), k || 2); }
function db2_arr(a) { var o = [], i; for (i = 0; i < a.length; i++) o.push(jzN(a[i])); return '[' + o.join(',') + ']'; }
function db2_pt(p) { return '[' + jzN(p[0]) + ',' + jzN(p[1]) + ']'; }
// colour as an expression literal [r,g,b,1]
function db2_cx(hex) { var c = jzHex(hex); return '[' + jzN(c[0]) + ',' + jzN(c[1]) + ',' + jzN(c[2]) + ',1]'; }
// expression header: jzTH (DUR IN OS OD SD, easings, PO, K) + ioc / wr / nz (table noise) / co (clearOf the lyric box) + t, V
var DB2_FNS = 'function ioc(x){x=cl(x);return x<0.5?4*x*x*x:1-Math.pow(-2*x+2,3)/2;}function wr(v,lo,sp){return lo+((v-lo)%sp+sp)%sp;}' +
    'function nz(x,I,A){var k=Math.floor(x),f=x-k,u=f*f*(3-2*f),i0=Math.max(0,Math.min(A.length-1,k-I)),i1=Math.max(0,Math.min(A.length-1,k+1-I));return A[i0]+(A[i1]-A[i0])*u;}' +
    'function co(x,y,p,s){var dx=Math.max(B0-p-x,0,x-B2-p),dy=Math.max(B1-p-y,0,y-B3-p);return cl(Math.sqrt(dx*dx+dy*dy)/s);}\n';
var DB2_BB = null;   // lyric box of the entry being built (for co())
function db2_H(ctx, st) {
    var b = DB2_BB || { x0: 0, y0: 0, x1: 0, y1: 0 };
    return jzTH(ctx) + DB2_FNS + 'var B0=' + jzN(b.x0) + ',B1=' + jzN(b.y0) + ',B2=' + jzN(b.x1) + ',B3=' + jzN(b.y1) + ',ST=' + jzN(st || 0) + ',t=time-ST,V=t<0?0:1;\n';
}
// layer opacity = value \u00D7 started \u00D7 exit fade \u00D7 extra factor
function db2_op(ctx, L, st, ex, pre) { jzSetExpr(jzXf(L, 'ADBE Opacity'), db2_H(ctx, st) + (pre || '') + 'value*V*K' + (ex ? '*(' + ex + ')' : '')); }
function db2_lx(ctx, L, mn, st, ex) { jzSetExpr(jzXf(L, mn), db2_H(ctx, st) + ex); }
function db2_sub(g, name) { var s = jzVecs(g).addProperty('ADBE Vector Group'); if (name) s.name = name; return s; }
// AE rule: adding / removing / moving a property invalidates the script's references to its siblings (and below) ->
// re-fetch named items after adding the next sibling: db2_kid = inside a group's contents, db2_top = a layer's top-level group
function db2_kid(g, name) { return jzVecs(g).property(name); }
function db2_top(S, name) { return S.property('ADBE Root Vectors Group').property(name); }
function db2_gx(ctx, g, mn, st, ex) { jzSetExpr(jzGX(g).property(mn), db2_H(ctx, st) + ex); }
function db2_gop(ctx, g, st, ex, pre) { db2_gx(ctx, g, 'ADBE Vector Group Opacity', st, (pre || '') + 'value*(' + ex + ')'); }
function db2_gpos(g, x, y) { var T = jzGX(g); T.property('ADBE Vector Anchor').setValue([x, y]); T.property('ADBE Vector Position').setValue([x, y]); }
function db2_st(g, col, w, op, cap, join) {
    var s = jzAddStroke(g, col, w, op);
    try { if (cap) s.property('ADBE Vector Stroke Line Cap').setValue(cap); if (join) s.property('ADBE Vector Stroke Line Join').setValue(join); } catch (e) {}
    return s;
}
// (each dash property is added, then re-fetched by matchName: adding the next one invalidates the previous reference)
function db2_dash(s, on, off) {
    var D = s.property('ADBE Vector Stroke Dashes'), p3 = null;
    try { D.addProperty('ADBE Vector Stroke Dash 1'); } catch (e) {}
    try { D.property('ADBE Vector Stroke Dash 1').setValue(on); } catch (e1) {}
    try { D.addProperty('ADBE Vector Stroke Gap 1'); } catch (e2) {}
    try { D.property('ADBE Vector Stroke Gap 1').setValue(off); } catch (e3) {}
    try { D.addProperty('ADBE Vector Stroke Offset'); } catch (e4) {}
    try { p3 = D.property('ADBE Vector Stroke Offset'); } catch (e5) { p3 = null; }
    return p3;
}
// trim paths with expressions (end / start / offset), all in one header
function db2_trim(ctx, g, st, endEx, startEx, offEx, pre) {
    var tr = jzVecs(g).addProperty('ADBE Vector Filter - Trim'), H = db2_H(ctx, st) + (pre || '');
    if (endEx != null) jzSetExpr(tr.property('ADBE Vector Trim End'), H + 'Math.max(0,Math.min(100,' + endEx + '))');
    if (startEx != null) jzSetExpr(tr.property('ADBE Vector Trim Start'), H + 'Math.max(0,Math.min(100,' + startEx + '))');
    if (offEx != null) jzSetExpr(tr.property('ADBE Vector Trim Offset'), H + offEx);
    return tr;
}
function db2_trimV(g, s, e, off) { var tr = jzVecs(g).addProperty('ADBE Vector Filter - Trim'); tr.property('ADBE Vector Trim Start').setValue(s); tr.property('ADBE Vector Trim End').setValue(e); if (off) tr.property('ADBE Vector Trim Offset').setValue(off); return tr; }
function db2_rep(g, n, px, py, rot, off) {
    var rp = jzVecs(g).addProperty('ADBE Vector Filter - Repeater');
    rp.property('ADBE Vector Repeater Copies').setValue(n);
    if (off) rp.property('ADBE Vector Repeater Offset').setValue(off);
    var rt = rp.property('ADBE Vector Repeater Transform');
    rt.property('ADBE Vector Repeater Position').setValue([px || 0, py || 0]);
    if (rot) rt.property('ADBE Vector Repeater Rotation').setValue(rot);
    return rp;
}
// point lists
function db2_arc(cx, cy, r, a0, a1, n) { var o = [], i; n = n || 48; for (i = 0; i <= n; i++) { var a = (a0 + (a1 - a0) * i / n) * DB2_D; o.push([cx + Math.cos(a) * r, cy + Math.sin(a) * r]); } return o; }
function db2_ell(cx, cy, rx, ry, rot, a0, a1, n) { var o = [], c = Math.cos(rot), s = Math.sin(rot), i; for (i = 0; i <= n; i++) { var a = (a0 + (a1 - a0) * i / n) * DB2_D, x = Math.cos(a) * rx, y = Math.sin(a) * ry; o.push([cx + x * c - y * s, cy + x * s + y * c]); } return o; }
function db2_bez(p0, p1, p2, p3, n) { var o = [], i; for (i = 0; i <= n; i++) { var t = i / n, m = 1 - t; o.push([m * m * m * p0[0] + 3 * m * m * t * p1[0] + 3 * m * t * t * p2[0] + t * t * t * p3[0], m * m * m * p0[1] + 3 * m * m * t * p1[1] + 3 * m * t * t * p2[1] + t * t * t * p3[1]]); } return o; }
function db2_off(pts, dx, dy) { var o = [], i; for (i = 0; i < pts.length; i++) o.push([pts[i][0] + dx, pts[i][1] + dy]); return o; }
function db2_scl(pts, s) { var o = [], i; for (i = 0; i < pts.length; i++) o.push([pts[i][0] * s, pts[i][1] * s]); return o; }
// the browser's part(pts, e0, e1) / along(pts, t)
function db2_cum(pts) { var d = [0], i; for (i = 1; i < pts.length; i++) { var dx = pts[i][0] - pts[i - 1][0], dy = pts[i][1] - pts[i - 1][1]; d.push(d[i - 1] + Math.sqrt(dx * dx + dy * dy)); } return d; }
function db2_part(pts, e0, e1) {
    e0 = jzClamp(e0, 0, 1); e1 = jzClamp(e1, 0, 1);
    if (e1 <= e0 || pts.length < 2) return [];
    var d = db2_cum(pts), L = d[d.length - 1], i; if (L <= 0) return [];
    var A = e0 * L, B = e1 * L;
    function at(s) { var j = 1; while (j < d.length - 1 && d[j] < s) j++; var k = jzClamp((s - d[j - 1]) / Math.max(1e-6, d[j] - d[j - 1]), 0, 1); return [pts[j - 1][0] + (pts[j][0] - pts[j - 1][0]) * k, pts[j - 1][1] + (pts[j][1] - pts[j - 1][1]) * k]; }
    var out = [at(A)];
    for (i = 1; i < pts.length - 1; i++) if (d[i] > A && d[i] < B) out.push(pts[i]);
    out.push(at(B));
    return out;
}
function db2_along(pts, t) {
    var p = db2_part(pts, 0, jzClamp(t, 0.0005, 1)), a = p[p.length - 1], b = p.length > 1 ? p[p.length - 2] : pts[0];
    return { x: a[0], y: a[1], ang: Math.atan2(a[1] - b[1], a[0] - b[0]) };
}
function db2_rectShape(x0, y0, x1, y1) { var sh = new Shape(); sh.vertices = [[x1, y0], [x1, y1], [x0, y1], [x0, y0]]; sh.closed = true; return sh; }
function db2_mask(L, sh, mode, feather) {
    var m = L.property('ADBE Mask Parade').addProperty('ADBE Mask Atom');
    m.property('ADBE Mask Shape').setValue(sh);
    if (mode) m.maskMode = mode;
    if (feather) m.property('ADBE Mask Feather').setValue([feather, feather]);
    return m;
}
// small secondary text (mono, sub colour, left aligned, like the browser's label())
function db2_label(ctx, text, x, y, o) {
    o = o || {};
    return jzText(ctx, String(text), { font: o.font || jzMonoF(ctx), size: o.size || db2_fs(ctx), color: o.color || ctx.sc.sub, x: x, y: y,
        align: o.align || 'left', track: o.track == null ? 0.08 : o.track, rot: o.rot, opacity: o.alpha == null ? 1 : o.alpha, name: o.name, maxW: o.maxW, maxSize: o.maxSize });
}
function db2_textW(L) { var r = jzRect(L); return r.width; }
// a text colour animator switched on by an amount expression (0..100)
function db2_txtCol(ctx, L, hex, st, amtEx) { return jzAnimator(L, 'JZ Colour', [['ADBE Text Fill Color', jzHex(hex)]], db2_H(ctx, st) + amtEx); }
// source text driven by an expression
function db2_srcText(ctx, L, st, ex) { try { L.property('ADBE Text Properties').property('ADBE Text Document').expression = db2_H(ctx, st) + ex; } catch (e) { jzWarn('db2 source text: ' + e.toString()); } }
// the expression's pad2
var DB2_PAD = 'function p2(n){n=Math.max(0,Math.floor(n));return n<10?"0"+n:""+n;}';

// lyric bbox clamped like the browser's getBB
function db2_bb(ctx, bb) {
    var W = ctx.W, H = ctx.H;
    var b = { x0: Math.max(bb.x0, -W * 0.1), x1: Math.min(bb.x1, W * 1.1), y0: Math.max(bb.y0, -H * 0.1), y1: Math.min(bb.y1, H * 1.1) };
    if (b.x1 <= b.x0) { b.x0 = bb.x0; b.x1 = bb.x1; }
    if (b.y1 <= b.y0) { b.y0 = bb.y0; b.y1 = bb.y1; }
    b.cx = bb.cx != null ? bb.cx : (b.x0 + b.x1) / 2; b.cy = bb.cy != null ? bb.cy : (b.y0 + b.y1) / 2;
    return b;
}
function db2_bw(bb) { return bb.x1 - bb.x0; }
function db2_bh(bb) { return bb.y1 - bb.y0; }
function db2_hit(x0, y0, x1, y1, bb, pad) { pad = pad || 0; return !(x1 < bb.x0 - pad || x0 > bb.x1 + pad || y1 < bb.y0 - pad || y0 > bb.y1 + pad); }
// 0 (touching the lyric) \u2026 1 (clear by soft)
function db2_clear(bb, x, y, pad, soft) { var dx = Math.max(bb.x0 - pad - x, 0, x - bb.x1 - pad), dy = Math.max(bb.y0 - pad - y, 0, y - bb.y1 - pad); return jzClamp(Math.sqrt(dx * dx + dy * dy) / soft, 0, 1); }
// a screen corner (inside the margin) that keeps clear of the lyric
function db2_corner(ctx, bb, w, h, P, mk) {
    var W = ctx.W, H = ctx.H, m = db2_mg(ctx) * (mk || 1), sx0 = P.right ? 1 : -1, sy0 = P.low ? 1 : -1;
    var order = [[sx0, sy0], [-sx0, sy0], [sx0, -sy0], [-sx0, -sy0]], best = null, bestA = 1e18, i;
    for (i = 0; i < 4; i++) {
        var sx = order[i][0], sy = order[i][1], X = sx > 0 ? W - m - w : m, Y = sy > 0 ? H - m - h : m;
        if (!db2_hit(X, Y, X + w, Y + h, bb, 8)) return { x: X, y: Y, cx: X + w / 2, cy: Y + h / 2, ok: true, sx: sx, sy: sy };
        var ov = Math.max(0, Math.min(X + w, bb.x1) - Math.max(X, bb.x0)) * Math.max(0, Math.min(Y + h, bb.y1) - Math.max(Y, bb.y0));
        if (ov < bestA) { bestA = ov; best = { x: X, y: Y, cx: X + w / 2, cy: Y + h / 2, ok: false, sx: sx, sy: sy }; }
    }
    return best;
}
// place a w\u00D7h box next to the lyric (outside it, inside the safe margin)
function db2_near(ctx, bb, w, h, P, gap) {
    var W = ctx.W, H = ctx.H, m = db2_mg(ctx) * 0.8, sx0 = P.right ? 1 : -1, sy0 = P.low ? 1 : -1, i;
    var order = P.corner ? [[sx0, sy0], [-sx0, sy0], [sx0, -sy0], [-sx0, -sy0]] : [[sx0, sy0], [sx0, -sy0], [-sx0, sy0], [-sx0, -sy0]], tries = [];
    for (i = 0; i < 4; i++) {
        var sx = order[i][0], sy = order[i][1];
        var ax = sx > 0 ? bb.x1 - w : bb.x0, ox = sx > 0 ? bb.x1 + gap : bb.x0 - gap - w;
        var ay = sy > 0 ? bb.y1 + gap : bb.y0 - gap - h, iy = sy > 0 ? bb.y1 - h : bb.y0;
        if ((P.v | 0) % 2) { tries.push([ox, iy, sx, sy]); tries.push([ax, ay, sx, sy]); tries.push([ox, ay, sx, sy]); }
        else { tries.push([ax, ay, sx, sy]); tries.push([ox, iy, sx, sy]); tries.push([ox, ay, sx, sy]); }
    }
    for (i = 0; i < tries.length; i++) {
        var X = jzClamp(tries[i][0], m, Math.max(m, W - m - w)), Y = jzClamp(tries[i][1], m, Math.max(m, H - m - h));
        if (!db2_hit(X, Y, X + w, Y + h, bb, gap * 0.4)) return { x: X, y: Y, cx: X + w / 2, cy: Y + h / 2, ok: true, sx: tries[i][2], sy: tries[i][3] };
    }
    return db2_corner(ctx, bb, w, h, P);
}
function db2_spot(ctx, bb, w, h, P, gap) { return P.corner ? db2_corner(ctx, bb, w, h, P) : db2_near(ctx, bb, w, h, P, gap); }
// a w\u00D7h block in the free band under (or over) the lyric, centred on it; falls back to spot()
function db2_band(ctx, bb, w, h, P, gap) {
    var W = ctx.W, H = ctx.H, m = db2_mg(ctx) * 0.8, order = P.low ? [false, true] : [true, false], i;
    for (i = 0; i < 2; i++) {
        var below = order[i], y = below ? bb.y1 + gap : bb.y0 - gap - h;
        if (y < m || y + h > H - m) continue;
        var x = jzClamp((bb.x0 + bb.x1) / 2 - w / 2, m, W - m - w);
        return { x: x, y: y, cx: x + w / 2, cy: y + h / 2, ok: true };
    }
    return db2_spot(ctx, bb, w, h, P, gap);
}
// free rectangles around the lyric, biggest usable first
function db2_free(ctx, bb, gap) {
    var W = ctx.W, H = ctx.H, m = db2_mg(ctx) * 0.8, i, j;
    var all = [{ x: m, y: m, w: W - 2 * m, h: bb.y0 - gap - m, side: 't' }, { x: m, y: bb.y1 + gap, w: W - 2 * m, h: H - m - bb.y1 - gap, side: 'b' },
        { x: m, y: m, w: bb.x0 - gap - m, h: H - 2 * m, side: 'l' }, { x: bb.x1 + gap, y: m, w: W - m - bb.x1 - gap, h: H - 2 * m, side: 'r' }], out = [];
    for (i = 0; i < all.length; i++) if (all[i].w > 30 && all[i].h > 30) out.push(all[i]);
    for (i = 1; i < out.length; i++) for (j = i; j > 0 && Math.min(out[j].w, out[j].h * 1.5) > Math.min(out[j - 1].w, out[j - 1].h * 1.5); j--) { var tmp = out[j]; out[j] = out[j - 1]; out[j - 1] = tmp; }
    return out;
}
// a point on the padded lyric box perimeter at arc length pt (clockwise from the top-left corner)
function db2_perim(X0, Y0, X1, Y1, pt) {
    if (pt < X1 - X0) return [X0 + pt, Y0];
    pt -= X1 - X0; if (pt < Y1 - Y0) return [X1, Y0 + pt];
    pt -= Y1 - Y0; if (pt < X1 - X0) return [X1 - pt, Y1];
    pt -= X1 - X0; return [X0, Y1 - pt];
}
// a character from the lyric: prefer kanji, then kana / latin
function db2_lyricChar(ctx, salt) {
    var c = ctx.cut, s = jzChars(String(c.text || c.lineText || '')), arr = [], kan = [], kana = [], i;
    for (i = 0; i < s.length; i++) if (jzTrim(s[i]) && !jzIsPunct(s[i])) arr.push(s[i]);
    if (!arr.length) return '';
    for (i = 0; i < arr.length; i++) { if (jzIsKanji(arr[i])) kan.push(arr[i]); else if (jzIsKata(arr[i]) || jzIsHira(arr[i]) || jzIsLatin(arr[i])) kana.push(arr[i]); }
    var p = kan.length ? kan : (kana.length ? kana : arr);
    return p[db2_h(c.seed, salt | 0, 5) % p.length];
}
// register an entry; all its layers are main-pass only in the browser -> keep them out of the ghosts
function db2_reg(k, back, fn) {
    jzReg('decor', k, { back: !!back, build: function (ctx, bb, d) {
        var n0 = ctx.comp.numLayers, i, b = db2_bb(ctx, bb);
        DB2_BB = b;
        try { fn(ctx, b, d); } finally { DB2_BB = null; }
        var added = ctx.comp.numLayers - n0;
        for (i = 1; i <= added; i++) jzNoGhost(ctx.comp.layer(i));
    } });
}

// ================================================================ nature / atmosphere

/* ---- vines \u2014 \u8513: a tendril growing in from a screen corner, sprouting leaves and curls, stopping short of the lyric */
db2_reg('vines', false, function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, u = db2_u(ctx), ST = db2_settle(ctx), v, i, k;
    var nV = d.big ? 2 : 1, ac = db2_acc(sc), leafC = jzMixHex(ac, sc.fg, 0.15);
    for (v = 0; v < nV; v++) {
        var sx = (d.right ? 1 : -1) * (v ? -1 : 1), sy = (d.low ? 1 : -1) * (v ? -1 : 1);
        var x0 = sx > 0 ? W - W * 0.08 : W * 0.08, y0 = sy > 0 ? H + 4 * u : -4 * u;
        var th = Math.atan2(-sy, -sx * 0.55), x = x0, y = y0, step = 7 * u, Lmax = jzU(ctx) * 0.62, pts = [[x, y]], seed = d.seed + v * 17, s;
        for (s = 0; s < Lmax && pts.length < 200; s += step) {
            th += Math.sin(s / (90 * u) + seed % 7) * 0.05 + db2_noise1(s / (60 * u), seed) * 0.03;
            x += Math.cos(th) * step; y += Math.sin(th) * step;
            if (db2_clear(bb, x, y, 22 * u, 1) < 1 || x < 4 || x > W - 4 || y < -8 || y > H + 8) break;
            pts.push([x, y]);
        }
        if (pts.length < 6) continue;
        var E = 'var e=oc((t-' + jzN(v * 0.12) + ')/1);';
        var S = jzShapeLayer(ctx, 'vine ' + (v + 1), 0, 0), gV = jzGrp(S, 'vine');
        db2_gpos(gV, x0, y0);
        db2_gx(ctx, gV, 'ADBE Vector Rotation', ST, 'Math.sin(t*0.9+' + v + ')*1.2');
        // growing tip (a bud riding the end of the stem)
        var tab = [], NS = 24;
        for (i = 0; i <= NS; i++) tab.push(db2_pt(db2_part(pts, 0, Math.max(0.0005, i / NS)).slice(-1)[0]));
        var gT = db2_sub(gV, 'bud'), eT = jzAddEllipse(gT, 4.8 * u, 4.8 * u);
        jzSetExpr(eT.property('ADBE Vector Ellipse Position'), db2_H(ctx, ST) + E + 'var P=[' + tab.join(',') + '],k=e*' + NS + ',i=Math.min(' + (NS - 1) + ',Math.floor(k)),f=k-i;[P[i][0]+(P[i+1][0]-P[i][0])*f,P[i][1]+(P[i+1][1]-P[i][1])*f]');
        jzAddFill(gT, ac);
        db2_gop(ctx, gT, ST, 'e<0.999?1:0', E);
        // curls and leaves sprout where the stem has passed
        db2_sub(gV, 'curls');
        var gL = db2_sub(gV, 'leaves'), gC = db2_kid(gV, 'curls');
        for (k = 1; k < 12; k++) {
            var f = k / 12 + db2_rs(seed, k, 1) * 0.02, p = db2_along(pts, f), side = k % 2 ? 1 : -1;
            var GR = E + 'var gw=ob(cl((e-' + jzN(f) + ')/0.12),1.8);';
            if (k % 4 === 3) {
                var cp = [], an0 = p.ang + side * 1.2, R0 = 12 * u, ccx = p.x + Math.cos(an0) * R0, ccy = p.y + Math.sin(an0) * R0;
                for (i = 0; i <= 20; i++) { var tt = i / 20, an = an0 + Math.PI + side * tt * 8, rr = R0 * (1 - tt * 0.85); cp.push([ccx + Math.cos(an) * rr, ccy + Math.sin(an) * rr]); }
                var gc = db2_sub(gC, 'curl ' + k); jzAddPath(gc, cp, false);
                db2_trim(ctx, gc, ST, '100*gw', null, null, GR);
                continue;
            }
            var Ll = (17 + db2_r(seed, k, 2) * 12) * u, an2 = p.ang + side * 0.95, c = Math.cos(an2), sn = Math.sin(an2), lp = [];
            for (i = 0; i <= 10; i++) { var t1 = i / 10, a1 = Math.sin(t1 * Math.PI) * Ll * 0.3; lp.push([t1 * Ll * c - a1 * sn, t1 * Ll * sn + a1 * c]); }
            for (i = 10; i >= 0; i--) { var t2 = i / 10, a2 = -Math.sin(t2 * Math.PI) * Ll * 0.3; lp.push([t2 * Ll * c - a2 * sn, t2 * Ll * sn + a2 * c]); }
            var gl = db2_sub(gL, 'leaf ' + k); jzAddPath(gl, lp, true);
            jzGX(gl).property('ADBE Vector Position').setValue([p.x, p.y]);
            db2_gx(ctx, gl, 'ADBE Vector Scale', ST, GR + '[100*gw,100*gw]');
        }
        db2_st(gC, sc.fg, Math.max(1, 1.2 * u), 85, 2, 2);
        jzAddFill(gL, leafC, 90);
        // the stem draws on over ~1 s
        var gS = db2_sub(gV, 'stem'); jzAddPath(gS, pts, false);
        db2_trim(ctx, gS, ST, '100*e', null, null, E);
        db2_st(gS, sc.fg, Math.max(1.2, 1.7 * u), 90, 2, 2);
        db2_op(ctx, S, ST);
    }
});

/* ---- cloudPuffs \u2014 \u96F2: puffy line-art cumulus clouds drifting slowly through the free band (one layer per cloud; the body fill is
   clipped at the flat base by a layer mask) */
function db2_cloud(Wc, seed) {            // the browser's cloudOutline(0, 0, Wc, seed): base line at y = 0
    var base = [[-0.38, 0.15, 0.3], [-0.21, 0.22, 0.5], [0.02, 0.26, 0.75], [0.23, 0.2, 0.5], [0.39, 0.13, 0.3], [-0.07, 0.19, 1.35]], cs = [], runs = [], i, j;
    for (i = 0; i < base.length; i++) { var r = base[i][1] * Wc * (0.9 + 0.2 * db2_r(seed, i, 1)); cs.push([base[i][0] * Wc, -r * base[i][2], r]); }
    function hid(px, py, x, y) {
        if (py > 0) return true;
        for (var k = 0; k < cs.length; k++) { var c = cs[k]; if ((c[0] !== x || c[1] !== y) && Math.sqrt((px - c[0]) * (px - c[0]) + (py - c[1]) * (py - c[1])) < c[2] - 0.5) return true; }
        return false;
    }
    function run(x, y, rr, a0, a1, n) {
        var cur = null;
        for (var q = 0; q <= n; q++) {
            var an = a0 + q / n * (a1 - a0), px = x + Math.cos(an) * rr, py = y + Math.sin(an) * rr;
            if (hid(px, py, x, y)) { cur = null; continue; }
            if (!cur) { cur = []; runs.push(cur); }
            cur.push([px, py]);
        }
    }
    for (j = 0; j < cs.length; j++) { run(cs[j][0], cs[j][1], cs[j][2], Math.PI, 2 * Math.PI, 40); run(cs[j][0], cs[j][1], cs[j][2], 0, Math.PI, 20); }
    var xl = 1e9, xr = -1e9;
    for (j = 0; j < cs.length; j++) { var c2 = cs[j]; if (-c2[1] < c2[2]) { var dd = Math.sqrt(c2[2] * c2[2] - c2[1] * c2[1]); xl = Math.min(xl, c2[0] - dd); xr = Math.max(xr, c2[0] + dd); } }
    var out = [];
    for (i = 0; i < runs.length; i++) if (runs[i].length > 1) out.push(runs[i]);
    return { runs: out, base: xl < 1e8 ? [[xl, 0], [xr, 0]] : null, circles: cs };
}
db2_reg('cloudPuffs', false, function (ctx, bb, d) {
    var sc = ctx.sc, u = db2_u(ctx), ST = db2_settle(ctx), i, k;
    var fr0 = db2_free(ctx, bb, 20 * u), fr = [];
    for (i = 0; i < fr0.length; i++) if (fr0[i].side === 't' || fr0[i].side === 'b' || fr0[i].h > fr0[i].w) fr.push(fr0[i]);
    if (!fr.length) return;
    var reg = fr[0], n = 2 + ((d.n | 0) % 2), lw = Math.max(1.2, 1.6 * u);
    for (i = 0; i < n; i++) {
        var r1 = db2_r(d.seed, i, 1), r2 = db2_r(d.seed, i, 2), r3 = db2_r(d.seed, i, 3), r4 = db2_r(d.seed, i, 4);
        var Wc = Math.min(jzClamp(reg.w * 0.42 * (0.6 + 0.4 * r1), 90 * u, 260 * u), reg.h / 0.56);
        if (Wc < 50 * u) continue;
        var lane = (i + 0.5) / n, dir = (d.right ? 1 : -1) * (i % 2 ? -1 : 1);
        var CX = 'var cx=wr(' + jzN(reg.x + reg.w * (lane + (r2 - 0.5) * 0.25)) + '+(' + dir + ')*t*' + jzN((6 + r3 * 8) * u) + ',' + jzN(reg.x - Wc * 0.2) + ',' + jzN(reg.w + Wc * 0.4) + ');';
        var BY = jzN(reg.y + Wc * 0.52 + Math.max(0, reg.h - Wc * 0.56) * r4);
        var Q = 'var q=ob(cl((t-' + jzN(i * 0.12) + ')/0.45),1.5);';
        var cld = db2_cloud(Wc, d.seed + i), S = jzShapeLayer(ctx, 'cloud ' + (i + 1), 0, 0);
        // inner shading curl
        var c0 = cld.circles[2], gc = jzGrp(S, 'curl');
        jzAddPath(gc, db2_arc(c0[0] - c0[2] * 0.1, c0[1] + c0[2] * 0.12, c0[2] * 0.55, 200, 290, 16), false); db2_st(gc, sc.sub, Math.max(1, u), 60, 2);
        // outline: the visible arcs of the circle union + the flat base
        var go = jzGrp(S, 'outline');
        for (k = 0; k < cld.runs.length; k++) jzAddPath(go, cld.runs[k], false);
        if (cld.base) jzAddPath(go, cld.base, false);
        db2_st(go, sc.fg, lw, 90, 2, 2);
        // soft body (union of the circles, one fill = no alpha build-up)
        var gf = jzGrp(S, 'body');
        for (k = 0; k < cld.circles.length; k++) jzAddEllipse(gf, cld.circles[k][2] * 2, cld.circles[k][2] * 2, cld.circles[k][0], cld.circles[k][1]);
        jzAddFill(gf, sc.dim, 50);
        db2_mask(S, db2_rectShape(-Wc * 2, -Wc * 2, Wc * 2, lw / 2));
        db2_lx(ctx, S, 'ADBE Position', ST, CX + '[cx,' + BY + '+Math.sin(t*0.8+' + i + ')*' + jzN(2 * u) + ']');
        db2_lx(ctx, S, 'ADBE Scale', ST, Q + 'var s=100*(0.6+0.4*q);[s,s]');
        db2_op(ctx, S, ST, '(q>0?1:0)*((cx-' + jzN(Wc * 0.6) + '<' + jzN(reg.x - Wc * 0.1) + '||cx+' + jzN(Wc * 0.6) + '>' + jzN(reg.x + reg.w + Wc * 0.1) + ')?0:1)', CX + Q);
    }
});

/* ---- starField \u2014 \u661F\u7A7A: a fine twinkling star field behind the lyric with an occasional shooting star.
   Stars are static ellipses sorted into (size level \u00D7 twinkle bucket) groups; each bucket twinkles on its own clock. */
db2_reg('starField', true, function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, u = db2_u(ctx), dur = ctx.cut.dur, i, b, lv;
    var dk = db2_dark(sc), col = dk ? sc.fg : sc.sub, kk = dk ? 1 : 0.7, N = Math.min(160, 90 + (d.n | 0) * 25), NB = 4;
    var LA = [0.45, 0.65, 0.9], LR = [1.2, 1.9, 2.8], stars = [[], [], []], bright = [];
    for (lv = 0; lv < 3; lv++) for (b = 0; b < NB; b++) stars[lv].push([]);
    for (i = 0; i < N; i++) {
        var x = db2_r(d.seed, i, 2) * W, y = db2_r(d.seed, i, 3) * H, r6 = db2_r(d.seed, i, 6), l = r6 < 0.6 ? 0 : r6 < 0.9 ? 1 : 2;
        stars[l][i % NB].push([x, y]);
        if (l === 2 && db2_r(d.seed, i, 7) < 0.5) bright.push([x, y, 1.5 + db2_r(d.seed, i, 4) * 3, db2_r(d.seed, i, 5) * 6, db2_r(d.seed, i, 1) * 0.5]);
    }
    var S = jzShapeLayer(ctx, 'star field', 0, 0);
    // shooting star: a fresh streak every 2.4 s (per-cycle start / angle pre-computed)
    var T = 2.4, PH = 0.6 + (d.r || 0) * 0.8, Ls = jzU(ctx) * 0.35, TL = Ls * 0.35, nc = Math.ceil(dur / T) + 2, Q = [], cy;
    for (cy = 0; cy < nc; cy++) Q.push('[' + jzN(W * db2_rr(0.15, 0.85, d.seed, cy, 1)) + ',' + jzN(H * db2_rr(0.05, 0.4, d.seed, cy, 2)) + ',' + (db2_r(d.seed, cy, 3) < 0.5 ? 25 : 155) + ']');
    var SH = 'var ph=t-' + jzN(PH) + ',cy=Math.max(0,Math.floor(ph/' + T + ')),tt=(ph-cy*' + T + ')/0.55,QQ=[' + Q.join(',') + '],q=QQ[Math.min(cy,QQ.length-1)],an=q[2]*Math.PI/180,tl=Math.sin(Math.PI*cl(tt));';
    var gs = jzGrp(S, 'shooting star');
    db2_gx(ctx, gs, 'ADBE Vector Position', 0, SH + 'var e=oc(tt)*' + jzN(Ls) + ';[q[0]+Math.cos(an)*e,q[1]+Math.sin(an)*e]');
    db2_gx(ctx, gs, 'ADBE Vector Rotation', 0, SH + 'q[2]');
    db2_gop(ctx, gs, 0, '(ph>=0&&tt<=1)?1:0', SH);
    for (i = 0; i < 6; i++) {
        var gk = db2_sub(gs, 'tail ' + (i + 1)); jzAddPath(gk, [[0, 0], [-TL, 0]], false);
        db2_trim(ctx, gk, 0, '100*' + jzN((i + 1) / 6) + '*tl', '100*' + jzN(i / 6) + '*tl', null, SH);
        db2_st(gk, col, 1.4 * u * (1 - i / 6), 80 * (1 - i / 6) * kk, 2);
    }
    // cross flares on some bright stars
    for (i = 0; i < bright.length; i++) {
        var B = bright[i], TW = 'var tw=0.5+0.5*Math.sin(t*' + jzN(B[2]) + '+' + jzN(B[3]) + '),f=cl((t-' + jzN(B[4]) + ')/0.3);';
        var gf = jzGrp(S, 'flare ' + (i + 1));
        jzAddPath(gf, [[B[0] - 14 * u, B[1]], [B[0] + 14 * u, B[1]]], false); jzAddPath(gf, [[B[0], B[1] - 14 * u], [B[0], B[1] + 14 * u]], false);
        db2_trim(ctx, gf, 0, '50+25*(1+tw)', '50-25*(1+tw)', null, TW);
        db2_st(gf, col, Math.max(1, 0.8 * u), 50 * kk);
        db2_gop(ctx, gf, 0, 'tw*f', TW);
    }
    // the stars: brightest level on top
    for (lv = 2; lv >= 0; lv--) for (b = 0; b < NB; b++) {
        var L = stars[lv][b]; if (!L.length) continue;
        var g = jzGrp(S, 'stars ' + lv + '.' + b), rr = LR[lv] * u * 0.85;
        for (i = 0; i < L.length; i++) jzAddEllipse(g, rr * 2, rr * 2, L[i][0], L[i][1]);
        jzAddFill(g, col, LA[lv] * kk * 100);
        var s7 = lv * 7 + b;
        db2_gop(ctx, g, 0, '(0.45+0.55*(0.5+0.5*Math.sin(t*' + jzN(1.5 + db2_r(d.seed, s7, 81) * 3) + '+' + jzN(db2_r(d.seed, s7, 82) * 6) + ')))*cl((t-' + jzN(db2_r(d.seed, s7, 83) * 0.5) + ')/0.3)');
    }
    db2_op(ctx, S, 0);
});

/* ---- moonPhases \u2014 \u6708\u9F62: a row of moon-phase icons (new \u2192 full \u2192 new) with the evening's phase ringed and named */
var DB2_MOON = ['\u65B0\u6708', '\u4E09\u65E5\u6708', '\u4E0A\u5F26', '\u5341\u4E09\u591C', '\u6E80\u6708', '\u5C45\u5F85\u6708', '\u4E0B\u5F26', '\u6709\u660E'];
db2_reg('moonPhases', false, function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, u = db2_u(ctx), m = db2_mg(ctx), ST = db2_settle(ctx), k, i;
    var n = 8, fs = db2_fs(ctx) * 0.95, r = jzClamp(jzU(ctx) * 0.025, 14 * u, 27 * u), dx = r * 2.8;
    if (dx * (n - 1) + r * 2 > W - 2 * m) { dx = (W - 2 * m - r * 2) / (n - 1); r = Math.min(r, dx / 2.6); }
    var w = dx * (n - 1) + r * 2, h = r * 2 + fs * 2.6, gap = 26 * u, above = !d.low;
    var y0 = above ? bb.y0 - gap - h : bb.y1 + gap;
    if (y0 < m * 0.6 || y0 + h > H - m * 0.6) y0 = above ? bb.y1 + gap : bb.y0 - gap - h;
    var sp = { x: jzClamp((bb.x0 + bb.x1) / 2 - w / 2, m, W - m - w), y: y0, ok: true };
    if (y0 < m * 0.4 || y0 + h > H - m * 0.4) sp = db2_spot(ctx, bb, w, h, d, gap);
    var a = sp.ok ? 1 : 0.35, cy = sp.y + r, ac = db2_acc(sc), target = db2_h(ctx.cut.seed, 3) % n;
    var SEL = 'var sel=ioc((t-0.25)/0.7)*' + target + ';', ly = cy + r * 1.8;
    var S = jzShapeLayer(ctx, 'moon phases', 0, 0);
    // the current phase: ring + tick riding along the row
    var gH = jzGrp(S, 'current');
    var gHt = db2_sub(gH, 'tick'); jzAddPath(gHt, [[0, r * 1.8 - 3 * u], [0, r * 1.8 + 3 * u]], false); db2_st(gHt, ac, Math.max(1, 1.4 * u));
    var gHr = db2_sub(gH, 'ring'); jzAddEllipse(gHr, r * 2.9, r * 2.9); db2_st(gHr, ac, Math.max(1.2, 1.6 * u)); db2_gop(ctx, gHr, ST, 'oe((t-0.2)/0.3)');
    db2_gx(ctx, gH, 'ADBE Vector Position', ST, SEL + '[' + jzN(sp.x + r) + '+sel*' + jzN(dx) + ',' + jzN(cy) + ']');
    var gB = jzGrp(S, 'baseline'); jzAddPath(gB, [[sp.x, ly], [sp.x + w, ly]], false);
    db2_trim(ctx, gB, ST, '100*oe((t-0.1)/0.5)'); db2_st(gB, sc.sub, Math.max(1, u), 50);
    for (k = 0; k < n; k++) {
        var cx = sp.x + r + k * dx, p = k / n, f = (1 - Math.cos(p * Math.PI * 2)) / 2, wax = p <= 0.5, g = jzGrp(S, 'moon ' + (k + 1));
        db2_gpos(g, cx, cy);
        db2_gx(ctx, g, 'ADBE Vector Scale', ST, 'var q=100*ob(cl((t-' + jzN(k * 0.05) + ')/0.3),1.8);[q,q]');
        if (f > 0.02) {
            var kx = 1 - 2 * f, pts = [], gl = db2_sub(g, 'lit');
            for (i = 0; i <= 16; i++) { var t1 = (-90 + 180 * i / 16) * DB2_D; pts.push([cx + (wax ? 1 : -1) * Math.cos(t1) * r, cy + Math.sin(t1) * r]); }
            for (i = 16; i >= 0; i--) { var t2 = (-90 + 180 * i / 16) * DB2_D; pts.push([cx + (wax ? 1 : -1) * kx * Math.cos(t2) * r, cy + Math.sin(t2) * r]); }
            jzAddPath(gl, pts, true); jzAddFill(gl, sc.fg, 92);
        }
        var go = db2_sub(g, 'outline'); jzAddEllipse(go, r * 2, r * 2, cx, cy); db2_st(go, sc.sub, Math.max(1, u), 80);
    }
    jzXf(S, 'ADBE Opacity').setValue(a * 100); db2_op(ctx, S, ST);
    var T = db2_label(ctx, DB2_MOON[target], sp.x + r + target * dx, ly + fs * 1.1, { font: jzSerifF(ctx), size: fs, align: 'center', color: sc.fg, track: 0.2, alpha: a });
    db2_srcText(ctx, T, ST, SEL + '["' + DB2_MOON.join('","') + '"][Math.round(sel)]');
    db2_lx(ctx, T, 'ADBE Position', ST, SEL + '[' + jzN(sp.x + r) + '+sel*' + jzN(dx) + ',value[1]]');
    db2_op(ctx, T, ST, '0.5+0.5*cl((t-0.9)/0.3)');
});

/* ---- sunRays \u2014 \u967D\u5C04\u3057: faint sunburst wedges slowly turning out of a corner (or the bottom edge), behind the lyric */
db2_reg('sunRays', true, function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, u = db2_u(ctx);
    var bottom = (d.v | 0) % 3 === 2, cx = bottom ? W / 2 : d.right ? W + W * 0.02 : -W * 0.02, cy = bottom ? H + H * 0.08 : d.low ? H + H * 0.03 : -H * 0.03;
    var N = 16 + (d.n | 0) * 4, R = Math.sqrt(W * W + H * H) * 1.05, dk = db2_dark(sc), ac = db2_acc(sc), R0 = jzU(ctx) * 0.13;
    var S = jzShapeLayer(ctx, 'sun rays', cx, cy);
    // the sun: glow disc + two rings, popping in
    var gS = jzGrp(S, 'sun');
    var g3 = db2_sub(gS, 'disc'); jzAddEllipse(g3, R0 * 1.6, R0 * 1.6); jzAddFill(g3, ac, 12);
    var g2 = db2_sub(gS, 'ring 2'); jzAddEllipse(g2, R0 * 2.36, R0 * 2.36); db2_st(g2, ac, Math.max(1, u), 25);
    var g1 = db2_sub(gS, 'ring'); jzAddEllipse(g1, R0 * 2, R0 * 2); db2_st(g1, ac, Math.max(1, 1.4 * u), 50);
    db2_gx(ctx, gS, 'ADBE Vector Scale', 0, 'var q=100*ob(cl(t/0.5),1.4);[q,q]');
    // wedges: one ray + a Repeater, turning slowly
    var gW = jzGrp(S, 'rays'), a1 = Math.PI * 2 / N * 0.5, gw = db2_sub(gW, 'ray');
    jzAddPath(gw, [[0, 0], [R, 0], [Math.cos(a1) * R, Math.sin(a1) * R]], true);
    db2_rep(gW, N, 0, 0, 360 / N);
    jzAddFill(gW, d.accent ? ac : sc.dim, (d.accent ? (dk ? 0.07 : 0.09) : (dk ? 0.45 : 0.6)) * 100);
    db2_gx(ctx, gW, 'ADBE Vector Rotation', 0, jzN((d.r || 0) * 30) + '+t*' + jzN(2.2 * (d.right ? -1 : 1)));
    db2_gx(ctx, gW, 'ADBE Vector Scale', 0, 'var e=100*oc(t/0.8);[e,e]');
    db2_op(ctx, S, 0);
});

/* ---- rainRipples \u2014 \u96E8\u306E\u6CE2\u7D0B: raindrops landing on a still ground plane in the lower screen, perspective ripple rings (under the lyric).
   One group per drop; each landing cycle's spot / size is pre-computed. */
db2_reg('rainRipples', true, function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, u = db2_u(ctx), dur = ctx.cut.dur, i, j, cy;
    var dk = db2_dark(sc), col = dk ? sc.fg : sc.sub, kk = dk ? 0.55 : 0.6, N = 9 + (d.n | 0) * 3, y0 = H * 0.6, y1 = H * 0.97, lw = Math.max(1, 1.1 * u);
    var S = jzShapeLayer(ctx, 'rain ripples', 0, 0);
    for (i = 0; i < N; i++) {
        var T = 1.5 + db2_r(d.seed, i, 1) * 0.9, off = db2_r(d.seed, i, 2) * T, nc = Math.ceil((dur + 1) / T) + 2, Q = [];
        for (cy = 0; cy < nc; cy++) {
            var x = W * db2_rr(0.03, 0.97, d.seed, i, cy, 3), f = db2_r(d.seed, i, cy, 4), y = jzLerp(y0, y1, f * f * 0.3 + f * 0.7);
            var dep = 0.45 + 0.55 * (y - y0) / (y1 - y0), Rm = (70 + db2_r(d.seed, i, cy, 5) * 90) * u * dep;
            Q.push(db2_arr([x, y, Rm, 0.26 * (0.8 + 0.4 * dep), dep]));
        }
        var HD = 'var TT=t+' + jzN(off) + ',cy=Math.floor(TT/' + jzN(T) + '),tau=TT-cy*' + jzN(T) + '-0.22,QQ=[' + Q.join(',') + '],q=QQ[Math.min(cy,QQ.length-1)];';
        var g = jzGrp(S, 'drop ' + (i + 1));
        db2_gx(ctx, g, 'ADBE Vector Position', 0, HD + '[q[0],q[1]]');
        // the falling drop (just before landing)
        var gd = db2_sub(g, 'drop'), gdl = db2_sub(gd, 'streak');
        jzAddPath(gdl, [[0, -26 * u], [0, 0]], false);
        db2_gx(ctx, gdl, 'ADBE Vector Scale', 0, HD + '[100,100*q[4]]');
        db2_gx(ctx, gd, 'ADBE Vector Position', 0, HD + 'var qq=1+tau/0.22;[0,-(1-qq)*' + jzN(H * 0.18) + ']');
        db2_gop(ctx, gd, 0, 'tau<0?0.6:0', HD);
        db2_st(gd, col, lw, kk * 100, 2);
        // three ripple rings, spreading and fading
        for (j = 0; j < 3; j++) {
            var TJ = HD + 'var tj=(tau-' + jzN(j * 0.14) + ')/1.1,r=q[2]*oc(tj)*' + jzN(1 - j * 0.18) + ',al=Math.pow(Math.max(0,1-tj),1.4);';
            var gr = db2_sub(g, 'ring ' + (j + 1)), el = jzAddEllipse(gr, 10, 10);
            jzSetExpr(el.property('ADBE Vector Ellipse Size'), db2_H(ctx, 0) + TJ + '[2*r,2*r*q[3]]');
            db2_gop(ctx, gr, 0, '(tj>0&&tj<1)?(al>0.6?0.8:al>0.3?0.5:0.22):0', TJ);
            db2_st(gr, col, lw, kk * 100);
        }
    }
    db2_op(ctx, S, 0, 'oc(t/0.4)');
});

/* ---- bubbles \u2014 \u30B7\u30E3\u30DC\u30F3\u7389: soap bubbles rising and wobbling; bubbles that drift up under the lyric pop just before touching it.
   One group per bubble (body + pop burst); each cycle's spot / size / speed / life is pre-computed. */
db2_reg('bubbles', false, function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, u = db2_u(ctx), m = db2_mg(ctx), dur = ctx.cut.dur, i, cy;
    var N = Math.min(14, 7 + (d.n | 0) * 2), iri = db2_acc2(sc), pad = 10 * u, lw = Math.max(1, 1.2 * u);
    var S = jzShapeLayer(ctx, 'bubbles', 0, 0);
    for (i = 0; i < N; i++) {
        var born = db2_r(d.seed, i, 9) < 0.4 && bb.y0 - pad - 30 * u > m, r1 = db2_r(d.seed, i, 1), r2 = db2_r(d.seed, i, 2);
        var Tc = born ? 2.2 + r1 * 1.2 : 3.4 + r1 * 1.6, T0 = born ? -(0.1 + r2 * 1.4) : r2 * Tc, nc = Math.ceil((dur + Tc + 1) / Tc) + 1, Q = [];
        for (cy = 0; cy < nc; cy++) {
            var rc3 = db2_r(d.seed, i, cy, 3), rc4 = db2_r(d.seed, i, cy, 4), rc5 = db2_r(d.seed, i, cy, 5), rc6 = db2_r(d.seed, i, cy, 6), rc7 = db2_r(d.seed, i, cy, 7);
            var R = (14 + rc3 * rc3 * 30) * u * (born ? 0.8 : 1), v = (born ? 45 + rc4 * 40 : 60 + rc4 * 60) * u;
            var x0 = born ? jzLerp(bb.x0 + R, bb.x1 - R, rc5) : m + (W - 2 * m) * rc5;
            var yy0 = born ? bb.y0 - pad - R : H + R + 10 * u, life = born ? Math.min(Tc - 0.3, (yy0 + R) / v) : 2.2 + rc6 * (Tc - 2.6);
            var over = x0 + R + 16 * u > bb.x0 - pad && x0 - R - 16 * u < bb.x1 + pad;
            if (!born && over && yy0 > bb.y1) life = Math.min(life, (yy0 - (bb.y1 + pad + R)) / v);
            Q.push(db2_arr([x0, yy0, R, v, life, rc7 * 6]));
        }
        var HD = 'var TT=t+' + jzN(T0) + ',on=TT>=0?1:0,cy=Math.max(0,Math.floor(TT/' + jzN(Tc) + ')),tau=TT-cy*' + jzN(Tc) + ',QQ=[' + Q.join(',') + '],q=QQ[Math.min(cy,QQ.length-1)],R=q[2],LF=q[4];' +
            'var x=q[0]+Math.sin(tau*1.4+q[5])*' + jzN(16 * u) + (born ? '*Math.min(1,tau)' : '') + ',y=q[1]-q[3]*Math.min(tau,LF);' +
            'var inf=' + (born ? 'ob(cl(tau/0.45),1.6)' : 'ob(cl(t/0.35+((cy>0||' + (r2 * Tc > 0.4 ? 1 : 0) + ')?1:0)),1.6)') + ',wob=Math.sin(tau*5+' + i + ')*0.05,rx=R*(1+wob)*inf,ry=R*(1-wob)*inf;';
        var g = jzGrp(S, 'bubble ' + (i + 1));
        db2_gx(ctx, g, 'ADBE Vector Position', 0, HD + '[x,y]');
        db2_gop(ctx, g, 0, 'on', HD);
        // pop: 8 short sparks flying out from the rim
        var gp = db2_sub(g, 'pop'), gps = db2_sub(gp, 'spark');
        jzAddPath(gps, [[0, 0], [100, 0]], false);
        var PQ = HD + 'var pq=cl((tau-LF)/0.24);';
        db2_gx(ctx, gps, 'ADBE Vector Position', 0, PQ + '[R*(1+pq*0.6),0]');
        db2_gx(ctx, gps, 'ADBE Vector Scale', 0, PQ + '[' + jzN(5 * u) + '*(1-pq),100]');
        db2_rep(gp, 8, 0, 0, 45);
        jzGX(gp).property('ADBE Vector Rotation').setValue((i / DB2_D) % 360);
        db2_st(gp, sc.fg, lw, 100, 2);
        db2_gop(ctx, gp, 0, '(tau>=LF&&tau<LF+0.24)?(1-pq)*0.9:0', PQ);
        // body: rim, iridescent arc, highlight arc, glint
        var gb = db2_sub(g, 'body');
        db2_gop(ctx, gb, 0, '(tau<LF&&co(x,y,' + jzN(pad * 0.5) + '+R,1)>=1)?1:0', HD);
        var g4 = db2_sub(gb, 'glint'), e4 = jzAddEllipse(g4, 3.2 * u, 3.2 * u);
        jzSetExpr(e4.property('ADBE Vector Ellipse Position'), db2_H(ctx, 0) + HD + '[-rx*0.36,-ry*0.44]');
        jzSetExpr(e4.property('ADBE Vector Ellipse Size'), db2_H(ctx, 0) + HD + 'var s=' + jzN(3.2 * u) + '*inf;[s,s]');
        jzAddFill(g4, sc.fg, 90);
        var g3 = db2_sub(gb, 'highlight'), e3 = jzAddEllipse(g3, 10, 10);
        jzSetExpr(e3.property('ADBE Vector Ellipse Size'), db2_H(ctx, 0) + HD + '[1.6*rx,1.6*ry]');
        db2_trimV(g3, 0, 38 / 3.6, 292 + 90); db2_st(g3, sc.fg, 1.6 * u, 70, 2);
        var g2 = db2_sub(gb, 'iridescence'), e2 = jzAddEllipse(g2, 10, 10);
        jzSetExpr(e2.property('ADBE Vector Ellipse Size'), db2_H(ctx, 0) + HD + '[1.6*rx,1.6*ry]');
        db2_trimV(g2, 0, 62 / 3.6, 200 + 90); db2_st(g2, iri, 1.8 * u, 80, 2);
        var g1 = db2_sub(gb, 'rim'), e1 = jzAddEllipse(g1, 10, 10);
        jzSetExpr(e1.property('ADBE Vector Ellipse Size'), db2_H(ctx, 0) + HD + '[2*rx,2*ry]');
        db2_st(g1, sc.fg, lw, 70);
    }
    db2_op(ctx, S, 0);
});

/* ---- smoke \u2014 \u7159: thin wisps of smoke curling upward (optionally from a stick of incense with a glowing tip), under the lyric.
   AE-native: the wisps are drawn once (the browser's curve at t \u2248 1.2 s), a skew expression sways each strand more the higher it
   gets, Wave Warp sends a ripple up through them, and a layer mask grows them from the base. */
db2_reg('smoke', true, function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, u = db2_u(ctx), m = db2_mg(ctx), w, s, q, dd;
    var incense = (d.v | 0) % 2 === 1, dk = db2_dark(sc), col = dk ? sc.fg : sc.sub, kk = dk ? 0.5 : 0.6, ac = db2_acc(sc);
    var nW = incense ? 1 : 2 + ((d.n | 0) % 2), Hm = H * 0.72, T = 1.2;
    var yb = incense ? H - m - 90 * u : H + 10 * u;
    var S = jzShapeLayer(ctx, 'smoke', 0, 0);
    for (w = 0; w < nW; w++) {
        var xb = incense ? (d.right ? W - W * 0.12 : W * 0.12) : W * db2_rr(0.15, 0.85, d.seed, w, 1), seed = d.seed + w * 31;
        var gw = jzGrp(S, 'wisp ' + (w + 1));
        for (s = 0; s < 4; s++) {
            var pts = [], spd = s - 1.5;
            for (dd = 0; dd <= Hm; dd += 8 * u) {
                var A = 6 * u + dd * 0.17, x = xb + spd * dd * 0.035 + db2_noise1(dd / (130 * u) - T * 0.8 + s * 0.45, seed) * A + Math.sin(dd / (75 * u) - T * 1.5 + s * 0.8) * A * 0.4;
                pts.push([x, yb - dd]);
            }
            if (pts.length < 3) continue;
            var gs = db2_sub(gw, 'strand ' + (s + 1));
            db2_gpos(gs, xb, yb);
            db2_gx(ctx, gs, 'ADBE Vector Skew', 0, 'Math.sin(t*' + jzN(0.7 + 0.25 * s) + '+' + jzN(w * 1.7 + s * 0.9) + ')*' + jzN(9 + 3 * s));
            for (q = 3; q >= 0; q--) {
                var gq = db2_sub(gs, 'part ' + (q + 1)); jzAddPath(gq, pts, false);
                db2_trimV(gq, q * 25, Math.min(100, (q + 1) * 25 + 0.5));
                db2_st(gq, col, (1.7 - s * 0.25) * u, (0.6 - q * 0.13) * (1 - s * 0.18) * kk * 100, 2, 2);
            }
        }
    }
    var mk = db2_mask(S, db2_rectShape(-W, yb, 2 * W, yb + 2 * H), null, 6 * u);
    jzSetExpr(mk.property('ADBE Mask Offset'), db2_H(ctx, 0) + jzN(Hm) + '*oc(t/1)');
    var wv = jzEffect(S, 'ADBE Wave Warp', 'JZ Smoke Ripple');
    jzEP(wv, 1, 1); jzEP(wv, 2, 5 * u); jzEP(wv, 3, 470 * u); jzEP(wv, 4, 0); jzEP(wv, 5, 0.24);
    db2_op(ctx, S, 0);
    if (incense) {
        var xi = d.right ? W - W * 0.12 : W * 0.12, I = jzShapeLayer(ctx, 'incense', 0, 0);
        var gt = jzGrp(I, 'ember'); jzAddEllipse(gt, 4.8 * u, 4.8 * u, xi, yb); jzAddFill(gt, ac);
        var gg = jzGrp(I, 'glow'); jzAddEllipse(gg, 12 * u, 12 * u, xi, yb); jzAddFill(gg, ac, 15);
        db2_gop(ctx, gg, 0, '0.7+0.3*Math.sin(t*7)');
        var gk = jzGrp(I, 'stick'); jzAddPath(gk, [[xi, yb], [xi + 1.5 * u, H - m * 0.4]], false); db2_st(gk, sc.sub, 2 * u, 80);
        db2_op(ctx, I, 0);
    }
});

/* ---- dandelion \u2014 \u7DBF\u6BDB: dandelion seeds (stalk + umbrella of filaments) drifting on the wind, optionally shed from a puffball */
db2_reg('dandelion', false, function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, u = db2_u(ctx), m = db2_mg(ctx), i, k;
    var dir = d.right ? 1 : -1, N = 6 + (d.n | 0) * 2 + (d.big ? 3 : 0), lw = Math.max(1, 0.9 * u), ac = db2_acc(sc);
    var puff = !!d.big, px = d.right ? W * 0.1 : W * 0.9, py = H - m - H * 0.16;
    var S = jzShapeLayer(ctx, 'dandelion', 0, 0);
    // one seed (local coords, scale 1): stalk + 13 filaments + tips + the seed body
    var fil = [[[0, 2 * u], [0, -24 * u]]], tips = [];
    for (k = 0; k < 13; k++) { var an = (-90 + (k - 6) * 14) * DB2_D, L = 17 * u, pp = [Math.cos(an) * L, -24 * u + Math.sin(an) * L * 0.75]; fil.push([[0, -24 * u], pp]); tips.push(pp); }
    for (i = 0; i < N; i++) {
        var r = [], j; for (j = 0; j < 10; j++) r.push(db2_r(d.seed, i, j));
        var v = (40 + r[2] * 50) * u, XY;
        if (puff) {
            var TT = (W * 0.9) / v;
            XY = 'var TT=' + jzN(TT) + ',age=wr(t+' + jzN(r[4] * TT) + ',0,TT),x=' + jzN(px) + '+(' + dir + ')*' + jzN(v) + '*age,y=' + jzN(py) + '-' + jzN(v * 0.38) + '*age+Math.sin(t*' + jzN(1 + r[7]) + '+' + jzN(r[8] * 6) + ')*' + jzN(14 * u) + '*Math.min(1,age),ok=age>=0.15;';
        } else {
            XY = 'var x=wr(' + jzN(r[3] * W) + '+(' + dir + ')*' + jzN(v) + '*(t+' + jzN(r[4] * 8) + '),' + jzN(-60 * u) + ',' + jzN(W + 120 * u) + '),y=wr(' + jzN(H * (0.12 + 0.76 * r[5])) + '-' + jzN((18 + r[6] * 20) * u) + '*t+Math.sin(t*' + jzN(1 + r[7]) + '+' + jzN(r[8] * 6) + ')*' + jzN(18 * u) + ',' + jzN(-40 * u) + ',' + jzN(H + 80 * u) + '),ok=true;';
        }
        var QQ = 'var q=oc((t-' + jzN(r[1] * 0.35) + ')/0.4);';
        var g = jzGrp(S, 'seed ' + (i + 1));
        var gb = db2_sub(g, 'body'); jzAddPath(gb, [[0, 2 * u], [0, 10 * u]], false); db2_st(gb, ac, 2.2 * u, 90, 2);
        var gt = db2_sub(g, 'tips'); for (k = 0; k < tips.length; k++) jzAddEllipse(gt, 2.2 * u, 2.2 * u, tips[k][0], tips[k][1]); jzAddFill(gt, sc.fg, 80);
        var gf = db2_sub(g, 'filaments'); for (k = 0; k < fil.length; k++) jzAddPath(gf, fil[k], false); db2_st(gf, sc.fg, lw, 70);
        db2_gx(ctx, g, 'ADBE Vector Position', 0, XY + '[x,y]');
        db2_gx(ctx, g, 'ADBE Vector Rotation', 0, jzN(dir * 12) + '+Math.sin(t*1.2+' + i + ')*18');
        db2_gx(ctx, g, 'ADBE Vector Scale', 0, QQ + 'var s=' + jzN((0.8 + r[9] * 0.5) * 100) + '*q;[s,s]');
        db2_gop(ctx, g, 0, '(ok&&q>0&&co(x,y,' + jzN(14 * u) + ',' + jzN(26 * u) + ')>=0.6)?1:0', XY + QQ);
    }
    if (puff) {                     // the puffball the seeds come from
        var E = 'var e=oc(t/0.5);', gP = jzGrp(S, 'puffball');
        var gc = db2_sub(gP, 'centre'), ec = jzAddEllipse(gc, 8 * u, 8 * u, px, py);
        jzSetExpr(ec.property('ADBE Vector Ellipse Size'), db2_H(ctx, 0) + E + 'var s=' + jzN(8 * u) + '*e;[s,s]');
        jzAddFill(gc, sc.sub);
        var gh = db2_sub(gP, 'head'); db2_sub(gh, 'tips');
        var ghl = db2_sub(gh, 'lines'), ght = db2_kid(gh, 'tips');
        for (k = 0; k < 30; k++) {
            if (db2_r(d.seed, k, 44) < 0.25) continue;
            var a2 = k / 30 * Math.PI * 2, x1 = px + Math.cos(a2) * 30 * u, y1 = py + Math.sin(a2) * 30 * u;
            jzAddPath(ghl, [[px, py], [x1, y1]], false); jzAddEllipse(ght, 2.6 * u, 2.6 * u, x1, y1);
        }
        jzAddFill(ght, sc.fg, 80); db2_st(ghl, sc.fg, lw, 70);
        db2_gpos(gh, px, py); db2_gx(ctx, gh, 'ADBE Vector Scale', 0, E + '[100*e,100*e]');
        var gs = db2_sub(gP, 'stem'), gss = db2_sub(gs, 'path'), st = [];
        for (k = 0; k <= 12; k++) { var f = k / 12; st.push([px + Math.sin(f * 2) * 8 * u * (d.right ? -1 : 1), H + 4 * u - (H + 4 * u - py) * f]); }
        jzAddPath(gss, st, false); db2_gpos(gss, px, H + 4 * u);
        db2_gx(ctx, gss, 'ADBE Vector Scale', 0, E + '[100,100*e]');
        db2_st(gs, sc.sub, 1.6 * u, 90, 2);
    }
    db2_op(ctx, S, 0);
});

/* ---- fireflies \u2014 \u86CD: fireflies wandering around the lyric, glowing on and off with soft halos and faint trails */
db2_reg('fireflies', false, function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, u = db2_u(ctx), m = db2_mg(ctx), dur = ctx.cut.dur, i;
    var col = db2_acc(sc);
    if (db2_dark(sc)) { var c2 = db2_acc2(sc); if (jzLum(c2) > jzLum(col)) col = c2; }
    var N = Math.min(16, 9 + (d.n | 0) * 2), cx = (bb.x0 + bb.x1) / 2, cy = (bb.y0 + bb.y1) / 2;
    var S = jzShapeLayer(ctx, 'fireflies', 0, 0);
    for (i = 0; i < N; i++) {
        var r = [], j; for (j = 0; j < 6; j++) r.push(db2_r(d.seed, i, j));
        var PS = 'function ps(T){var th=' + jzN(r[2] * Math.PI * 2) + '+nz(T*0.18+' + jzN(i * 3.1) + ',' + db2_ntab(d.seed, i * 3.1 - 0.1, i * 3.1 + (dur + 1) * 0.18) + ')*1.6,' +
            'rd=' + jzN(0.55 + 0.45 * r[3]) + '+0.12*nz(T*0.3+' + jzN(i * 5.3) + ',' + db2_ntab(d.seed + 1, i * 5.3 - 0.1, i * 5.3 + (dur + 1) * 0.3) + ');' +
            'return [Math.max(' + jzN(m * 0.5) + ',Math.min(' + jzN(W - m * 0.5) + ',' + jzN(cx) + '+Math.cos(th)*' + jzN(W * 0.5 - m) + '*rd)),Math.max(' + jzN(m * 0.5) + ',Math.min(' + jzN(H - m * 0.5) + ',' + jzN(cy) + '+Math.sin(th)*' + jzN(H * 0.5 - m) + '*rd))];}var p=ps(t);';
        var g = jzGrp(S, 'firefly ' + (i + 1));
        var gc = db2_sub(g, 'core'); jzAddEllipse(gc, 6 * u, 6 * u); jzAddFill(gc, col);
        var gt = db2_sub(g, 'trail'), gts = db2_sub(gt, 'seg'); jzAddPath(gts, [[0, 0], [100, 0]], false);
        db2_gx(ctx, gts, 'ADBE Vector Rotation', 0, PS + 'var q=ps(t-0.25);Math.atan2(q[1]-p[1],q[0]-p[0])*180/Math.PI');
        db2_gx(ctx, gts, 'ADBE Vector Scale', 0, PS + 'var q=ps(t-0.25);[Math.sqrt((q[0]-p[0])*(q[0]-p[0])+(q[1]-p[1])*(q[1]-p[1])),100]');
        db2_st(gt, col, 1.2 * u, 20, 2);
        var gh2 = db2_sub(g, 'halo'); jzAddEllipse(gh2, 17 * u, 17 * u); jzAddFill(gh2, col, 26);
        var gh1 = db2_sub(g, 'glow'); jzAddEllipse(gh1, 40 * u, 40 * u); jzAddFill(gh1, col, 10);
        db2_gx(ctx, g, 'ADBE Vector Position', 0, PS + 'p');
        db2_gop(ctx, g, 0, 'al>0.02?cl((t-' + jzN(r[1] * 0.4) + ')/0.4)*al*(Math.pow(Math.max(0,Math.sin(t*' + jzN(1.6 + r[4] * 1.4) + '+' + jzN(r[5] * 6) + ')),2)*0.85+0.15):0',
            PS + 'var al=co(p[0],p[1],' + jzN(10 * u) + ',' + jzN(34 * u) + ');');
    }
    db2_op(ctx, S, 0);
});

// ================================================================ graphic shapes

/* ---- memphis \u2014 \u30E1\u30F3\u30D5\u30A3\u30B9: Memphis-style squiggles, zigzags, outlined triangles, dot grids and hatch strokes placed round the lyric */
db2_reg('memphis', false, function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, u = db2_u(ctx), ST = db2_settle(ctx), i, k;
    var K = Math.min(8, 5 + (d.n | 0)), pad = 34 * u + db2_bh(bb) * 0.22;
    var X0 = bb.x0 - pad, X1 = bb.x1 + pad, Y0 = bb.y0 - pad, Y1 = bb.y1 + pad, per = 2 * (X1 - X0 + Y1 - Y0);
    var cols = [db2_acc(sc), db2_acc2(sc), sc.fg], S0 = jzClamp(jzU(ctx) * 0.036, 24 * u, 44 * u), lw = 3.4 * u;
    var types = ['squig', 'zig', 'tri', 'dots', 'half', 'hatch'];
    var S = jzShapeLayer(ctx, 'memphis', 0, 0);
    for (i = 0; i < K; i++) {
        var p = db2_perim(X0, Y0, X1, Y1, ((i + 0.2 + db2_r(d.seed, i, 1) * 0.6) / K) * per);
        var x = jzClamp(p[0], S0 * 1.6, W - S0 * 1.6), y = jzClamp(p[1], S0 * 1.6, H - S0 * 1.6);
        if (db2_clear(bb, x, y, S0 * 1.3, 1) < 1) continue;
        var type = types[(i + (d.v | 0)) % types.length], col = cols[i % 3], pts = [];
        var Q = 'var q=cl((t-' + jzN(0.04 + i * 0.06) + ')/0.4),s2=ob(q,2),de=ioc(q);';
        var g = jzGrp(S, type + ' ' + (i + 1)), gi = db2_sub(g, 'shape');
        jzGX(g).property('ADBE Vector Position').setValue([x, y]);
        db2_gx(ctx, g, 'ADBE Vector Rotation', ST, jzN(db2_r(d.seed, i, 3) * 360) + '+Math.sin(t*1.3+' + i + ')*6');
        db2_gop(ctx, g, ST, 'q>0?1:0', Q);
        if (type === 'squig' || type === 'zig') {
            if (type === 'squig') for (k = 0; k <= 24; k++) { var f = k / 24; pts.push([(f - 0.5) * S0 * 2.6, Math.sin(f * Math.PI * 3) * S0 * 0.28]); }
            else for (k = 0; k <= 6; k++) pts.push([(k / 6 - 0.5) * S0 * 2.4, (k % 2 ? -1 : 1) * S0 * 0.3]);
            jzAddPath(gi, pts, false);
            db2_trim(ctx, g, ST, '100*de', null, null, Q);
            db2_st(g, col, lw, 100, 2, 2);
        } else if (type === 'tri') {
            for (k = 0; k < 3; k++) { var an = (-90 + k * 120) * DB2_D; pts.push([Math.cos(an) * S0 * 0.8, Math.sin(an) * S0 * 0.8]); }
            jzAddPath(gi, pts, true);
            db2_gx(ctx, gi, 'ADBE Vector Scale', ST, Q + '[100*s2,100*s2]');
            db2_st(g, col, lw * 0.8, 100, 1, 2);
        } else if (type === 'dots') {
            var gd = db2_sub(gi, 'dot'), ed = jzAddEllipse(gd, 5.2 * u, 5.2 * u, -S0 * 0.5, -S0 * 0.5);
            jzSetExpr(ed.property('ADBE Vector Ellipse Size'), db2_H(ctx, ST) + Q + 'var r=' + jzN(5.2 * u) + '*s2;[r,r]');
            db2_rep(gi, 3, S0 * 0.5, 0);
            var rp2 = db2_rep(gi, 3, 0, S0 * 0.5);
            jzAddFill(g, col);
        } else if (type === 'half') {
            jzAddPath(gi, db2_arc(0, 0, S0 * 0.7, 180, 360, 20), true);
            db2_gx(ctx, gi, 'ADBE Vector Scale', ST, Q + '[100*s2,100*s2]');
            jzAddFill(g, col);
        } else {
            for (k = -1; k <= 1; k++) jzAddPath(gi, [[k * S0 * 0.45 - S0 * 0.3, S0 * 0.5], [k * S0 * 0.45 + S0 * 0.3, -S0 * 0.5]], false);
            db2_trim(ctx, g, ST, '50+50*de', '50-50*de', null, Q);
            db2_st(g, col, lw * 0.9, 100, 2);
        }
    }
    db2_op(ctx, S, ST);
});

/* ---- zigzagRibbon \u2014 \u30B8\u30B0\u30B6\u30B0\u30EA\u30DC\u30F3: an accordion-folded two-tone ribbon unfurling in from a screen corner.
   Each fold is a parallelogram that grows out of its start point as the ribbon unfurls (uniform scale instead of the browser's
   lengthening). */
db2_reg('zigzagRibbon', false, function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, u = db2_u(ctx), ST = db2_settle(ctx), k;
    var rw = jzClamp(jzU(ctx) * 0.3, 200 * u, 420 * u), rh = rw * 0.42;
    var sp = db2_corner(ctx, bb, rw, rh, d, 0.6), a = sp.ok ? 1 : 0.3;
    var sx = sp.sx, sy = sp.sy, ox = sx > 0 ? W + 20 * u : -20 * u, oy = sy > 0 ? sp.y + rh * 0.75 : sp.y + rh * 0.25;
    var ix = sx > 0 ? sp.x : sp.x + rw, iy = sy > 0 ? sp.y + rh * 0.2 : sp.y + rh * 0.8;
    var L = Math.sqrt((ix - ox) * (ix - ox) + (iy - oy) * (iy - oy)), dx = (ix - ox) / L, dy = (iy - oy) / L, nx = -dy, ny = dx;
    var seg = 7, Z = rh * 0.22, wv = rh * 0.3, vx = nx * 0.35, vy = 1, vl = Math.sqrt(vx * vx + vy * vy), VX = vx / vl * wv, VY = vy / vl * wv;
    function C(k) { var s = L * k / seg, z = k % 2 ? Z : -Z; return [ox + dx * s + nx * z, oy + dy * s + ny * z]; }
    var c1 = db2_acc(sc), a2 = db2_acc2(sc), c2 = jzContrast(a2, sc.bg) > 1.6 && a2 !== c1 ? a2 : jzMixHex(c1, sc.bg, 0.4);
    var S = jzShapeLayer(ctx, 'zigzag ribbon', 0, 0);
    jzGrp(S, 'fold edges'); jzGrp(S, 'front folds'); jzGrp(S, 'back folds');
    var gE = db2_top(S, 'fold edges'), gF = db2_top(S, 'front folds'), gB = db2_top(S, 'back folds');
    for (k = 0; k < seg; k++) {
        var p0 = C(k), p1 = C(k + 1), F = 'var f=cl(oc(t/0.6)*' + seg + '-' + k + ');';
        var gq = db2_sub(k % 2 ? gB : gF, 'fold ' + (k + 1));
        jzAddPath(gq, [p0, p1, [p1[0] + VX, p1[1] + VY], [p0[0] + VX, p0[1] + VY]], true);
        var ge = db2_sub(gE, 'edges ' + (k + 1));
        jzAddPath(ge, [p0, p1], false); jzAddPath(ge, [[p0[0] + VX, p0[1] + VY], [p1[0] + VX, p1[1] + VY]], false);
        for (var q = 0; q < 2; q++) {
            var gg = q ? ge : gq;
            db2_gpos(gg, p0[0], p0[1]);
            db2_gx(ctx, gg, 'ADBE Vector Scale', ST, F + '[100*f,100*f]');
        }
    }
    db2_st(gE, sc.bg, Math.max(1, 0.8 * u), 25);
    jzAddFill(gF, c1, 95); jzAddFill(gB, c2, 95);
    jzXf(S, 'ADBE Opacity').setValue(a * 100); db2_op(ctx, S, ST);
});

/* ---- polkaPatch \u2014 \u6C34\u7389: a patch of polka dots whose sizes breathe in a travelling diagonal wave.
   Dots on the same diagonal share their size; a few light up in the other colour at the wave crest. */
db2_reg('polkaPatch', false, function (ctx, bb, d) {
    var sc = ctx.sc, u = db2_u(ctx), ST = db2_settle(ctx), i, j;
    var cols = 6 + (d.n | 0), rows = 3 + ((d.v | 0) % 2), s = jzClamp(jzU(ctx) * 0.03, 20 * u, 36 * u);
    var w = (cols - 0.5) * s, h = (rows - 1) * s * 0.87 + s;
    var sp = db2_spot(ctx, bb, w, h, d, 24 * u), a = sp.ok ? 1 : 0.35;
    var round = (d.v | 0) % 3 === 2, ac = db2_acc(sc), dir = d.right ? 1 : -1, cMain = d.accent ? ac : sc.fg, cHot = d.accent ? sc.fg : ac;
    var S = jzShapeLayer(ctx, 'polka patch', 0, 0), groups = {};
    for (j = 0; j < rows; j++) for (i = 0; i < cols; i++) {
        var x = sp.x + s * 0.5 + i * s + (j % 2 ? s * 0.5 : 0), y = sp.y + s * 0.5 + j * s * 0.87;
        if (x > sp.x + w) continue;
        if (round) { var fx = (x - sp.x - w / 2) / (w / 2), fy = (y - sp.y - h / 2) / (h / 2); if (fx * fx + fy * fy > 1.05) continue; }
        var dd = (dir > 0 ? i : cols - i) + j, hot = (i + j) % 3 === 0, key = dd + (hot ? 'h' : '');
        if (!groups[key]) {
            var gn = 'diagonal ' + dd + (hot ? ' hot' : '');
            jzGrp(S, gn);
            groups[key] = { name: gn, dd: dd, hot: hot, list: [] };
        }
        groups[key].list.push([x, y]);
    }
    for (var kk in groups) {
        if (!groups.hasOwnProperty(kk)) continue;
        var G = groups[kk], gG = db2_top(S, G.name), WV = 'var q=ob(cl((t-' + jzN(G.dd * 0.03) + ')/0.3),2),wv=0.5+0.5*Math.sin(t*3.2-' + jzN(G.dd * 0.7) + '),r=' + jzN(s * 0.84) + '*(0.35+0.65*wv)*q;';
        for (i = 0; i < G.list.length; i++) {
            var el = jzAddEllipse(gG, s * 0.84, s * 0.84, G.list[i][0], G.list[i][1]);
            jzSetExpr(el.property('ADBE Vector Ellipse Size'), db2_H(ctx, ST) + WV + '[r,r]');
        }
        var fl = jzAddFill(gG, cMain, 90);
        if (G.hot) {
            jzSetExpr(fl.property('ADBE Vector Fill Color'), db2_H(ctx, ST) + WV + 'wv>0.93?' + db2_cx(cHot) + ':' + db2_cx(cMain));
            jzSetExpr(fl.property('ADBE Vector Fill Opacity'), db2_H(ctx, ST) + WV + 'wv>0.93?100:90');
        }
    }
    jzXf(S, 'ADBE Opacity').setValue(a * 100); db2_op(ctx, S, ST);
});

/* ---- stripeCircle \u2014 \u7E1E\u306E\u5186: a disc filled with fine rotating stripes, ringed, with an off-register outline "shadow"
   (variant: a half disc of sliding stripes under fine concentric arcs). Stripes are clipped by a layer mask. */
db2_reg('stripeCircle', false, function (ctx, bb, d) {
    var sc = ctx.sc, u = db2_u(ctx), ST = db2_settle(ctx), k;
    var R = jzClamp(jzU(ctx) * 0.062, 40 * u, 80 * u), sp = db2_spot(ctx, bb, R * 2.4, R * 2.4, d, 22 * u), a = sp.ok ? 1 : 0.35;
    var cx = sp.cx - 3 * u, cy = sp.cy - 3 * u, half = (d.v | 0) % 2 === 1, ac = db2_acc(sc), sp2 = 8 * u;
    var E = 'var e=ob(cl(t/0.45),1.5);', SCL = E + '[100*e,100*e]';
    // off-register shadow ring
    var C = jzShapeLayer(ctx, 'stripe circle shadow', cx, cy), gc = jzGrp(C, 'shadow'); jzAddEllipse(gc, R * 2, R * 2, 7 * u, 7 * u);
    db2_st(gc, sc.sub, Math.max(1, u), 70);
    db2_lx(ctx, C, 'ADBE Scale', ST, SCL); jzXf(C, 'ADBE Opacity').setValue(a * 100); db2_op(ctx, C, ST, 'cl((t-0.2)/0.3)');
    // stripes, clipped to the disc (or its lower half)
    var B = jzShapeLayer(ctx, 'stripe circle stripes', cx, cy), gS = jzGrp(B, 'stripes'), gm = db2_sub(gS, 'slide'), gl = db2_sub(gm, 'stripe');
    var n = Math.ceil(R / sp2) + 1;
    jzAddPath(gl, [[0, -R * 1.2], [0, R * 1.2]], false);
    db2_rep(gm, 2 * n + 1, sp2, 0, 0, -n);
    if (half) db2_gx(ctx, gm, 'ADBE Vector Position', ST, '[(t*' + jzN(14 * u) + ')%' + jzN(sp2) + ',0]');
    db2_st(gS, ac, 3.2 * u);
    db2_gx(ctx, gS, 'ADBE Vector Rotation', ST, '45' + (half ? '' : '+t*' + jzN(18 * (d.right ? 1 : -1))));
    var sh = new Shape(), kk = 0.5523 * R;
    if (half) { sh.vertices = [[R, 0], [0, R], [-R, 0]]; sh.inTangents = [[0, 0], [kk, 0], [0, kk]]; sh.outTangents = [[0, kk], [-kk, 0], [0, 0]]; sh.closed = true; }
    else sh = jzCircleShape(0, 0, R);
    db2_mask(B, sh);
    db2_lx(ctx, B, 'ADBE Scale', ST, SCL); jzXf(B, 'ADBE Opacity').setValue(a * 100); db2_op(ctx, B, ST);
    // ring (+ arcs and diameter on the half variant)
    var A = jzShapeLayer(ctx, 'stripe circle', cx, cy), gr = jzGrp(A, 'ring'); jzAddEllipse(gr, R * 2, R * 2);
    db2_st(gr, sc.fg, Math.max(1, 1.5 * u));
    if (half) {
        var gd = jzGrp(A, 'diameter'); jzAddPath(gd, [[-R, 0], [R, 0]], false); db2_st(gd, sc.fg, Math.max(1, 1.2 * u));
        var ga = jzGrp(A, 'arcs');
        for (k = 1; k <= 3; k++) jzAddEllipse(ga, R * k / 2, R * k / 2);
        var tr = db2_trim(ctx, ga, ST, '50*ioc((t-0.2)/0.5)'); tr.property('ADBE Vector Trim Offset').setValue(270);
        db2_st(ga, sc.fg, Math.max(1, 1.1 * u), 80);
    }
    db2_lx(ctx, A, 'ADBE Scale', ST, SCL); jzXf(A, 'ADBE Opacity').setValue(a * 100); db2_op(ctx, A, ST);
});

/* ---- decoCorners \u2014 \u88C5\u98FE\u30B3\u30FC\u30CA\u30FC: art-deco corner ornaments (stepped double L, quarter arc, diamonds) drawn out from two screen corners */
db2_reg('decoCorners', false, function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, u = db2_u(ctx), m = db2_mg(ctx), k, j;
    var Lc = jzClamp(jzU(ctx) * 0.15, 90 * u, 190 * u), g = 9 * u, lw = Math.max(1, 1.3 * u), ac = db2_acc(sc);
    var cs = d.big ? [[-1, -1], [1, -1], [1, 1], [-1, 1]] : d.corner ? [[-1, -1], [1, 1]] : [[1, -1], [-1, 1]];
    var S = jzShapeLayer(ctx, 'deco corners', 0, 0);
    for (k = 0; k < cs.length; k++) {
        var sx = cs[k][0], sy = cs[k][1], X = sx < 0 ? m * 0.7 : W - m * 0.7, Y = sy < 0 ? m * 0.7 : H - m * 0.7;
        if (db2_hit(Math.min(X, X - sx * Lc), Math.min(Y, Y - sy * Lc), Math.max(X, X - sx * Lc), Math.max(Y, Y - sy * Lc), bb, 6 * u)) continue;
        var ix = -sx, iy = -sy, dk = jzN(k * 0.06);
        var P2 = function (ddx, ddy) { return [X + ix * ddx, Y + iy * ddy]; };
        var E1 = 'var e=oc((t-' + dk + ')/0.55);', E2 = 'var e2=oc((t-0.15-' + dk + ')/0.5);', Q = 'var q=ob(cl((t-0.35-' + dk + ')/0.3),2);';
        var G = jzGrp(S, 'corner ' + (k + 1));
        // diamonds + dot (pop in last, on top)
        var dia = [[P2(Lc, 0), 4 * u, ac, 100], [P2(0, Lc), 4 * u, ac, 100], [P2(Lc * 0.42 * 0.7071, Lc * 0.42 * 0.7071), 5.5 * u, sc.fg, 90]];
        var gdt = db2_sub(G, 'dot'), pdt = P2(g * 3.4, g * 3.4); jzAddEllipse(gdt, 3.6 * u, 3.6 * u); jzAddFill(gdt, ac);
        jzGX(gdt).property('ADBE Vector Position').setValue(pdt); db2_gx(ctx, gdt, 'ADBE Vector Scale', 0, Q + '[100*q,100*q]');
        for (j = 0; j < dia.length; j++) {
            var r = dia[j][1], gd = db2_sub(G, 'diamond ' + (j + 1));
            jzAddPath(gd, [[0, -r], [r, 0], [0, r], [-r, 0]], true); jzAddFill(gd, dia[j][2], dia[j][3]);
            jzGX(gd).property('ADBE Vector Position').setValue(dia[j][0]); db2_gx(ctx, gd, 'ADBE Vector Scale', 0, Q + '[100*q,100*q]');
        }
        // quarter arcs opening from their middle
        var a0 = Math.atan2(iy, 0) / DB2_D, a1 = Math.atan2(0, ix) / DB2_D, s0 = a0, s1 = a1;
        if (Math.abs(s1 - s0) > 180) { if (s1 < s0) s1 += 360; else s0 += 360; }
        var mid = (s0 + s1) / 2, hs = (s1 - s0) / 2;
        var ga2 = db2_sub(G, 'arc 2'); jzAddPath(ga2, db2_arc(X, Y, Lc * 0.42 + 5 * u, mid - hs * 0.6, mid + hs * 0.6, 20), false);
        db2_trim(ctx, ga2, 0, '50+50*e2', '50-50*e2', null, E2); db2_st(ga2, sc.sub, Math.max(1, 0.8 * u), 50);
        var ga = db2_sub(G, 'arc'); jzAddPath(ga, db2_arc(X, Y, Lc * 0.42, mid - hs, mid + hs, 30), false);
        db2_trim(ctx, ga, 0, '50+50*e2', '50-50*e2', null, E2); db2_st(ga, sc.fg, lw, 50);
        // stepped inner L and the outer L, drawn out from the corner
        var gi = db2_sub(G, 'inner'); jzAddPath(gi, [P2(Lc * 0.62, g), P2(g * 2, g), P2(g * 2, g * 2), P2(g, g * 2), P2(g, Lc * 0.62)], false);
        db2_trim(ctx, gi, 0, '50+50*e', '50-50*e', null, E1); db2_st(gi, sc.fg, lw, 60);
        var go = db2_sub(G, 'outer'); jzAddPath(go, [P2(Lc, 0), P2(0, 0), P2(0, Lc)], false);
        db2_trim(ctx, go, 0, '50+50*e', '50-50*e', null, E1); db2_st(go, sc.fg, lw, 85);
    }
    db2_op(ctx, S, 0);
});

/* ---- halfCircles \u2014 \u534A\u5186\u306E\u7A4D\u5C64: Bauhaus half-circles: a stacked tower of domes, a scalloped row, or nested rainbow arches */
db2_reg('halfCircles', false, function (ctx, bb, d) {
    var sc = ctx.sc, u = db2_u(ctx), ST = db2_settle(ctx), k;
    var v = (d.v | 0) % 3, R = jzClamp(jzU(ctx) * 0.058, 38 * u, 74 * u), lw = Math.max(1.2, 1.6 * u);
    var bw0 = v === 1 ? R * 7.4 : R * 2.2, bh0 = v === 0 ? R * 2.5 : v === 1 ? R * 1.3 : R * 1.2;
    var sp = db2_spot(ctx, bb, bw0, bh0, d, 24 * u), a = sp.ok ? 1 : 0.35, ac = db2_acc(sc), a2 = db2_acc2(sc);
    function grow(k) { return 'ob(cl((t-' + jzN(k * 0.08) + ')/0.35),1.6)'; }
    var S = jzShapeLayer(ctx, 'half circles', 0, 0), g;
    if (v === 0) {                                     // tower of domes (each sits on the one below)
        var yb = sp.y + bh0;
        for (k = 3; k >= 0; k--) {
            var r = R * (1 - k * 0.2), stack = [], j;
            for (j = 0; j < k; j++) stack.push(jzN(R * (1 - j * 0.2) * 0.62) + '*' + grow(j));
            g = jzGrp(S, 'dome ' + (k + 1));
            var gp = db2_sub(g, 'dome'); jzAddPath(gp, db2_arc(0, 0, r, 180, 360, 28), true);
            db2_gx(ctx, gp, 'ADBE Vector Scale', ST, 'var q=100*' + grow(k) + ';[q,q]');
            db2_gx(ctx, g, 'ADBE Vector Position', ST, '[' + jzN(sp.cx) + '+Math.sin(t*1.2+' + k + ')*' + jzN(2 * u * k) + ',' + jzN(yb) + (stack.length ? '-(' + stack.join('+') + ')' : '') + ']');
            if (k % 2 === 0) jzAddFill(g, k === 0 ? ac : sc.fg, 95); else db2_st(g, sc.fg, lw, 100, 1, 2);
        }
    } else if (v === 1) {                              // scalloped row, alternating up / down
        var r1 = R * 0.6, y = sp.cy;
        g = jzGrp(S, 'baseline'); jzAddPath(g, [[sp.x, y], [sp.x + bw0, y]], false);
        db2_trim(ctx, g, ST, '100*oe(t/0.5)'); db2_st(g, sc.fg, Math.max(1, u), 60);
        for (k = 0; k < 6; k++) {
            var cx = sp.x + r1 + k * r1 * 2, up = k % 2 === 0;
            g = jzGrp(S, 'scallop ' + (k + 1));
            var gs = db2_sub(g, 'arc'); jzAddPath(gs, db2_arc(0, 0, r1, up ? 180 : 0, up ? 360 : 180, 20), true);
            db2_gx(ctx, gs, 'ADBE Vector Scale', ST, 'var q=100*' + grow(k) + ';[q,q]');
            db2_gx(ctx, g, 'ADBE Vector Position', ST, '[' + jzN(cx) + ',' + jzN(y) + '+Math.sin(t*2+' + k + ')*' + jzN(2 * u) + ']');
            if (k % 3 === 0) jzAddFill(g, ac, 95); else if (k % 3 === 1) jzAddFill(g, a2, 90); else db2_st(g, sc.fg, lw);
        }
    } else {                                           // nested arches drawing on, with a filled core
        var yb2 = sp.y + bh0;
        g = jzGrp(S, 'baseline'); jzAddPath(g, [[sp.cx - R * 1.1, yb2], [sp.cx + R * 1.1, yb2]], false);
        db2_st(g, sc.fg, Math.max(1, u), 70); db2_gop(ctx, g, ST, 'oe(t/0.4)');
        g = jzGrp(S, 'core'); var gcc = db2_sub(g, 'disc'); jzAddPath(gcc, db2_arc(0, 0, R * 0.28, 180, 360, 20), true);
        db2_gx(ctx, gcc, 'ADBE Vector Scale', ST, 'var q=100*' + grow(0) + ';[q,q]');
        jzAddFill(g, ac);
        jzGX(g).property('ADBE Vector Position').setValue([sp.cx, yb2]);
        for (k = 3; k >= 0; k--) {
            var rk = R * (1 - k * 0.18);
            g = jzGrp(S, 'arch ' + (k + 1)); jzAddEllipse(g, rk * 2, rk * 2, sp.cx, yb2);
            db2_trim(ctx, g, ST, '50*oc(cl(' + grow(4 - k) + '))', null, '270+Math.sin(t*0.8)*3');
            db2_st(g, k % 2 ? ac : sc.fg, 3 * u);
        }
    }
    jzXf(S, 'ADBE Opacity').setValue(a * 100); db2_op(ctx, S, ST);
});

/* ---- loopArrows \u2014 \u5FAA\u74B0\u77E2\u5370: circular chasing arrows (2 or 3) rotating round a small readout, like a repeat / refresh glyph */
db2_reg('loopArrows', false, function (ctx, bb, d) {
    var sc = ctx.sc, u = db2_u(ctx), ST = db2_settle(ctx), k;
    var R = jzClamp(jzU(ctx) * 0.055, 36 * u, 70 * u), sp = db2_spot(ctx, bb, R * 2.8, R * 2.8, d, 22 * u), a = sp.ok ? 1 : 0.35;
    var n = 2 + ((d.n | 0) % 2), dir = d.right ? 1 : -1, ac = db2_acc(sc), lw = Math.max(1.4, 2.4 * u), seg = 360 / n, hs = 7 * u;
    var E = 'var e=oc(t/0.5),sw=' + jzN(seg - 38) + '*e;';
    var S = jzShapeLayer(ctx, 'loop arrows', sp.cx, sp.cy), gA = jzGrp(S, 'arrows');
    db2_gx(ctx, gA, 'ADBE Vector Rotation', ST, 'var e=oc(t/0.5);' + dir + '*(t*70+(1-e)*-120)+' + jzN((d.r || 0) * 360));
    for (k = 0; k < n; k++) {
        var gh = db2_sub(gA, 'head ' + (k + 1));
        jzAddPath(gh, [[R, 1.3 * dir * hs], [R + hs, -0.4 * dir * hs], [R - hs, -0.4 * dir * hs]], true); jzAddFill(gh, ac);
        jzGX(gh).property('ADBE Vector Anchor').setValue([R, 0]);
        var A1 = E + 'var a1=' + jzN(k * seg) + '+(' + dir + ')*sw;';
        db2_gx(ctx, gh, 'ADBE Vector Position', ST, A1 + '[' + jzN(R) + '*Math.cos(a1*Math.PI/180),' + jzN(R) + '*Math.sin(a1*Math.PI/180)]');
        db2_gx(ctx, gh, 'ADBE Vector Rotation', ST, A1 + 'a1');
        db2_gx(ctx, gh, 'ADBE Vector Scale', ST, E + '[100*e,100*e]');
        var gc = db2_sub(gA, 'arc ' + (k + 1)); jzAddEllipse(gc, R * 2, R * 2);
        db2_trim(ctx, gc, ST, 'sw/3.6', null, jzN(k * seg + 90) + (dir < 0 ? '-sw' : ''), E);
        db2_st(gc, sc.fg, lw, 100, 2);
    }
    var gr = jzGrp(S, 'ring'); jzAddEllipse(gr, R * 2.64, R * 2.64); db2_st(gr, sc.sub, Math.max(1, 0.8 * u), 35);
    db2_gop(ctx, gr, ST, 'oc(t/0.5)');
    jzXf(S, 'ADBE Opacity').setValue(a * 100); db2_op(ctx, S, ST);
    var T = db2_label(ctx, '\u00D71', sp.cx, sp.cy, { size: R * 0.5, align: 'center', color: sc.fg, track: 0.02, alpha: a });
    db2_srcText(ctx, T, ST, '"\u00D7"+(1+Math.floor(Math.max(0,t)/1.1))');
    db2_op(ctx, T, ST, 'oe((t-0.2)/0.3)');
});

/* ---- starburst \u2014 \u30B9\u30BF\u30FC\u30D0\u30FC\u30B9\u30C8: a zig-zag "sale sticker" badge that spins in beside the lyric, with a word / number inside */
db2_reg('starburst', false, function (ctx, bb, d) {
    var sc = ctx.sc, u = db2_u(ctx), ST = db2_settle(ctx);
    var R = jzClamp(jzU(ctx) * 0.068, 44 * u, 88 * u), sp = db2_near(ctx, bb, R * 2.2, R * 2.2, d, 14 * u), a = sp.ok ? 1 : 0.35;
    var ac = db2_acc(sc), txtC = jzLum(ac) > 0.55 ? '#000000' : sc.bg, Q = 'var q=ob(cl((t-0.05)/0.4),2.2);';
    var ROT = Q + 'var rot=t*9+(1-q)*-120+' + jzN((d.r || 0) * 30) + ';';
    var S = jzShapeLayer(ctx, 'starburst', sp.cx, sp.cy);
    var gi = jzGrp(S, 'inner ring'); jzAddEllipse(gi, R * 1.4, R * 1.4); db2_st(gi, txtC, Math.max(1, 1.2 * u), 55);
    var gs = jzGrp(S, 'badge'), st = jzAddStar(gs, 22, R, R * 0.83);
    st.property('ADBE Vector Star Rotation').setValue(90); jzAddFill(gs, ac, 97);
    db2_lx(ctx, S, 'ADBE Rotate Z', ST, ROT + 'rot');
    db2_lx(ctx, S, 'ADBE Scale', ST, Q + '[100*q,100*q]');
    jzXf(S, 'ADBE Opacity').setValue(a * 100); db2_op(ctx, S, ST);
    var v = (d.v | 0) % 5, ch = db2_lyricChar(ctx, 9);
    var txt = ['No.' + db2_pad((ctx.cut.line | 0) + 1), 'NEW', 'HIT!', ch || '\u266A', 'LOVE'][v];
    var font = v === 3 ? jzSerifF(ctx) : db2_dispF(ctx), fs = R * (v === 3 ? 0.7 : 0.42);
    var T = jzText(ctx, txt, { font: font, size: fs, color: txtC, x: sp.cx, y: sp.cy, track: 0.02, opacity: a });
    var tw = db2_textW(T); if (tw > R * 1.15) jzFit(T, R * 1.15, 1e6), jzAnchor(T, 'center');
    db2_lx(ctx, T, 'ADBE Rotate Z', ST, ROT + 'rot*0.15');
    db2_lx(ctx, T, 'ADBE Scale', ST, Q + '[value[0]*q,value[1]*q]');
    db2_op(ctx, T, ST);
});

/* ---- tally \u2014 \u6B63\u306E\u5B57: hand-drawn tally marks (\u6B63) counting up stroke by stroke through the cut */
var DB2_SEI = [[[0.1, 0.12], [0.9, 0.12]], [[0.5, 0.12], [0.5, 0.9]], [[0.5, 0.5], [0.84, 0.5]], [[0.22, 0.46], [0.22, 0.9]], [[0.02, 0.9], [0.98, 0.9]]];
db2_reg('tally', false, function (ctx, bb, d) {
    var sc = ctx.sc, u = db2_u(ctx), ST = db2_settle(ctx), c, k, i;
    var nC = 2 + ((d.n | 0) % 2), S0 = jzClamp(jzU(ctx) * 0.064, 40 * u, 76 * u), gp = S0 * 0.3, fs = db2_fs(ctx) * 1.05;
    var w = nC * S0 + (nC - 1) * gp + fs * 3.2, h = S0 * 1.1;
    var sp = db2_spot(ctx, bb, w, h, d, 24 * u), a = sp.ok ? 1 : 0.35;
    var total = nC * 5, dt = jzClamp((ctx.cut.dur * 0.75 - 0.2) / total, 0.09, 0.26), dd = Math.min(0.14, dt * 0.9);
    var col = d.accent ? db2_acc(sc) : sc.fg, lw = Math.max(1.6, 3 * u);
    var S = jzShapeLayer(ctx, 'tally', 0, 0);
    for (c = 0; c < nC; c++) {
        var x0 = sp.x + c * (S0 + gp), y0 = sp.y + (h - S0) / 2, tilt = db2_rs(d.seed, c, 1) * 4 * DB2_D, g = jzGrp(S, '\u6B63 ' + (c + 1));
        for (k = 4; k >= 0; k--) {
            var idx = c * 5 + k, pts = [];
            for (i = 0; i < 2; i++) {
                var X = (DB2_SEI[k][i][0] + db2_rs(d.seed, idx, i * 2) * 0.035 - 0.5) * S0, Y = (DB2_SEI[k][i][1] + db2_rs(d.seed, idx, i * 2 + 1) * 0.035 - 0.5) * S0;
                pts.push([x0 + S0 / 2 + X * Math.cos(tilt) - Y * Math.sin(tilt), y0 + S0 / 2 + X * Math.sin(tilt) + Y * Math.cos(tilt)]);
            }
            var gk = db2_sub(g, 'stroke ' + (k + 1)); jzAddPath(gk, pts, false);
            db2_trim(ctx, gk, ST, '100*oc((t-' + jzN(0.1 + idx * dt) + ')/' + jzN(dd) + ')');
        }
        db2_st(g, col, lw, 95, 2);
    }
    jzXf(S, 'ADBE Opacity').setValue(a * 100); db2_op(ctx, S, ST);
    var lx = sp.x + nC * (S0 + gp) - gp + fs * 0.8, CNT = 'var n=Math.max(0,Math.min(' + total + ',Math.floor((t-' + jzN(0.1 + dd) + ')/' + jzN(dt) + ')+1));';
    var T1 = db2_label(ctx, '\u00D7', lx, sp.y + h / 2, { size: fs, alpha: a });
    db2_op(ctx, T1, ST, 'oe((t-0.1)/0.3)');
    var T2 = db2_label(ctx, '00', lx + fs * 0.9, sp.y + h / 2, { size: fs * 1.3, color: sc.fg, track: 0.04, alpha: a });
    db2_srcText(ctx, T2, ST, DB2_PAD + CNT + 'p2(n)');
    db2_txtCol(ctx, T2, db2_acc(sc), ST, CNT + 'n>=' + total + '?100:0');
    db2_op(ctx, T2, ST, 'oe((t-0.1)/0.3)');
});

// ================================================================ UI widgets

/* ---- cursorClick \u2014 \u30AB\u30FC\u30BD\u30EB: a mouse pointer glides in and clicks beside the lyric (ripple + tooltip),
   or drags a marching-ants selection round it (variant) */
var DB2_CURSOR = [[0, 0], [0, 1], [0.27, 0.76], [0.45, 1.13], [0.6, 1.06], [0.42, 0.7], [0.74, 0.7]];
db2_reg('cursorClick', false, function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, u = db2_u(ctx), m = db2_mg(ctx), i;
    var Sc = jzClamp(jzU(ctx) * 0.034, 24 * u, 42 * u), ac = db2_acc(sc), marquee = (d.v | 0) % 2 === 1;
    var pad = 14 * u + db2_bh(bb) * 0.06, lw = Math.max(1, 1.2 * u), TIP, SCL = '100';
    if (marquee) {
        var A = [jzClamp(bb.x0 - pad, m * 0.4, W), jzClamp(bb.y0 - pad, m * 0.4, H)], B = [jzClamp(bb.x1 + pad, 0, W - m * 0.4), jzClamp(bb.y1 + pad, 0, H - m * 0.4)];
        var s0 = [A[0] - 60 * u, A[1] - 90 * u];
        TIP = 'var en=oc(t/0.3),dr=ioc((t-0.3)/0.6),tip;if(t>0.95){var z=oc((t-0.95)/0.4);tip=[' + jzN(B[0]) + '+' + jzN(18 * u) + '*z,' + jzN(B[1]) + '+' + jzN(12 * u) + '*z];}' +
            'else if(dr<=0)tip=[' + jzN(s0[0]) + '+' + jzN(A[0] - s0[0]) + '*en,' + jzN(s0[1]) + '+' + jzN(A[1] - s0[1]) + '*en];' +
            'else tip=[' + jzN(A[0]) + '+' + jzN(B[0] - A[0]) + '*dr,' + jzN(A[1]) + '+' + jzN(B[1] - A[1]) + '*dr];var C=dr<1?tip:' + db2_pt(B) + ';';
        var M = jzShapeLayer(ctx, 'marquee', 0, 0);
        // resize handles pop in when the drag ends
        var gH = jzGrp(M, 'handles'), x0 = A[0], y0 = A[1], x1 = B[0], y1 = B[1];
        var hp = [[x0, y0], [x1, y0], [x1, y1], [x0, y1], [(x0 + x1) / 2, y0], [(x0 + x1) / 2, y1], [x0, (y0 + y1) / 2], [x1, (y0 + y1) / 2]];
        for (i = 0; i < hp.length; i++) {
            var rh = jzAddRect(gH, 7 * u, 7 * u, 0, hp[i][0], hp[i][1]);
            jzSetExpr(rh.property('ADBE Vector Rect Size'), db2_H(ctx, 0) + 'var h=' + jzN(7 * u) + '*ob(cl((t-0.9)/0.25),2);[h,h]');
        }
        jzAddFill(gH, ac);
        // marching-ants rectangle from the anchor corner to the pointer
        var gR = jzGrp(M, 'selection'), rr = jzAddRect(gR, 10, 10);
        jzSetExpr(rr.property('ADBE Vector Rect Size'), db2_H(ctx, 0) + TIP + '[Math.abs(C[0]-' + jzN(A[0]) + '),Math.abs(C[1]-' + jzN(A[1]) + ')]');
        jzSetExpr(rr.property('ADBE Vector Rect Position'), db2_H(ctx, 0) + TIP + '[(C[0]+' + jzN(A[0]) + ')/2,(C[1]+' + jzN(A[1]) + ')/2]');
        var sk = db2_st(gR, sc.fg, lw, 90), dof = db2_dash(sk, 6 * u, 4 * u);
        if (dof) jzSetExpr(dof, 'time*' + jzN(30 * u));
        db2_gop(ctx, gR, 0, 'dr>0?1:0', TIP);
        db2_op(ctx, M, 0);
        var fsz = db2_fs(ctx);
        var TL = db2_label(ctx, Math.round(x1 - x0) + ' \u00D7 ' + Math.round(y1 - y0), x1, y1 + fsz * 1.1, { size: fsz * 0.8, align: 'right', color: sc.fg });
        db2_op(ctx, TL, 0, 'cl((t-1)/0.3)');
    } else {
        var sx = d.right ? 1 : -1, T0 = [sx > 0 ? bb.x1 + 10 * u : bb.x0 - 10 * u - Sc * 0.7, (d.low ? bb.y1 : bb.y0) + (d.low ? 8 * u : -Sc * 1.2)];
        T0[0] = jzClamp(T0[0], m * 0.5, W - m * 0.5 - Sc); T0[1] = jzClamp(T0[1], m * 0.5, H - m * 0.5 - Sc * 1.2);
        var F0 = [T0[0] + sx * W * 0.22, T0[1] + H * 0.18];
        TIP = 'var mv=oc(t/0.55),aw=ic(PO),tip=[' + jzN(F0[0]) + '+' + jzN(T0[0] - F0[0]) + '*mv+Math.sin(mv*Math.PI)*' + jzN(30 * u) + '+' + jzN(sx * 60 * u) + '*aw,' + jzN(F0[1]) + '+' + jzN(T0[1] - F0[1]) + '*mv+' + jzN(40 * u) + '*aw];';
        SCL = '((t>0.62&&t<0.72)||(t>0.84&&t<0.94)||(t>1.9&&t<2))?86:100';
        var K1 = jzShapeLayer(ctx, 'click', 0, 0), tcs = [0.62, 0.84, 1.9];
        for (i = 0; i < 3; i++) {
            var Q = 'var q=(t-' + tcs[i] + ')/0.5;', gq = jzGrp(K1, 'click ripple ' + (i + 1)), eq = jzAddEllipse(gq, 12 * u, 12 * u, T0[0], T0[1]);
            jzSetExpr(eq.property('ADBE Vector Ellipse Size'), db2_H(ctx, 0) + Q + 'var r=' + jzN(2 * u) + '*(6+26*oc(q));[r,r]');
            var sq = db2_st(gq, ac, 2 * u);
            jzSetExpr(sq.property('ADBE Vector Stroke Width'), db2_H(ctx, 0) + Q + jzN(2 * u) + '*Math.max(0.001,1-cl(q))');
            db2_gop(ctx, gq, 0, '(q>0&&q<1)?1-q:0', Q);
        }
        // tooltip under the click
        var txt = 'LYRIC ' + db2_pad((ctx.cut.line | 0) + 1), fs = db2_fs(ctx) * 0.85;
        var TT = db2_label(ctx, txt, 0, 0, { size: fs, align: 'center', color: sc.bg, track: 0.1 });
        var tw = db2_textW(TT) + 16 * u, th = fs * 1.9;
        var bx = jzClamp(T0[0] + Sc * 0.9, m * 0.4, W - m * 0.4 - tw), by = jzClamp(T0[1] + Sc * 1.25, m * 0.4, H - m * 0.4 - th);
        if (!db2_hit(bx, by, bx + tw, by + th, bb, 2)) {
            var gt = jzGrp(K1, 'tooltip'), gtr = db2_sub(gt, 'box'); jzAddRect(gtr, tw, th, 4 * u, bx + tw / 2, by + th / 2);
            db2_gpos(gtr, bx + tw / 2, by);
            db2_gx(ctx, gtr, 'ADBE Vector Scale', 0, 'var q=cl((t-0.9)/0.25);[100,100*ob(q,1.5)]');
            jzAddFill(gt, sc.fg, 92);
            TT.moveToBeginning();
            jzXf(TT, 'ADBE Position').setValue([bx + tw / 2, by + th / 2]);
            db2_op(ctx, TT, 0, 'cl((t-0.9)/0.25)');
        } else TT.remove();
        db2_op(ctx, K1, 0);
    }
    // the pointer (shadow, body, outline)
    var S = jzShapeLayer(ctx, 'cursor', 0, 0), g = jzGrp(S, 'cursor'), pts = db2_scl(DB2_CURSOR, Sc);
    var gb = db2_sub(g, 'body'); jzAddPath(gb, pts, true); db2_st(gb, sc.bg, Math.max(1, 1.4 * u), 100, 1, 2); jzAddFill(gb, sc.fg);
    var gs = db2_sub(g, 'shadow'); jzAddPath(gs, db2_off(pts, 3 * u, 4 * u), true); jzAddFill(gs, sc.sub, 30);
    db2_gx(ctx, g, 'ADBE Vector Position', 0, TIP + 'tip');
    db2_gx(ctx, g, 'ADBE Vector Scale', 0, 'var s=' + SCL + ';[s,s]');
    db2_op(ctx, S, 0);
});

/* ---- windowChrome \u2014 \u30A6\u30A3\u30F3\u30C9\u30A6: an OS window frame opening round the lyric: title bar with buttons, file name, scrollbar and status line */
db2_reg('windowChrome', false, function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, u = db2_u(ctx), m = db2_mg(ctx), ST = db2_settle(ctx), k;
    var bar = jzClamp(jzU(ctx) * 0.036, 26 * u, 44 * u), px = 30 * u + db2_bw(bb) * 0.03, py = 20 * u + db2_bh(bb) * 0.08;
    var X0 = Math.max(bb.x0 - px, m * 0.35), X1 = Math.min(bb.x1 + px, W - m * 0.35), Y0 = Math.max(bb.y0 - py - bar, m * 0.35), Y1 = Math.min(bb.y1 + py + bar * 0.6, H - m * 0.35);
    if (Y0 + bar > bb.y0 - 4 * u) Y0 = bb.y0 - 4 * u - bar;
    var cx = (X0 + X1) / 2, cy = (Y0 + Y1) / 2, lw = Math.max(1, 1.4 * u), r = 8 * u, ac = db2_acc(sc), mac = (d.v | 0) % 2 === 0;
    var SC = 'var e=oe(t/0.45)*(1-0.06*ic(PO)),s=0.94+0.06*e;', BE = 'var be=oe((t-0.2)/0.3);';
    var by = Y0 + bar / 2 - cy, br = bar * 0.17, fs = db2_fs(ctx) * 0.92;
    function lx(x) { return x - cx; }
    var S = jzShapeLayer(ctx, 'window', cx, cy);
    // buttons
    var gb = jzGrp(S, 'buttons');
    if (mac) {
        var cs = [ac, db2_acc2(sc), sc.sub];
        for (k = 0; k < 3; k++) {
            var gk = db2_sub(gb, 'button ' + (k + 1)); jzAddEllipse(gk, br * 2, br * 2); jzAddFill(gk, cs[k]);
            jzGX(gk).property('ADBE Vector Position').setValue([lx(X0 + bar * 0.55 + k * br * 3), by]);
            db2_gx(ctx, gk, 'ADBE Vector Scale', ST, BE + '[100*be,100*be]');
        }
    } else {
        var bx = lx(X1 - bar * 0.6);
        db2_sub(gb, 'close'); db2_sub(gb, 'maximise'); db2_sub(gb, 'minimise');
        var gw = [db2_kid(gb, 'close'), db2_kid(gb, 'maximise'), db2_kid(gb, 'minimise')];
        jzAddPath(gw[0], [[-br, -br], [br, br]], false); jzAddPath(gw[0], [[-br, br], [br, -br]], false);
        jzAddRect(gw[1], br * 2, br * 2); jzAddPath(gw[2], [[-br, 0], [br, 0]], false);
        var xs = [bx, bx - bar * 0.95, bx - bar * 1.9];
        for (k = 0; k < 3; k++) { jzGX(gw[k]).property('ADBE Vector Position').setValue([xs[k], by]); db2_gx(ctx, gw[k], 'ADBE Vector Scale', ST, BE + '[100*be,100*be]'); }
        db2_st(gb, sc.fg, lw);
    }
    // scrollbar
    var sbx = lx(X1 - 9 * u), st = Y0 + bar + 8 * u - cy, sbH = (Y1 - 8 * u - cy) - st;
    if (sbH > 30 * u && X1 - 9 * u > bb.x1 + 12 * u) {
        var gt = jzGrp(S, 'scroll thumb'), tr = jzAddRect(gt, 4 * u, sbH * 0.35, 2 * u), thb = sbH * 0.35;
        var TY = BE + 'var h=' + jzN(thb) + '*be,ty=' + jzN(st) + '+' + jzN(sbH - thb) + '*cl(t/' + jzN(Math.max(1, ctx.cut.dur)) + ');';
        jzSetExpr(tr.property('ADBE Vector Rect Size'), db2_H(ctx, ST) + TY + '[' + jzN(4 * u) + ',h]');
        jzSetExpr(tr.property('ADBE Vector Rect Position'), db2_H(ctx, ST) + TY + '[' + jzN(sbx) + ',ty+h/2]');
        jzAddFill(gt, sc.sub, 80);
        var gs = jzGrp(S, 'scroll track'); jzAddPath(gs, [[sbx, st], [sbx, st + sbH]], false);
        db2_trim(ctx, gs, ST, '100*be', null, null, BE); db2_st(gs, sc.sub, Math.max(1, u), 40);
    }
    // title bar, separator, frame
    var gl = jzGrp(S, 'separator'); jzAddPath(gl, [[lx(X0), Y0 + bar - cy], [lx(X1), Y0 + bar - cy]], false); db2_st(gl, sc.fg, lw, 80);
    var gT = jzGrp(S, 'title bar'); jzAddRect(gT, X1 - X0, bar, r, 0, Y0 + bar / 2 - cy); jzAddFill(gT, sc.fg, 10);
    var gF = jzGrp(S, 'frame'); jzAddRect(gF, X1 - X0, Y1 - Y0, r); db2_st(gF, sc.fg, lw, 90);
    db2_lx(ctx, S, 'ADBE Scale', ST, SC + '[100*s,100*s]');
    db2_op(ctx, S, ST, 'cl(t/0.15)');
    // labels follow the window's scale about its centre
    function follow(L, x, y, al) {
        db2_lx(ctx, L, 'ADBE Position', ST, SC + '[' + jzN(cx) + '+' + jzN(x - cx) + '*s,' + jzN(cy) + '+' + jzN(y - cy) + '*s]');
        db2_lx(ctx, L, 'ADBE Scale', ST, SC + '[value[0]*s,value[1]*s]');
        db2_op(ctx, L, ST, 'cl(t/0.15)*oe((t-0.2)/0.3)*' + jzN(al));
    }
    var T1 = db2_label(ctx, 'lyric_' + db2_pad((ctx.cut.line | 0) + 1) + '.txt', cx, Y0 + bar / 2, { size: fs, align: 'center', color: sc.fg, track: 0.08 });
    follow(T1, cx, Y0 + bar / 2, 1);
    if (Y1 - bb.y1 > fs * 1.4) {
        var T2 = db2_label(ctx, 'Ln ' + db2_pad((ctx.cut.line | 0) + 1) + ', Col ' + jzChars(String(ctx.cut.text || '')).length + '   UTF-8', X1 - 14 * u, Y1 - fs * 0.8, { size: fs * 0.8, align: 'right', track: 0.08 });
        follow(T2, X1 - 14 * u, Y1 - fs * 0.8, 0.8);
    }
});

/* ---- progressBar \u2014 \u8AAD\u307F\u8FBC\u307F\u30D0\u30FC: a UI progress bar: pill track, barber-pole fill that loads in uneven bursts, file-size readout.
   The fill (accent + moving stripes) is clipped by a rounded-rect alpha matte that grows with the progress. */
db2_reg('progressBar', false, function (ctx, bb, d) {
    var W = ctx.W, sc = ctx.sc, u = db2_u(ctx), ST = db2_settle(ctx), k;
    var fs = db2_fs(ctx) * 0.9, th = jzClamp(jzU(ctx) * 0.014, 10 * u, 18 * u);
    var w = Math.min(W * 0.4, 460 * u, Math.max(db2_bw(bb) * 0.65, 260 * u)), h = th + fs * 3.2;
    var sp = db2_band(ctx, bb, w, h, d, 26 * u), a = sp.ok ? 1 : 0.35, ac = db2_acc(sc);
    var x0 = sp.x, ty = sp.y + fs * 1.5, xc = x0 + w / 2, yc = ty + th / 2;
    var T = Math.max(0.8, ctx.cut.dur * 0.85 - 0.3), terms = [];
    for (k = 0; k < 5; k++) terms.push('0.2*ioc((t-' + jzN(0.25 + T * k / 5 + db2_r(d.seed, k, 1) * T * 0.08) + ')/' + jzN(T / 5 * (0.35 + db2_r(d.seed, k, 2) * 0.4)) + ')');
    var PR = 'var e=oe(t/0.45),tw=' + jzN(w) + '*e,tx=' + jzN(x0) + '+(' + jzN(w) + '-tw)/2,p=cl(' + terms.join('+') + '),done=p>=0.999;';
    // fill: accent + barber-pole stripes, matted by the growing rounded bar
    var F = jzShapeLayer(ctx, 'progress fill', 0, 0), gS = jzGrp(F, 'stripes'), gm = db2_sub(gS, 'slide'), gl = db2_sub(gm, 'stripe');
    var gap = 12 * u, nS = Math.ceil((w + 2 * th) / gap) + 2;
    jzAddPath(gl, [[x0 - th - gap, ty + th], [x0 - gap, ty]], false);
    db2_rep(gm, nS, gap, 0);
    db2_gx(ctx, gm, 'ADBE Vector Position', ST, '[(Math.max(0,t)*' + jzN(26 * u) + ')%' + jzN(gap) + ',0]');
    db2_st(gS, jzLum(ac) > 0.5 ? '#000000' : '#ffffff', 4 * u, 16);
    var gA = jzGrp(F, 'accent'); jzAddRect(gA, w, th, 0, xc, yc); jzAddFill(gA, ac);
    jzXf(F, 'ADBE Opacity').setValue(a * 100); db2_op(ctx, F, ST);
    var Mt = jzShapeLayer(ctx, 'progress matte', 0, 0), gM = jzGrp(Mt, 'bar'), rm = jzAddRect(gM, 10, th - 4 * u, (th - 4 * u) / 2);
    jzSetExpr(rm.property('ADBE Vector Rect Size'), db2_H(ctx, ST) + PR + '[Math.max(0,(tw-' + jzN(4 * u) + ')*p),' + jzN(th - 4 * u) + ']');
    jzSetExpr(rm.property('ADBE Vector Rect Position'), db2_H(ctx, ST) + PR + '[tx+' + jzN(2 * u) + '+Math.max(0,(tw-' + jzN(4 * u) + ')*p)/2,' + jzN(yc) + ']');
    jzAddFill(gM, '#ffffff');
    F.trackMatteType = TrackMatteType.ALPHA; Mt.enabled = false;
    // track outline + check mark
    var S = jzShapeLayer(ctx, 'progress bar', 0, 0);
    var gc = jzGrp(S, 'check'), Yc = ty + th + fs * 1.1;
    jzAddPath(gc, [[x0 + 2 * u, Yc], [x0 + 6 * u, Yc + 4 * u], [x0 + 13 * u, Yc - 5 * u]], false);
    db2_trim(ctx, gc, ST, '100*cl((t-' + jzN(T + 0.3) + ')*4)'); db2_st(gc, ac, 2 * u, 100, 2, 2);
    db2_gop(ctx, gc, ST, 'done?1:0', PR);
    var gt = jzGrp(S, 'track'), rt = jzAddRect(gt, w, th, th / 2, xc, yc);
    jzSetExpr(rt.property('ADBE Vector Rect Size'), db2_H(ctx, ST) + 'var e=oe(t/0.45);[' + jzN(w) + '*e,' + jzN(th) + ']');
    db2_st(gt, sc.fg, Math.max(1, 1.2 * u), 80);
    jzXf(S, 'ADBE Opacity').setValue(a * 100); db2_op(ctx, S, ST);
    // readouts
    var LE = 'oe((t-0.15)/0.3)', tot = 8 + (db2_h(d.seed, 3) % 400) / 10;
    var L1 = db2_label(ctx, 'Loading...', x0, sp.y + fs * 0.6, { size: fs, font: jzBodyF(ctx), color: sc.fg, track: 0.04, alpha: a });
    db2_srcText(ctx, L1, ST, PR + 'done?"Complete":"Loading"+"...".substr(0,Math.floor(Math.max(0,t)*3)%4)');
    db2_lx(ctx, L1, 'ADBE Position', ST, PR + '[tx,value[1]]'); db2_op(ctx, L1, ST, LE);
    var L2 = db2_label(ctx, '100%', x0 + w, sp.y + fs * 0.6, { size: fs, align: 'right', color: sc.fg, alpha: a });
    db2_srcText(ctx, L2, ST, PR + 'Math.floor(p*100)+"%"');
    db2_lx(ctx, L2, 'ADBE Position', ST, PR + '[tx+tw,value[1]]'); db2_txtCol(ctx, L2, ac, ST, PR + 'done?100:0'); db2_op(ctx, L2, ST, LE);
    var L3 = db2_label(ctx, tot.toFixed(1) + ' MB / ' + tot.toFixed(1) + ' MB', x0 + w, Yc, { size: fs * 0.78, align: 'right', alpha: a });
    db2_srcText(ctx, L3, ST, PR + '(' + jzN(tot) + '*p).toFixed(1)+" MB / ' + tot.toFixed(1) + ' MB"');
    db2_lx(ctx, L3, 'ADBE Position', ST, PR + '[tx+tw,value[1]]'); db2_op(ctx, L3, ST, LE + '*0.85');
});

/* ---- toggleSwitch \u2014 \u30C8\u30B0\u30EB: a small settings panel: two or three iOS-style switches flicking on one after another */
var DB2_TOGGLE = [['SHUFFLE', 'REPEAT', 'LYRICS'], ['\u60F3\u3044', '\u8A18\u61B6', '\u672A\u7DF4'], ['LOVE', 'MEMORY', 'REPLAY'], ['\u58F0', '\u5149', '\u591C']];
db2_reg('toggleSwitch', false, function (ctx, bb, d) {
    var sc = ctx.sc, u = db2_u(ctx), ST = db2_settle(ctx), i;
    var n = 2 + ((d.n | 0) % 2), th = jzClamp(jzU(ctx) * 0.034, 24 * u, 44 * u), tw = th * 1.8, rowH = th * 1.7, fs = th * 0.58;
    var labs = DB2_TOGGLE[(d.v | 0) % DB2_TOGGLE.length], Ls = [], lw0 = 0;
    for (i = 0; i < n; i++) { Ls.push(db2_label(ctx, labs[i], 0, 0, { size: fs, font: jzBodyF(ctx), color: sc.fg, track: 0.12 })); lw0 = Math.max(lw0, db2_textW(Ls[i])); }
    var w = lw0 + 16 * u + tw, h = n * rowH, sp = db2_spot(ctx, bb, w, h, d, 24 * u), a = sp.ok ? 1 : 0.35, ac = db2_acc(sc);
    var knobC = jzLum(ac) > 0.6 ? '#000000' : sc.bg;
    var S = jzShapeLayer(ctx, 'toggles', 0, 0);
    for (i = 0; i < n; i++) {
        var y = sp.y + i * rowH + (rowH - th) / 2, x = sp.x + w - tw, Q = 'var q=oc((t-' + jzN(i * 0.08) + ')/0.3),on=ob(cl((t-' + jzN(0.45 + i * 0.32) + ')/0.25),1.6);';
        var g = jzGrp(S, 'switch ' + (i + 1));
        var gk = db2_sub(g, 'knob'), fk = (jzAddEllipse(gk, th * 0.76, th * 0.76), jzAddFill(gk, sc.fg));
        jzSetExpr(fk.property('ADBE Vector Fill Color'), db2_H(ctx, ST) + Q + 'cl(on)>0.5?' + db2_cx(knobC) + ':' + db2_cx(sc.fg));
        db2_gx(ctx, gk, 'ADBE Vector Position', ST, Q + '[' + jzN(x + th / 2) + '+' + jzN(tw - th) + '*on,' + jzN(y + th / 2) + ']');
        db2_gx(ctx, gk, 'ADBE Vector Scale', ST, Q + '[100*q,100*q]');
        var gs = db2_sub(g, 'track'); jzAddRect(gs, tw, th, th / 2, x + tw / 2, y + th / 2);
        var sk = db2_st(gs, sc.sub, Math.max(1, 1.2 * u));
        jzSetExpr(sk.property('ADBE Vector Stroke Color'), db2_H(ctx, ST) + Q + 'cl(on)>0.5?' + db2_cx(ac) + ':' + db2_cx(sc.sub));
        var fl = jzAddFill(gs, ac);
        jzSetExpr(fl.property('ADBE Vector Fill Opacity'), db2_H(ctx, ST) + Q + '100*cl(on)');
        db2_gop(ctx, g, ST, 'q', Q);
        if (i < n - 1) {
            var gl = jzGrp(S, 'rule ' + (i + 1)), yl = sp.y + (i + 1) * rowH;
            jzAddPath(gl, [[sp.x, yl], [sp.x + w, yl]], false); db2_trim(ctx, gl, ST, '100*q', null, null, Q); db2_st(gl, sc.sub, Math.max(1, 0.8 * u), 30);
        }
        jzXf(Ls[i], 'ADBE Position').setValue([sp.x, y + th / 2]);
        jzXf(Ls[i], 'ADBE Opacity').setValue(a * 100); db2_op(ctx, Ls[i], ST, 'oc((t-' + jzN(i * 0.08) + ')/0.3)');
    }
    jzXf(S, 'ADBE Opacity').setValue(a * 100); db2_op(ctx, S, ST);
});

/* ---- notifBell \u2014 \u901A\u77E5: a line-art bell that rings each time its red badge counts up */
db2_reg('notifBell', false, function (ctx, bb, d) {
    var sc = ctx.sc, u = db2_u(ctx), ST = db2_settle(ctx);
    var S0 = jzClamp(jzU(ctx) * 0.075, 48 * u, 92 * u), fs = db2_fs(ctx) * 0.95;
    var sp = db2_spot(ctx, bb, S0 * 1.9, S0 * 1.7 + fs * 1.8, d, 22 * u), a = sp.ok ? 1 : 0.35, ac = db2_acc(sc);
    var cx = sp.cx - S0 * 0.15, cy = sp.y + S0 * 0.95, MX = 3 + (db2_h(d.seed, 2) % 7);
    var CNT = 'var k=Math.max(0,Math.floor((t-0.35)/0.5)+1),cnt=Math.min(' + MX + ',k),since=t-(0.35+(Math.min(k,' + MX + ')-1)*0.5),ring=cnt>0?Math.exp(-since*5)*Math.sin(since*30)*16:0,bump=1+0.35*Math.exp(-Math.max(0,since)*9);';
    var S = jzShapeLayer(ctx, 'bell', cx, cy), gO = jzGrp(S, 'swing');
    jzGX(gO).property('ADBE Vector Anchor').setValue([0, -S0 / 2]); jzGX(gO).property('ADBE Vector Position').setValue([0, -S0 / 2]);
    db2_gx(ctx, gO, 'ADBE Vector Rotation', ST, CNT + 'ring');
    var gI = db2_sub(gO, 'bell');
    db2_gx(ctx, gI, 'ADBE Vector Scale', ST, 'var e=100*ob(cl(t/0.4),1.6);[e,e]');
    var blw = Math.max(1.4, 2 * u);
    var gc = db2_sub(gI, 'clapper'), gcp = db2_sub(gc, 'arc'); jzAddPath(gcp, db2_arc(0, 0.44 * S0, 0.1 * S0, 0, 180, 12), false);
    db2_gx(ctx, gcp, 'ADBE Vector Position', ST, CNT + '[Math.sin(ring*2*Math.PI/180)*' + jzN(S0 * 0.08) + ',0]');
    db2_st(gc, sc.fg, blw, 100, 2);
    var gr = db2_sub(gI, 'loop'); jzAddEllipse(gr, 0.12 * S0, 0.12 * S0, 0, -0.52 * S0); db2_st(gr, sc.fg, Math.max(1.2, 1.6 * u));
    var body = [[-0.44, 0.36]].concat(db2_bez([-0.34, 0.26], [-0.38, -0.12], [-0.3, -0.45], [0, -0.47], 12), db2_bez([0, -0.47], [0.3, -0.45], [0.38, -0.12], [0.34, 0.26], 12), [[0.44, 0.36]]);
    var gb = db2_sub(gI, 'body'); jzAddPath(gb, db2_scl(body, S0), true); db2_st(gb, sc.fg, blw, 100, 2, 2);
    jzXf(S, 'ADBE Opacity').setValue(a * 100); db2_op(ctx, S, ST);
    // badge + count
    var bx = cx + S0 * 0.36, by = cy - S0 * 0.42, br = S0 * 0.26;
    var B = jzShapeLayer(ctx, 'bell badge', bx, by);
    var g2 = jzGrp(B, 'rim'); jzAddEllipse(g2, br * 2 + 4 * u, br * 2 + 4 * u); db2_st(g2, sc.bg, 2 * u);
    var g1 = jzGrp(B, 'badge'); jzAddEllipse(g1, br * 2, br * 2); jzAddFill(g1, ac);
    db2_lx(ctx, B, 'ADBE Scale', ST, CNT + '[100*bump,100*bump]');
    jzXf(B, 'ADBE Opacity').setValue(a * 100); db2_op(ctx, B, ST, 'cnt>0?1:0', CNT);
    var TB = jzText(ctx, '1', { font: db2_dispF(ctx), size: br * 1.15, color: jzLum(ac) > 0.6 ? '#000000' : sc.bg, x: bx, y: by, opacity: a });
    db2_srcText(ctx, TB, ST, CNT + 'cnt>=9?"9+":String(cnt)');
    db2_lx(ctx, TB, 'ADBE Scale', ST, CNT + '[value[0]*bump,value[1]*bump]');
    db2_op(ctx, TB, ST, 'cnt>0?1:0', CNT);
    var TL = db2_label(ctx, '\u901A\u77E5 00\u4EF6', cx, cy + S0 * 0.72 + fs * 0.8, { size: fs, font: jzBodyF(ctx), align: 'center', track: 0.1, alpha: a });
    db2_srcText(ctx, TL, ST, DB2_PAD + CNT + '"\u901A\u77E5 "+p2(cnt)+"\u4EF6"');
    db2_op(ctx, TL, ST, 'oe((t-0.3)/0.3)');
});

/* ---- likeCounter \u2014 \u3044\u3044\u306D: a heart button that fills with a burst when "liked", beside a rolling like counter */
function db2_heart(cx, cy, s) { var o = [], i; for (i = 0; i < 36; i++) { var t = i / 36 * Math.PI * 2, x = 16 * Math.pow(Math.sin(t), 3), y = 13 * Math.cos(t) - 5 * Math.cos(2 * t) - 2 * Math.cos(3 * t) - Math.cos(4 * t); o.push([cx + x * s / 17, cy - y * s / 17]); } return o; }
db2_reg('likeCounter', false, function (ctx, bb, d) {
    var sc = ctx.sc, u = db2_u(ctx), ST = db2_settle(ctx), i;
    var S0 = jzClamp(jzU(ctx) * 0.042, 28 * u, 52 * u), fs = S0 * 0.85, font = jzMonoF(ctx), ac = db2_acc(sc);
    var base = 1000 + db2_h(d.seed, 5) % 90000, STEP = 1 + db2_h(d.seed, 6) % 3, tLike = 0.45;
    function inc(t) { return t < tLike ? 0 : 1 + Math.floor(Math.max(0, t - tLike - 0.4) / 0.38) * STEP; }
    function fmt(n) { var s = String(Math.floor(n)), o = ''; while (s.length > 3) { o = ',' + s.substr(s.length - 3) + o; s = s.substr(0, s.length - 3); } return s + o; }
    var LK = 'function fm(n){var s=String(Math.floor(n)),o="";while(s.length>3){o=","+s.substr(s.length-3)+o;s=s.substr(0,s.length-3);}return s+o;}' +
        'function inc(x){return x<' + tLike + '?0:1+Math.floor(Math.max(0,x-' + (tLike + 0.4) + ')/0.38)*' + STEP + ';}' +
        'var cur=' + base + '+inc(t),prev=' + base + '+inc(Math.max(0,t-0.16)),roll=cur!=prev,f=oc((((t-' + (tLike + 0.4) + ')%0.38+0.38)%0.38)/0.16),liked=cl((t-' + tLike + ')/0.3);';
    var TC = db2_label(ctx, fmt(base + inc(ctx.cut.dur)), 0, 0, { size: fs, font: font, color: sc.fg, track: 0.04 });
    var w = S0 * 2.4 + db2_textW(TC) + 10 * u, h = S0 * 2.4, sp = db2_spot(ctx, bb, w, h, d, 20 * u), a = sp.ok ? 1 : 0.35;
    var hx = sp.x + S0 * 1.2, hy = sp.cy, lx = hx + S0 * 1.25, ly = hy;
    // heart + burst
    var S = jzShapeLayer(ctx, 'like', 0, 0);
    var gd = jzGrp(S, 'burst dots'), gdd = db2_sub(gd, 'dot'), ed = jzAddEllipse(gdd, 4.8 * u, 4.8 * u);
    jzSetExpr(ed.property('ADBE Vector Ellipse Position'), db2_H(ctx, ST) + LK + '[' + jzN(S0) + '*(0.9+0.7*oc(liked)),0]');
    jzSetExpr(ed.property('ADBE Vector Ellipse Size'), db2_H(ctx, ST) + LK + 'var r=' + jzN(4.8 * u) + '*(1-liked);[r,r]');
    db2_rep(gd, 7, 0, 0, 360 / 7); jzAddFill(gd, ac);
    jzGX(gd).property('ADBE Vector Position').setValue([hx, hy]); jzGX(gd).property('ADBE Vector Rotation').setValue(-90);
    db2_gop(ctx, gd, ST, '(liked>0&&liked<1)?1:0', LK);
    var gr = jzGrp(S, 'burst ring'), er = jzAddEllipse(gr, S0, S0, hx, hy);
    jzSetExpr(er.property('ADBE Vector Ellipse Size'), db2_H(ctx, ST) + LK + 'var r=' + jzN(2 * S0) + '*(0.6+0.8*liked);[r,r]');
    var sr = db2_st(gr, ac, 2 * u);
    jzSetExpr(sr.property('ADBE Vector Stroke Width'), db2_H(ctx, ST) + LK + jzN(2 * u) + '*Math.max(0.001,1-liked)');
    db2_gop(ctx, gr, ST, '(liked>0&&liked<1)?1-liked:0', LK);
    var gh = jzGrp(S, 'heart'); jzAddPath(gh, db2_heart(hx, hy + S0 * 0.05, S0), true);
    var sh = db2_st(gh, sc.fg, Math.max(1.4, 2 * u), 100, 1, 2);
    jzSetExpr(sh.property('ADBE Vector Stroke Color'), db2_H(ctx, ST) + LK + 'liked>0?' + db2_cx(ac) + ':' + db2_cx(sc.fg));
    var fh = jzAddFill(gh, ac);
    jzSetExpr(fh.property('ADBE Vector Fill Opacity'), db2_H(ctx, ST) + LK + 'liked>0?100:0');
    db2_gpos(gh, hx, hy + S0 * 0.05);
    db2_gx(ctx, gh, 'ADBE Vector Scale', ST, LK + 'var p=liked>0?1+0.35*Math.sin(Math.PI*cl(liked*1.4)):ob(cl(t/0.3),1.6);[100*p,100*p]');
    jzXf(S, 'ADBE Opacity').setValue(a * 100); db2_op(ctx, S, ST);
    // rolling counter: the old value slides up and out as the new one slides in (clipped to its row by a mask)
    var TP = db2_label(ctx, fmt(base), lx, ly, { size: fs, font: font, color: sc.fg, track: 0.04 });
    jzXf(TC, 'ADBE Position').setValue([lx, ly]); TC.moveToBeginning();
    var CL = [[TC, 'cur', [0, fs * 1.2], 'roll?100*(1-f):0', 'roll?f:oe((t-0.1)/0.3)'], [TP, 'prev', [0, -fs * 1.2], 'roll?100*f:0', 'roll?1-f:0']];
    for (i = 0; i < 2; i++) {
        var L = CL[i][0], an = jzXf(L, 'ADBE Anchor Point').value;
        db2_srcText(ctx, L, ST, LK + 'fm(' + CL[i][1] + ')');
        jzAnimator(L, 'JZ Roll', [['ADBE Text Position 3D', [CL[i][2][0], CL[i][2][1], 0]]], db2_H(ctx, ST) + LK + CL[i][3]);
        if (i === 0) db2_txtCol(ctx, L, ac, ST, LK + 'liked>0?100:0');
        jzMaskRect(L, an[0] - 2 * u, an[1] - fs * 0.75, an[0] - 2 * u + w, an[1] + fs * 0.75);
        jzXf(L, 'ADBE Opacity').setValue(a * 100); db2_op(ctx, L, ST, CL[i][4], LK);
    }
});

/* ---- mediaControls \u2014 \u518D\u751F\u30DC\u30BF\u30F3: transport controls (prev / play / next); play turns into pause on a click, with a running time readout.
   The play \u2192 pause morph is a quick cross-scale of the triangle into the two bars. */
db2_reg('mediaControls', false, function (ctx, bb, d) {
    var sc = ctx.sc, u = db2_u(ctx), ST = db2_settle(ctx), k;
    var R = jzClamp(jzU(ctx) * 0.04, 28 * u, 52 * u), fs = db2_fs(ctx) * 0.9, w = R * 6.4, h = R * 2.2 + fs * 1.8;
    var sp = db2_band(ctx, bb, w, h, d, 34 * u), a = sp.ok ? 1 : 0.35, ac = db2_acc(sc), cx = sp.cx, cy = sp.y + R * 1.1, lw = Math.max(1.2, 1.6 * u);
    var MM = 'var e=ob(cl(t/0.35),1.6),m=ioc((t-0.55)/0.22);', s2 = R * 0.84;
    var S = jzShapeLayer(ctx, 'media controls', cx, cy);
    for (k = 0; k < 2; k++) {                          // prev / next
        var dir = k ? 1 : -1, x = dir * R * 2.3, ss = R * 0.36, g = jzGrp(S, k ? 'next' : 'prev');
        jzAddPath(g, [[x - dir * ss * 0.6, -ss], [x + dir * ss * 0.9, 0], [x - dir * ss * 0.6, ss]], true);
        jzAddRect(g, 2.4 * u, ss * 2, 0, x + dir * ss * 0.9 - (dir > 0 ? 0 : 2.4 * u) + 1.2 * u, 0);
        jzAddFill(g, sc.fg); db2_gpos(g, x, 0);
        db2_gx(ctx, g, 'ADBE Vector Scale', ST, 'var q=100*ob(cl((t-' + jzN(0.1 + k * 0.06) + ')/0.3),1.6);[q,q]');
    }
    var gp = jzGrp(S, 'pause');
    jzAddRect(gp, 0.28 * s2, s2, 0, -0.26 * s2, 0); jzAddRect(gp, 0.28 * s2, s2, 0, 0.26 * s2, 0); jzAddFill(gp, sc.fg);
    db2_gx(ctx, gp, 'ADBE Vector Scale', ST, MM + '[100*e*(0.5+0.5*m),100*e]');
    db2_gop(ctx, gp, ST, 'm', MM);
    var gy = jzGrp(S, 'play');
    jzAddPath(gy, [[-0.35 * s2, -0.5 * s2], [0.5 * s2, 0], [-0.35 * s2, 0.5 * s2]], true); jzAddFill(gy, sc.fg);
    db2_gx(ctx, gy, 'ADBE Vector Position', ST, MM + '[' + jzN(R * 0.42 * 0.12) + '*e*(1-m),0]');
    db2_gx(ctx, gy, 'ADBE Vector Scale', ST, MM + '[100*e*(1-0.5*m),100*e]');
    db2_gop(ctx, gy, ST, '1-m', MM);
    var gq = jzGrp(S, 'click ripple'), eq = jzAddEllipse(gq, R * 2, R * 2), RQ = 'var q=cl((t-0.55)/0.45);';
    jzSetExpr(eq.property('ADBE Vector Ellipse Size'), db2_H(ctx, ST) + RQ + 'var r=' + jzN(R * 2) + '*(1+0.5*oc(q));[r,r]');
    var sq = db2_st(gq, ac, 2 * u);
    jzSetExpr(sq.property('ADBE Vector Stroke Width'), db2_H(ctx, ST) + RQ + jzN(2 * u) + '*Math.max(0.001,1-q)');
    db2_gop(ctx, gq, ST, 't>0.55?1-q:0', RQ);
    var gc = jzGrp(S, 'ring'); jzAddEllipse(gc, R * 2, R * 2); db2_st(gc, sc.fg, lw);
    db2_gx(ctx, gc, 'ADBE Vector Scale', ST, 'var e=100*ob(cl(t/0.35),1.6);[e,e]');
    jzXf(S, 'ADBE Opacity').setValue(a * 100); db2_op(ctx, S, ST);
    var tot = (ctx.plan && ctx.plan.duration) ? ctx.plan.duration : 200;
    var T = db2_label(ctx, '0:00 / 0:00', cx, cy + R * 1.1 + fs * 1.1, { size: fs, align: 'center', track: 0.1, alpha: a });
    db2_srcText(ctx, T, ST, DB2_PAD + 'function ms(x){return Math.floor(x/60)+":"+p2(x%60);}ms(Math.max(0,' + jzN(ctx.cut.start || 0) + '+time))+" / ' + Math.floor(tot / 60) + ':' + db2_pad(tot % 60) + '"');
    db2_op(ctx, T, ST, 'oe((t-0.2)/0.3)');
});

/* ---- volumeBars \u2014 \u97F3\u91CF: a speaker glyph with an ascending stepped volume meter that follows the music (the browser's fallback level:
   smooth noise, as AE has no audio energy here) */
db2_reg('volumeBars', false, function (ctx, bb, d) {
    var sc = ctx.sc, u = db2_u(ctx), ST = db2_settle(ctx), i;
    var S0 = jzClamp(jzU(ctx) * 0.042, 28 * u, 52 * u), nB = 8, bwid = S0 * 0.3, gap = S0 * 0.18, fs = db2_fs(ctx) * 0.9;
    var w = S0 * 1.9 + nB * (bwid + gap), h = S0 * 1.8 + fs * 1.6, sp = db2_spot(ctx, bb, w, h, d, 22 * u), a = sp.ok ? 1 : 0.35, ac = db2_acc(sc);
    var x0 = sp.x, yb = sp.y + S0 * 1.6, lw = Math.max(1.2, 1.6 * u), cy = yb - S0 * 0.55;
    var LV = 'var en=0.55+0.4*nz(Math.max(0,t)*3,' + db2_ntab(d.seed, 0, (ctx.cut.dur + 1) * 3) + '),lvl=Math.round(cl(en)*' + nB + '*oc((t-0.15)/0.5));';
    var S = jzShapeLayer(ctx, 'volume', 0, 0);
    for (i = 0; i < nB; i++) {
        var bh0 = S0 * (0.3 + 1.3 * (i + 1) / nB), x = x0 + S0 * 1.5 + i * (bwid + gap), g = jzGrp(S, 'bar ' + (i + 1));
        jzAddRect(g, bwid, bh0, 0, x + bwid / 2, yb - bh0 / 2);
        var fl = jzAddFill(g, sc.sub, 30);
        jzSetExpr(fl.property('ADBE Vector Fill Color'), db2_H(ctx, ST) + LV + i + '<lvl?(' + i + '==lvl-1?' + db2_cx(ac) + ':' + db2_cx(sc.fg) + '):' + db2_cx(sc.sub));
        jzSetExpr(fl.property('ADBE Vector Fill Opacity'), db2_H(ctx, ST) + LV + i + '<lvl?(' + i + '==lvl-1?100:90):30');
        db2_gpos(g, x + bwid / 2, yb);
        db2_gx(ctx, g, 'ADBE Vector Scale', ST, '[100,100*ob(cl((t-' + jzN(0.05 + i * 0.03) + ')/0.25),1.6)]');
    }
    for (i = 0; i < 2; i++) {
        var gw = jzGrp(S, 'wave ' + (i + 1)); jzAddPath(gw, db2_arc(x0 + S0 * 0.62, cy, S0 * (i ? 0.62 : 0.35), -45, 45, 16), false);
        db2_st(gw, sc.fg, lw, 100, 2); db2_gop(ctx, gw, ST, 'lvl>' + (i ? nB / 2 : 0) + '?1:0', LV);
    }
    var gs = jzGrp(S, 'speaker');
    jzAddPath(gs, [[x0, cy - S0 * 0.2], [x0 + S0 * 0.25, cy - S0 * 0.2], [x0 + S0 * 0.6, cy - S0 * 0.48], [x0 + S0 * 0.6, cy + S0 * 0.48], [x0 + S0 * 0.25, cy + S0 * 0.2], [x0, cy + S0 * 0.2]], true);
    jzAddFill(gs, sc.fg); db2_gpos(gs, x0, cy);
    db2_gx(ctx, gs, 'ADBE Vector Scale', ST, '[100,100*ob(cl(t/0.3),1.6)]');
    jzXf(S, 'ADBE Opacity').setValue(a * 100); db2_op(ctx, S, ST);
    var T = db2_label(ctx, 'VOL 00', x0, yb + fs, { size: fs, track: 0.16, alpha: a });
    db2_srcText(ctx, T, ST, DB2_PAD + LV + '"VOL "+p2(lvl)');
    db2_op(ctx, T, ST, 'oe((t-0.2)/0.3)');
});

/* ---- musicNotes \u2014 \u97F3\u7B26: eighth / beamed / quarter notes floating up and swaying out of the free space beside the lyric.
   One group per note slot; each rise cycle's start point, size and note type are pre-computed. */
db2_reg('musicNotes', false, function (ctx, bb, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, u = db2_u(ctx), ST = db2_settle(ctx), dur = ctx.cut.dur, i, cy;
    var N = Math.min(10, 5 + (d.n | 0) * 2), S0 = jzClamp(jzU(ctx) * 0.036, 24 * u, 44 * u), pad = 26 * u + db2_bh(bb) * 0.15;
    var X0 = bb.x0 - pad, X1 = bb.x1 + pad, Y0 = bb.y0 - pad, Y1 = bb.y1 + pad, per = 2 * (X1 - X0 + Y1 - Y0), bcx = (bb.x0 + bb.x1) / 2;
    var cols = [sc.fg, db2_acc(sc), db2_acc2(sc)], s = S0, lw = Math.max(1.2, s * 0.07);
    var S = jzShapeLayer(ctx, 'music notes', 0, 0);
    function head(g, hx) { var gh = db2_sub(g, 'head'); jzAddEllipse(gh, s * 0.6, s * 0.42, hx, 0); db2_gpos(gh, hx, 0); jzGX(gh).property('ADBE Vector Rotation').setValue(-22); return gh; }
    for (i = 0; i < N; i++) {
        var T = 2.2 + db2_r(d.seed, i, 1) * 1.2, off = db2_r(d.seed, i, 2) * 0.8, nc = Math.ceil((dur + 1) / T) + 1, Q = [];
        for (cy = 0; cy < nc; cy++) {
            var p = db2_perim(X0, Y0, X1, Y1, ((i + db2_r(d.seed, i, cy, 3)) / N) * per);
            Q.push(db2_arr([p[0], p[1], p[0] - bcx > 0 ? 1 : (p[0] - bcx < 0 ? -1 : 1), 0.75 + 0.4 * db2_r(d.seed, i, cy, 4), (i + db2_h(d.seed, cy)) % 3]));
        }
        var HD = 'var tt=t-' + jzN(off) + ',cy=Math.max(0,Math.floor(tt/' + jzN(T) + ')),tau=(tt-cy*' + jzN(T) + ')/' + jzN(T) + ',QQ=[' + Q.join(',') + '],q=QQ[Math.min(cy,QQ.length-1)];' +
            'var x=Math.max(' + jzN(s) + ',Math.min(' + jzN(W - s) + ',q[0]+q[2]*tau*' + jzN(40 * u) + '+Math.sin(tau*2*Math.PI+' + i + ')*' + jzN(12 * u) + ')),' +
            'y=Math.max(' + jzN(s * 1.5) + ',Math.min(' + jzN(H - s) + ',q[1]-tau*' + jzN(90 * u) + ')),al=co(x,y,' + jzN(s * 1.1) + ',' + jzN(20 * u) + ')*Math.sin(Math.PI*tau)*1.4;';
        var col = cols[i % 3], g = jzGrp(S, 'note ' + (i + 1)), k;
        db2_gx(ctx, g, 'ADBE Vector Position', ST, HD + '[x,y]');
        db2_gx(ctx, g, 'ADBE Vector Rotation', ST, HD + 'Math.sin(tau*5+' + i + ')*14-6');
        db2_gx(ctx, g, 'ADBE Vector Scale', ST, HD + 'var z=100*q[3]*ob(cl(tau*5),1.8);[z,z]');
        db2_gop(ctx, g, ST, '(tt>=0&&al>0.02)?Math.min(1,al):0', HD);
        for (k = 0; k < 3; k++) {
            var gt = db2_sub(g, ['eighth', 'quarter', 'beamed'][k]);
            if (k === 2) {
                var gB = db2_sub(gt, 'beam'); jzAddPath(gB, [[-s * 0.09 - lw / 2, -s * 1.05], [s * 0.71 + lw / 2, -s * 1.15], [s * 0.71 + lw / 2, -s * 0.97], [-s * 0.09 - lw / 2, -s * 0.87]], true); jzAddFill(gB, col);
                var gS = db2_sub(gt, 'stems'); jzAddPath(gS, [[-s * 0.09, -s * 0.05], [-s * 0.09, -s * 1.05]], false); jzAddPath(gS, [[s * 0.71, -s * 0.05], [s * 0.71, -s * 1.15]], false); db2_st(gS, col, lw);
                var gH = db2_sub(gt, 'heads'); head(gH, -s * 0.35); head(gH, s * 0.45); jzAddFill(gH, col);
            } else {
                if (k === 0) { var gf = db2_sub(gt, 'flag'); jzAddPath(gf, db2_bez([s * 0.26, -s * 1.05], [s * 0.3, -s * 0.8], [s * 0.72, -s * 0.72], [s * 0.55, -s * 0.35], 10), false); db2_st(gf, col, lw * 1.3, 100, 2); }
                var gs2 = db2_sub(gt, 'stem'); jzAddPath(gs2, [[s * 0.26, -s * 0.05], [s * 0.26, -s * 1.05]], false); db2_st(gs2, col, lw);
                var gh2 = db2_sub(gt, 'heads'); head(gh2, 0); jzAddFill(gh2, col);
            }
            db2_gop(ctx, gt, ST, 'q[4]==' + k + '?1:0', HD);
        }
    }
    db2_op(ctx, S, ST);
});

// ================================================================ pack enter part 1 (AE port of src/11p_enter.js)
// Worked example: riseMask. Per-glyph motion lives in text animators with Expression Selectors:
// the selector's amount (percent, per glyph via textIndex / textTotal) scales the animator's property value.

/* ---- riseMask \u2014 \u4E0B\u304B\u3089\u51FA\u73FE: glyphs rise into place from under the line, left to right */
jzReg('enter', 'riseMask', { apply: function (m) {
    var L = m.L, r = jzRect(L), h = r.height;
    // stg(p, ordLR(i, n), 0.45) with a quintic ease: each glyph starts a little after the previous one
    var q = m.HD + 'var n=textTotal,o=n>1?(textIndex-1)/(n-1):0,q=cl((P-0.45*o)/0.55);';
    jzAnimator(L, 'JZ In Rise', [['ADBE Text Position 3D', [0, h * 1.15, 0]]], q + '(q<=0?1:Math.pow(1-q,5))*100');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + 'q<=0?100:0');
    // the "mask": clip everything below the text block while glyphs come up (opens once the entrance is over)
    var mk = jzMaskRect(L, r.left - h, r.top - h * 2, r.left + r.width + h, r.top + r.height + h * 0.08);
    mk.property('ADBE Mask Offset').expression = m.HD + 'time>DL+IN+0.05?1e4:0';   // mask expansion: gone once the entrance is over
} });

// ================================================================ shared helpers of this pack (prefix en1_)
// easing not in JZ_FNS: o2 outQuad, o4 outQuart, o5 outQuint, io3 inOutCubic, io4 inOutQuart, sm smoothstep(a, b, x)
var EN1_FNS = 'function o2(x){x=cl(x);return 1-(1-x)*(1-x);}function o4(x){x=cl(x);return 1-Math.pow(1-x,4);}function o5(x){x=cl(x);return 1-Math.pow(1-x,5);}' +
    'function io3(x){x=cl(x);return x<0.5?4*x*x*x:1-Math.pow(-2*x+2,3)/2;}function io4(x){x=cl(x);return x<0.5?8*x*x*x*x:1-Math.pow(-2*x+2,4)/2;}' +
    'function sm(a,b,x){x=cl((x-a)/(b-a));return x*x*(3-2*x);}\n';
// entrance timing of this layer (same numbers as jzHead)
function en1_T(m) { var c = m.c; return { IN: Math.max(0.02, c.inDur || 0.3), DL: (m.o.mi || 0) * (c.stagger || 0.04) }; }
// compact header for masks / helper layers: IN, DL, SZ, P + easing
function en1_head(m) { var t = en1_T(m); return 'var IN=' + jzN(t.IN) + ',DL=' + jzN(t.DL) + ',SZ=' + jzN(m.size) + ';' + JZ_FNS + EN1_FNS + 'var P=cl((time-DL)/IN);\n'; }
// per-glyph stagger inside an Expression Selector: browser stg(p, ordLR | ordC, spread) -> q
function en1_q(kind, spread) {
    var o = kind === 'C' ? 'n>1?Math.abs(textIndex-1-(n-1)/2)/((n-1)/2):0' : 'n>1?(textIndex-1)/(n-1):0';
    return 'var n=textTotal,o=' + o + ',q=cl((P-' + jzN(spread) + '*o)/' + jzN(1 - spread) + ');';
}
function en1_arr(a) { var s = []; for (var i = 0; i < a.length; i++) s.push(jzN(a[i])); return '[' + s.join(',') + ']'; }
function en1_col(hex) { var c = jzHex(hex); return '[' + jzN(c[0]) + ',' + jzN(c[1]) + ',' + jzN(c[2]) + ',1]'; }
// the browser's numeric hash J.h, so seeded directions (dirOf) match the web version
function en1_h(a, b, c, d, e) {
    var h = 0x9e3779b9 ^ (a | 0);
    h = jzImul(h ^ (h >>> 16), 0x85ebca6b);
    h = (h + jzImul((b | 0) + 0x632be5ab, 0xc2b2ae35)) | 0;
    h = jzImul(h ^ (h >>> 13), 0xc2b2ae35);
    h = (h + jzImul((c | 0) + 0x5bd1e995, 0x27d4eb2f)) | 0;
    h = jzImul(h ^ (h >>> 15), 0x165667b1);
    h = (h + jzImul((d | 0) + 0x1b873593, 0x85ebca6b)) | 0;
    h = jzImul(h ^ (h >>> 16), 0x27d4eb2f);
    h = (h + jzImul((e | 0) + 0x68e31da4, 0x9e3779b1)) | 0;
    h ^= h >>> 15; h = jzImul(h, 0x2c1b3c6d); h ^= h >>> 12; h = jzImul(h, 0x297a2d39); h ^= h >>> 15;
    return h >>> 0;
}
function en1_dir(m, salt) { return en1_h((m.c.seed || 0) | 0, salt, 5) / 4294967296 < 0.5 ? -1 : 1; }
function en1_isSp(ch) { return /^[\s\u3000]$/.test(ch); }
function en1_meas(T, L, s, just) {
    var d = L.property('ADBE Text Properties').property('ADBE Text Document').value;
    d.text = s; d.justification = just;
    T.property('ADBE Text Properties').property('ADBE Text Document').setValue(d);
    var q = T.sourceRectAtTime(0, false);
    return { l: q.left, r: q.left + q.width };
}
// Geometry of the lyric in the text layer's own space (call BEFORE adding animators).
//   G.fs font size, G.lead line pitch, G.rect source rect, G.vert (one glyph per line), G.lines[{cy, x0, x1, chars}],
//   G.g[] one entry per AE character (textIndex order, spaces included): {i, li, sp, k (index among non-spaces), x0, x1, cx, cy, w, h}
//   G.W per-glyph widths, G.N = textTotal, G.arranged (layout animators / text on a path: glyphs are not at their plain places),
//   G.masked (the layout masks this layer already). measure = true measures every glyph box with a temporary text layer.
function en1_geo(m, measure) {
    var L = m.L, TP = L.property('ADBE Text Properties'), td = TP.property('ADBE Text Document').value, i, j;
    var fs = td.fontSize, lead = (!td.autoLeading && td.leading > 0) ? td.leading : fs * 1.2;
    var lines = String(td.text).split(/\r\n|\r|\n|\u0003/), nL = lines.length, r = jzRect(L);
    var G = { fs: fs, lead: lead, nL: nL, rect: r, g: [], W: [], lines: [], N: 0, vert: nL > 1, arranged: false, masked: false, K: 0 };
    try { G.arranged = TP.property('ADBE Text Animators').numProperties > 0; } catch (e0) {}
    try { if (TP.property('ADBE Text Path Options').property('ADBE Text Path').value > 0) G.arranged = true; } catch (e1) {}
    try { G.masked = L.property('ADBE Mask Parade').numProperties > 0; } catch (e2) {}
    var inkH = r.height - (nL - 1) * lead; if (!(inkH > fs * 0.3 && inkH < fs * 2)) inkH = fs * 0.9;
    var cy0 = r.top + inkH / 2, trk = (td.tracking || 0) / 1000 * fs;
    for (i = 0; i < nL; i++) { var cs0 = jzChars(lines[i]); if (cs0.length > 1) G.vert = false; G.lines.push({ chars: cs0, cy: cy0 + i * lead, x0: r.left, x1: r.left + r.width }); }
    var T = null;
    if (measure && !G.arranged && jzCount(td.text) <= 60) { try { T = m.ctx.comp.layers.addText('x'); } catch (e3) { T = null; } }
    var k = 0, LEFT = ParagraphJustification.LEFT_JUSTIFY;
    try {
        for (i = 0; i < nL; i++) {
            var ln = G.lines[i], cs = ln.chars, n = cs.length;
            if (!n) continue;
            var F = null, F0 = null, off = 0, prev = 0;
            if (T && !G.vert) {
                F = en1_meas(T, L, lines[i], td.justification); ln.x0 = F.l; ln.x1 = F.r;
                if (n > 1) { F0 = en1_meas(T, L, lines[i], LEFT); off = F.l - F0.l; }
                prev = F.l - trk;
            } else if (!G.vert) {      // not measured: monospace estimate centred like the block
                var wEst = r.width / Math.max(1, n), lx = r.left + (r.width - wEst * n) / 2;
                ln.x0 = lx; ln.x1 = lx + wEst * n; prev = lx - trk;
            }
            for (j = 0; j < n; j++) {
                var sp = en1_isSp(cs[j]), x0, x1;
                if (G.vert) { x0 = r.left; x1 = r.left + r.width; }
                else if (T) {
                    x0 = prev + trk;
                    x1 = (j === n - 1) ? F.r : off + en1_meas(T, L, cs.slice(0, j + 1).join(''), LEFT).r;
                    if (x1 < x0) x1 = x0;
                    prev = x1;
                } else { x0 = prev + trk; x1 = x0 + (r.width + trk) / Math.max(1, n) - trk; prev = x1; }
                var gi = { i: G.N, li: i, sp: sp, k: sp ? -1 : k, x0: x0, x1: x1, cx: (x0 + x1) / 2, cy: ln.cy, w: Math.max(1, x1 - x0), h: fs };
                if (G.vert) gi.w = fs;
                if (!sp) k++;
                G.g.push(gi); G.W.push(gi.w); G.N++;
                if (gi.w > G.K) G.K = gi.w;
            }
        }
    } catch (e4) { jzWarn('en1 geometry: ' + e4.toString()); }
    if (T) { try { T.remove(); } catch (e5) {} }
    if (!G.K) G.K = fs;
    return G;
}
// rect mask in the layer's space with optional opacity / expansion expressions
function en1_mask(Lx, x0, y0, x1, y1, opEx, exEx, name) {
    var mk = jzMaskRect(Lx, x0, y0, x1, y1);
    try { if (name) mk.name = name; } catch (e) {}
    if (opEx) jzSetExpr(mk.property('ADBE Mask Opacity'), opEx);
    if (exEx) jzSetExpr(mk.property('ADBE Mask Offset'), exEx);
    return mk;
}
// L keeps glyph set 0, a copy D shows set 1 while the entrance runs, so neighbouring glyphs / lines can each be clipped to
// their own boxes (one layer mask cannot tell glyphs apart). D is parented to L (follows every move of it), takes L's final
// text style by expression (the treatment is applied later) and is gone once the entrance is over.
function en1_twin(m, S, tag) {
    var L = m.L, D = L.duplicate(), t = en1_T(m), H = en1_head(m), i, mns = ['ADBE Position', 'ADBE Scale', 'ADBE Rotate Z', 'ADBE Opacity'];
    try { D.name = String(L.name).substr(0, 20) + ' ' + (tag || 'JZ In B'); } catch (e0) {}
    var tr = D.property('ADBE Transform Group');
    for (i = 0; i < mns.length; i++) { try { if (tr.property(mns[i]).expression) tr.property(mns[i]).expression = ''; } catch (e1) {} }
    D.parent = L;
    tr.property('ADBE Scale').setValue([100, 100]); tr.property('ADBE Rotate Z').setValue(0);
    jzSetExpr(tr.property('ADBE Position'), 'parent.transform.anchorPoint');
    jzSetExpr(tr.property('ADBE Opacity'), H + 'time>=DL&&time<DL+IN?value:0');
    try { D.property('ADBE Text Properties').property('ADBE Text Document').expression = 'parent.text.sourceText.style'; } catch (e2) {}
    try { D.outPoint = Math.max(D.inPoint + 0.05, Math.min(D.outPoint, t.DL + t.IN + 0.1)); } catch (e3) {}
    if (!S) return D;            // both layers show every glyph (they get different masks)
    var arr = en1_arr(S);
    jzAnimator(L, 'JZ In Set A', [['ADBE Text Opacity', 0]], H + 'var S=' + arr + ';S[textIndex-1]==1&&time<DL+IN?100:0');
    jzAnimator(D, 'JZ In Set B', [['ADBE Text Opacity', 0]], 'var S=' + arr + ';S[textIndex-1]==1?0:100');
    return D;
}
// helper shape layer drawn in the text layer's own space (parented to it, right above it), only during the entrance.
// Helpers the browser draws with ghost off (accent bars / rings / blocks) are marked jzNoGhost where they are made; the flipX
// tiles are the glyphs' own backs (ghosted like the lyric) and stay in the ghosts.
function en1_shape(m, name) {
    var L = m.L, S = jzShapeLayer(m.ctx, name, 0, 0), t = en1_T(m);
    try { S.moveBefore(L); } catch (e0) {}
    S.parent = L;
    var tr = S.property('ADBE Transform Group');
    tr.property('ADBE Anchor Point').setValue([0, 0]); tr.property('ADBE Position').setValue([0, 0]);
    tr.property('ADBE Scale').setValue([100, 100]); tr.property('ADBE Rotate Z').setValue(0);
    try { S.outPoint = Math.max(S.inPoint + 0.05, Math.min(S.outPoint, t.DL + t.IN + 0.1)); } catch (e1) {}
    return S;
}
// one band mask per line (glyphs move vertically). set -1 = every line, 0 / 1 = lines of that parity.
// The first mask of the lyric layer itself opens completely once the entrance is over.
function en1_bands(m, Lx, G, set, pad, first) {
    var i, n = 0, H = en1_head(m), x0 = G.rect.left - G.fs, x1 = G.rect.left + G.rect.width + G.fs;
    for (i = 0; i < G.nL; i++) {
        if (!G.lines[i].chars.length || (set >= 0 && i % 2 !== set)) continue;
        en1_mask(Lx, x0, G.lines[i].cy - pad, x1, G.lines[i].cy + pad, null, (first && !n) ? H + 'time>=DL+IN?1e4:0' : null, 'JZ In Line ' + (i + 1));
        n++;
    }
}
// one box mask per glyph (glyphs move sideways), switched on when its glyph starts (browser stg order LR, spread).
// set -1 = every glyph, 0 / 1 = glyphs of that parity (S). padX in glyph widths, padY in px.
function en1_boxes(m, Lx, G, S, set, padX, padY, spread, first) {
    var i, n = 0, H = en1_head(m);
    for (i = 0; i < G.N; i++) {
        var g = G.g[i]; if (g.sp || (set >= 0 && S[i] !== set)) continue;
        var o = G.N > 1 ? i / (G.N - 1) : 0;
        var op = H + 'cl((P-' + jzN(spread * o) + ')/' + jzN(1 - spread) + ')>0?100:0';
        en1_mask(Lx, g.x0 - padX * g.w, g.cy - padY, g.x1 + padX * g.w, g.cy + padY, op, (first && !n) ? H + 'time>=DL+IN?1e4:0' : null, 'JZ In Box ' + (i + 1));
        n++;
    }
}
// glyph sets: alternate glyphs (by position among non-spaces) or alternate lines
function en1_setGlyph(G) { var s = [], i; for (i = 0; i < G.N; i++) s.push(G.g[i].sp ? 0 : G.g[i].k % 2); return s; }
function en1_setLine(G) { var s = [], i; for (i = 0; i < G.N; i++) s.push(G.g[i].li % 2); return s; }
function en1_maxRow(G) { var b = 0, i, c; for (i = 0; i < G.nL; i++) { c = 0; for (var j = 0; j < G.lines[i].chars.length; j++) if (!en1_isSp(G.lines[i].chars[j])) c++; if (c > b) b = c; } return b; }
// helper layers (twins / tiles / bars) are skipped for very long lines, for one-glyph-per-layer layouts of long lyrics
// (decided per cut so every glyph of the cut is built the same way) and when the comp is already crowded
function en1_crowded(m, G) {
    if (G && (G.N > 60 || (G.N <= 1 && jzCount(m.c.text || '') > 14))) return true;
    try { return m.ctx.comp.numLayers > 60; } catch (e) { return false; }
}
// add a stroke to the text document (width 0, animated by a Stroke Width animator) when it has none; returns true if added
var EN1_OWNSTROKE = 'var s0=0;try{s0=text.sourceText.style.strokeWidth;}catch(err){}';   // > 0.5: a treatment gave the text its own stroke
function en1_addStroke(L) {
    var td = L.property('ADBE Text Properties').property('ADBE Text Document').value;
    if (td.applyStroke && td.strokeWidth > 0) return false;
    jzTextDoc(L, function (d) {
        d.applyStroke = true; d.strokeWidth = 0;
        try { d.strokeColor = d.applyFill ? d.fillColor : d.strokeColor; } catch (e) {}
        try { d.strokeOverFill = false; } catch (e2) {}
    });
    return true;
}

/* ---- dropMask \u2014 \u4E0A\u304B\u3089\u51FA\u73FE: glyphs drop into place from the top of their own line box, left to right, with a little overshoot */
jzReg('enter', 'dropMask', { apply: function (m) {
    var L = m.L, G = en1_geo(m, false), fs = G.fs, q = m.HD + EN1_FNS + en1_q('LR', 0.45);
    var soft = G.arranged || G.masked || (G.nL > 1 && en1_crowded(m, G));   // no clean clip possible: glyphs fade in instead of being clipped
    jzAnimator(L, 'JZ In Drop', [['ADBE Text Position 3D', [0, -fs * 1.15, 0]]], q + '(q<=0?1:1-ob(q,1.35))*100');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + (soft ? 'q<=0?100:100*(1-cl((1.1-1.15*(1-ob(q,1.35)))/0.88))' : 'q<=0?100:0'));
    if (soft) return;
    // each line is clipped to its own band; alternate lines go to a linked copy so a glyph coming down never shows over the line above
    var D = G.nL > 1 ? en1_twin(m, en1_setLine(G)) : null;
    en1_bands(m, L, G, D ? 0 : -1, fs * 0.66, true);
    if (D) en1_bands(m, D, G, 1, fs * 0.66, false);
} });

/* ---- slideL \u2014 \u5DE6\u304B\u3089\u30B9\u30E9\u30A4\u30C9: glyphs slide in from the left and fade up, staggered left to right */
jzReg('enter', 'slideL', { apply: function (m) {
    var q = m.HD + EN1_FNS + en1_q('LR', 0.5);
    jzAnimator(m.L, 'JZ In Slide', [['ADBE Text Position 3D', [-m.size * 0.85, 0, 0]]], q + '(q<=0?1:1-o5(q))*100');
    jzAnimator(m.L, 'JZ In Fade', [['ADBE Text Opacity', 0]], q + 'q<=0?100:(1-Math.pow(cl(q*1.6),1.6))*100');
} });

/* ---- slideR \u2014 \u53F3\u304B\u3089\u30B9\u30E9\u30A4\u30C9: every glyph slides in from the right inside its own box */
jzReg('enter', 'slideR', { apply: function (m) {
    var L = m.L, G = en1_geo(m, true), K = G.K * 1.25;
    var q = m.HD + EN1_FNS + en1_q('LR', 0.45) + 'var W=' + en1_arr(G.W) + ',w=W[textIndex-1]||SZ;';
    var soft = G.arranged || G.masked || G.N > 60 || (en1_maxRow(G) > 1 && en1_crowded(m, G));
    jzAnimator(L, 'JZ In Slide', [['ADBE Text Position 3D', [K, 0, 0]]], q + '(q<=0?1:1-o5(q))*1.2*w/' + jzN(K) + '*100');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + (soft ? 'q<=0?100:100*(1-cl(1.12-1.2*(1-o5(q))))' : 'q<=0?100:0'));
    if (soft) return;
    // per-glyph box masks; neighbours within a line alternate between L and a linked copy so a glyph's box never shows the one before it
    var S = en1_setGlyph(G), D = en1_maxRow(G) > 1 ? en1_twin(m, S) : null, pY = G.vert ? Math.max(G.lead, G.fs) * 0.5 : G.fs * 0.66;
    en1_boxes(m, L, G, S, D ? 0 : -1, 0.12, pY, 0.45, true);
    if (D) en1_boxes(m, D, G, S, 1, 0.12, pY, 0.45, false);
} });

/* ---- slideWhole \u2014 \u5168\u4F53\u30B9\u30E9\u30A4\u30C9: the whole line slides in along its reading direction with an overshoot */
jzReg('enter', 'slideWhole', { apply: function (m) {
    var n = 1, dir = en1_dir(m, 11), vert = false;
    try {
        var tx = String(m.L.property('ADBE Text Properties').property('ADBE Text Document').value.text), lines = tx.split(/\r\n|\r|\n/);
        n = jzCount(tx); vert = lines.length > 1; for (var i = 0; i < lines.length; i++) if (jzChars(lines[i]).length > 1) vert = false;
    } catch (e) {}
    var sc = jzXf(m.L, 'ADBE Scale').value, k = Math.abs((vert ? sc[1] : sc[0]) / 100) || 1;
    var D = m.size * k * (n <= 1 ? 1.3 : 3.2) * (0.7 + 0.6 * m.ctx.fx.motion) * dir;
    m.parts.pos.push('var e1=ob(P,1.7);d=[d[0]+' + (vert ? '0' : jzN(D) + '*(1-e1)') + ',d[1]+' + (vert ? jzN(D) + '*(1-e1)' : '0') + '];');
    m.parts.op.push('f*=cl(P*4);');
} });

/* ---- flipX \u2014 \u7E26\u8EF8\u30D5\u30EA\u30C3\u30D7: an accent tile pops up and turns over around the vertical axis to reveal each glyph */
jzReg('enter', 'flipX', { apply: function (m) {
    var L = m.L, G = en1_geo(m, true), sc = m.ctx.sc, col = jzTextColor(L), bg = sc.bg || '#000000', i;
    var back = (String(col).toUpperCase() === String(sc.accent || '').toUpperCase()) ? (sc.accent2 || sc.fg) : (sc.accent || sc.fg);
    var th = 'var pop=ob(q/0.22,1.6),th=(1-ob((q-0.22)/0.78,1.25))*Math.PI,c=Math.cos(th),sh=Math.abs(Math.sin(th));';
    var q = m.HD + EN1_FNS + en1_q('LR', 0.45) + th;
    jzAnimator(L, 'JZ In Flip', [['ADBE Text Scale 3D', [0, 100, 100]]], q + 'q<=0||q>=1||c<0?[0,0,0]:[(1-Math.max(0.04,c))*100,0,0]');
    jzAnimator(L, 'JZ In Shade', [['ADBE Text Fill Color', jzHex(bg)]], q + 'q<=0||q>=1?0:Math.min(0.5,sh*0.5)*100');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + 'q<=0||(q<1&&c<0)?100:0');
    if (G.arranged || en1_crowded(m, G)) return;
    // the tile side: one square per glyph on a helper shape layer in the text's own space
    var S = en1_shape(m, 'JZ In Tiles'), H = en1_head(m), ts = G.fs * 0.84, cB = jzHex(back), cG = jzHex(bg);
    for (i = 0; i < G.N; i++) {
        var g = G.g[i]; if (g.sp) continue;
        var o = G.N > 1 ? i / (G.N - 1) : 0, hq = H + 'var q=cl((P-' + jzN(0.45 * o) + ')/0.55);' + th;
        var grp = jzGrp(S, 'tile ' + (i + 1));
        jzAddRect(grp, ts, ts, 0);
        var f = jzAddFill(grp, back), gx = jzGX(grp);
        gx.property('ADBE Vector Position').setValue([g.cx, g.cy]);
        jzSetExpr(gx.property('ADBE Vector Scale'), hq + 'var s=1.02*pop*100;[Math.max(0.04,-c)*s,s]');
        jzSetExpr(gx.property('ADBE Vector Group Opacity'), hq + 'q>0&&q<1&&c<0?cl(q*8)*100:0');
        jzSetExpr(f.property('ADBE Vector Fill Color'), hq + 'var k=sh*0.35,a=' + en1_col(back) + ',b=' + en1_col(bg) + ';[a[0]+(b[0]-a[0])*k,a[1]+(b[1]-a[1])*k,a[2]+(b[2]-a[2])*k,1]');
    }
} });

/* ---- flipY \u2014 \u6A2A\u8EF8\u30D5\u30EA\u30C3\u30D7: three-quarter tumble around the horizontal axis (shows the mirrored back once), centre glyphs first */
jzReg('enter', 'flipY', { apply: function (m) {
    var bg = m.ctx.sc.bg || '#000000';
    var q = m.HD + EN1_FNS + en1_q('C', 0.4) + 'var th=(1-o4(q))*1.5*Math.PI,c=Math.cos(th);if(Math.abs(c)<0.04)c=c<0?-0.04:0.04;';
    jzAnimator(m.L, 'JZ In Tumble', [['ADBE Text Scale 3D', [100, -100, 100]]], q + 'q<=0||q>=1?[0,0,0]:[0,(1-c)/2*100,0]');
    jzAnimator(m.L, 'JZ In Shade', [['ADBE Text Fill Color', jzHex(bg)]], q + 'q<=0||q>=1?0:Math.min(0.75,Math.abs(Math.sin(th))*0.6+(c<0?0.2:0))*100');
    jzAnimator(m.L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + 'q<=0?100:(1-cl(q*3))*100');
} });

/* ---- domino \u2014 \u30C9\u30DF\u30CE: each glyph lies on its right side and swings upright around its bottom-right corner, left to right */
jzReg('enter', 'domino', { apply: function (m) {
    var L = m.L, G = en1_geo(m, true), K = Math.max(G.K, G.fs) * 1.6, hh = G.fs / 2;
    var q = m.HD + EN1_FNS + en1_q('LR', 0.55) + 'var W=' + en1_arr(G.W) + ',hw=(W[textIndex-1]||SZ)/2,hhv=' + jzN(hh) + ';' +
        'var th=(1-ob(q,1.5))*Math.PI/2,c=Math.cos(th),s=Math.sin(th),vx=-hw,vy=-hhv;';
    jzAnimator(L, 'JZ In Domino', [['ADBE Text Rotation', 90]], q + 'q<=0||q>=1?0:(1-ob(q,1.5))*100');
    jzAnimator(L, 'JZ In Pivot', [['ADBE Text Position 3D', [K, K, 0]]], q + 'q<=0||q>=1?[0,0,0]:[(c*vx-s*vy-vx)/' + jzN(K) + '*100,(s*vx+c*vy-vy)/' + jzN(K) + '*100,0]');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + 'q<=0?100:(1-cl(q*4))*100');
} });

/* ---- fold \u2014 \u6298\u308A\u958B\u304D: accordion \u2014 glyphs unfold vertically, alternately from their top and bottom edge */
jzReg('enter', 'fold', { apply: function (m) {
    var L = m.L, fs = m.size, bg = m.ctx.sc.bg || '#000000';
    var q = m.HD + EN1_FNS + en1_q('LR', 0.5) + 'var sy=Math.max(0.02,ob(q,1.6));';
    jzAnimator(L, 'JZ In Fold', [['ADBE Text Scale 3D', [100, 0, 100]]], q + 'q<=0||q>=1?[0,0,0]:[0,(1-sy)*100,0]');
    jzAnimator(L, 'JZ In Hinge', [['ADBE Text Position 3D', [0, fs, 0]]], q + 'q<=0||q>=1?[0,0,0]:[0,((textIndex-1)%2?1:-1)*(1-sy)*50,0]');
    jzAnimator(L, 'JZ In Shade', [['ADBE Text Fill Color', jzHex(bg)]], q + 'q<=0||q>=1?0:cl(1-sy)*60');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + 'q<=0?100:(1-cl(q*4))*100');
} });

/* ---- unroll \u2014 \u5DFB\u304D\u958B\u304D: each glyph unrolls from its leading edge like a scroll, an accent roll-bar riding the open edge */
jzReg('enter', 'unroll', { apply: function (m) {
    var L = m.L, G = en1_geo(m, true), vert = G.vert, K = Math.max(G.K, G.fs), i;
    // browser: width k = 0.3..1 (outCubic) and a clip that opens from the leading edge -> here: one squash k*open
    var ev = 'var e=oc(q),k=0.3+0.7*e,r=-0.62+1.24*e,s=k*cl(0.5+r);';
    var q = m.HD + EN1_FNS + en1_q('LR', 0.5) + ev + 'var W=' + en1_arr(G.W) + ',w=W[textIndex-1]||SZ;';
    if (vert) {
        jzAnimator(L, 'JZ In Unroll', [['ADBE Text Scale 3D', [100, 0, 100]]], q + 'q<=0||q>=1?[0,0,0]:[0,(1-s)*100,0]');
        jzAnimator(L, 'JZ In Edge', [['ADBE Text Position 3D', [0, K, 0]]], q + 'q<=0||q>=1?[0,0,0]:[0,-(1-s)*' + jzN(G.fs / 2) + '/' + jzN(K) + '*100,0]');
    } else {
        jzAnimator(L, 'JZ In Unroll', [['ADBE Text Scale 3D', [0, 100, 100]]], q + 'q<=0||q>=1?[0,0,0]:[(1-s)*100,0,0]');
        jzAnimator(L, 'JZ In Edge', [['ADBE Text Position 3D', [K, 0, 0]]], q + 'q<=0||q>=1?[0,0,0]:[-(1-s)*w/2/' + jzN(K) + '*100,0,0]');
    }
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + 'q<=0?100:0');
    if (G.arranged || en1_crowded(m, G)) return;
    // the roll bars (accent), one per glyph, at the open edge
    var S = jzNoGhost(en1_shape(m, 'JZ In Roll')), H = en1_head(m), t = Math.max(1.5 * m.u, G.fs * 0.028), acc = m.ctx.sc.accent || '#ffffff';
    for (i = 0; i < G.N; i++) {
        var g = G.g[i]; if (g.sp) continue;
        var o = G.N > 1 ? i / (G.N - 1) : 0, hq = H + 'var q=cl((P-' + jzN(0.5 * o) + ')/0.5);' + ev;
        var grp = jzGrp(S, 'roll ' + (i + 1)), gx = jzGX(grp);
        if (vert) { jzAddRect(grp, G.fs * 1.12, t, 0); jzSetExpr(gx.property('ADBE Vector Position'), hq + '[' + jzN(g.cx) + ',' + jzN(g.cy - G.fs / 2) + '+k*(0.5+r)*' + jzN(G.fs) + ']'); }
        else { jzAddRect(grp, t, G.fs * 1.12, 0); jzSetExpr(gx.property('ADBE Vector Position'), hq + '[' + jzN(g.cx - g.w / 2) + '+k*(0.5+r)*' + jzN(g.w) + ',' + jzN(g.cy) + ']'); }
        jzAddFill(grp, acc);
        jzSetExpr(gx.property('ADBE Vector Group Opacity'), hq + 'q>0&&q<1?Math.pow(1-e,0.6)*100:0');
    }
} });

/* ---- strokeDraw \u2014 \u7DDA\u753B\u304B\u3089\u5857\u308A: outlines draw in everywhere at once (growing dashes), then the fill comes up and the line thins */
jzReg('enter', 'strokeDraw', { apply: function (m) {
    var L = m.L, fs = m.size;
    var added = en1_addStroke(L);
    if (added) jzAnimator(L, 'JZ In Line', [['ADBE Text Stroke Width', Math.max(0.6, fs * 0.032)]], m.HD + EN1_FNS + EN1_OWNSTROKE + 's0>0.5?0:(1-sm(0.55,0.86,P))*100');
    jzAnimator(L, 'JZ In Fill', [['ADBE Text Fill Opacity', 0]], m.HD + EN1_FNS + '(1-sm(0.42,0.9,P))*100');
    // the outlines draw themselves everywhere at once (browser: a growing dash on every contour) -> fine diagonal blinds
    // whose gaps close with the same outQuad timing, so each stroke shows as growing dashes until it is complete
    var vb = jzEffect(L, 'ADBE Venetian Blinds', 'JZ In Draw');
    jzEP(vb, 2, 35); jzEP(vb, 3, Math.max(3, fs * 0.2)); jzEP(vb, 4, 0);
    jzEX(vb, 1, m.HD + EN1_FNS + '(1-o2(P/0.62))*100');
} });

/* ---- outlineFill \u2014 \u8F2A\u90ED\u2192\u5857\u308A: glyphs arrive as outlines (settling from 130%), then fill in and the outline melts away, left to right */
jzReg('enter', 'outlineFill', { apply: function (m) {
    var L = m.L, td = L.property('ADBE Text Properties').property('ADBE Text Document').value, fs = td.fontSize;
    var hasFill = td.applyFill !== false, q = m.HD + EN1_FNS + en1_q('LR', 0.4);
    jzAnimator(L, 'JZ In Settle', [['ADBE Text Scale 3D', [130, 130, 100]]], q + 'q<=0||q>=1?0:(1-o5(q/0.35))*100');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + 'q<=0?100:(1-cl(q*5))*100');
    if (!hasFill) return;
    if (en1_addStroke(L)) jzAnimator(L, 'JZ In Outline', [['ADBE Text Stroke Width', Math.max(1.2 * m.u, fs * 0.028)]], q + EN1_OWNSTROKE + 'q<=0||q>=1||s0>0.5?0:(1-sm(0.68,0.9,q))*100');
    jzAnimator(L, 'JZ In Fill', [['ADBE Text Fill Opacity', 0]], q + 'q>=1?0:(1-io4((q-0.3)/0.55))*100');
} });

/* ---- splitJoin \u2014 \u4E0A\u4E0B\u5408\u4F53: the top and bottom halves of every glyph slide in from opposite sides and join */
jzReg('enter', 'splitJoin', { apply: function (m) {
    var L = m.L, G = en1_geo(m, true), vert = G.vert, dir = en1_dir(m, 13), K = Math.max(G.K, G.fs) * 1.2, i;
    var q = m.HD + EN1_FNS + en1_q('LR', 0.4) + 'var W=' + en1_arr(G.W) + ',w=' + (vert ? jzN(G.fs) : 'W[textIndex-1]||SZ') + ',f=1.1*(1-o5(q));';
    var soft = G.arranged || G.masked || en1_crowded(m, G);
    var mv = function (Lx, sgn) {      // sgn -1 = top (vertical text: right) half, +1 = the other half
        var sv = jzN(sgn * dir);
        jzAnimator(Lx, 'JZ In Half', [['ADBE Text Position 3D', [vert ? 0 : K, vert ? K : 0, 0]]], q + 'var v=q<=0||q>=1?0:' + sv + '*f*w/' + jzN(K) + '*100;' + (vert ? '[0,v,0]' : '[v,0,0]'));
    };
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + (soft ? 'q<=0?100:100*(1-cl(1.12-f))' : 'q<=0?100:100*(1-cl((1.12-f)*2))'));
    if (soft) { mv(L, -1); return; }
    var D = en1_twin(m, null, 'JZ In Half B');
    mv(L, -1); mv(D, 1);
    // L keeps the top halves (vertical: right halves), the copy the other halves: one mask per line / column
    var pad = G.fs * 0.66, r = G.rect, H = en1_head(m), n = 0;
    if (vert) {
        var xc = r.left + r.width / 2, y0 = r.top - G.fs, y1 = r.top + r.height + G.fs;
        en1_mask(L, xc, y0, r.left + r.width + G.fs, y1, null, H + 'time>=DL+IN?1e4:0', 'JZ In Half');
        en1_mask(D, r.left - G.fs, y0, xc + 0.004 * G.fs, y1, null, null, 'JZ In Half');
    } else {
        for (i = 0; i < G.nL; i++) {
            var ln = G.lines[i]; if (!ln.chars.length) continue;
            var x0 = r.left - G.fs * 1.5, x1 = r.left + r.width + G.fs * 1.5;
            en1_mask(L, x0, ln.cy - pad, x1, ln.cy + 0.004 * G.fs, null, !n ? H + 'time>=DL+IN?1e4:0' : null, 'JZ In Top ' + (i + 1));
            en1_mask(D, x0, ln.cy, x1, ln.cy + pad, null, null, 'JZ In Bottom ' + (i + 1));
            n++;
        }
    }
} });

/* ---- vSlice \u2014 \u7E26\u30B9\u30E9\u30A4\u30B9: the line is cut into vertical strips that slide in alternately from above and below */
jzReg('enter', 'vSlice', { apply: function (m) {
    var r = jzRect(m.L), fs = m.size, span = r.width + fs * 0.6;
    var n = jzClamp(Math.round(span / (fs * 0.55)), 4, 8), dist = r.height * 0.9 + fs * 1.4;
    // square Wave Warp = strips of alternating vertical offset (one strip = half a wave)
    var ww = jzEffect(m.L, 'ADBE Wave Warp', 'JZ In VSlice');
    jzEP(ww, 1, 2); jzEP(ww, 3, span / n * 2); jzEP(ww, 4, 90); jzEP(ww, 5, 0); jzEP(ww, 6, 1); jzEP(ww, 7, 0);
    // browser: centre strips first (ordC stagger over half the entrance); one Wave Warp moves all strips by their mean offset
    jzEX(ww, 2, m.HD + 'var N=' + n + ',s=0;for(var i=0;i<N;i++){var o=Math.abs(i-(N-1)/2)/((N-1)/2);s+=Math.pow(1-cl((P-0.5*o)/0.5),5);}' + jzN(dist) + '*s/N');
    m.parts.op.push('f*=cl(P*3);');
} });

/* ---- shutter \u2014 \u30B7\u30E3\u30C3\u30BF\u30FC: two accent lines grow from the centre, then part like a shutter and reveal the line between them */
jzReg('enter', 'shutter', { apply: function (m) {
    var L = m.L, G = en1_geo(m, false), r = G.rect, fs = G.fs, vert = G.vert, mg = fs * 0.2, H = en1_head(m);
    var cx = r.left + r.width / 2, cy = r.top + r.height / 2, w = r.width, h = r.height, t = Math.max(2 * m.u, fs * 0.03);
    var ev = 'var grow=o5(P/0.32),open=io4((P-0.18)/0.82),hhv=' + jzN((vert ? w : h) / 2 + mg) + '*open;';
    if (!G.masked) {
        var mk = vert ? en1_mask(L, cx - 0.25, r.top - mg, cx + 0.25, r.top + h + mg, null, null, 'JZ In Shutter') : en1_mask(L, r.left - mg, cy - 0.25, r.left + w + mg, cy + 0.25, null, null, 'JZ In Shutter');
        jzSetExpr(mk.property('ADBE Mask Opacity'), H + ev + 'open>0?100:0');
        jzSetExpr(mk.property('ADBE Mask Offset'), H + ev + 'time>=DL+IN?1e4:hhv');
    }
    if (G.arranged || en1_crowded(m, G)) return;
    var S = jzNoGhost(en1_shape(m, 'JZ In Shutter')), acc = m.ctx.sc.accent || '#ffffff', len = (vert ? h : w) + mg * 2, k;
    for (k = 0; k < 2; k++) {
        // the rect is set up before the fill is added (AE invalidates the rect reference once a sibling is added)
        var grp = jzGrp(S, k ? 'line B' : 'line A'), rc = jzAddRect(grp, 10, 10, 0), sg = k ? '+' : '-';
        jzSetExpr(rc.property('ADBE Vector Rect Size'), H + ev + (vert ? '[' + jzN(t) + ',' + jzN(len) + '*grow]' : '[' + jzN(len) + '*grow,' + jzN(t) + ']'));
        jzAddFill(grp, acc);
        var gx = jzGX(grp);
        jzSetExpr(gx.property('ADBE Vector Position'), H + ev + (vert ? '[' + jzN(cx) + sg + 'hhv,' + jzN(cy) + ']' : '[' + jzN(cx) + ',' + jzN(cy) + sg + 'hhv]'));
        jzSetExpr(gx.property('ADBE Vector Group Opacity'), H + ev + (k ? '(open>0?1:0)*' : '') + '(1-sm(0.6,0.9,P))*100');
    }
} });

/* ---- iris \u2014 \u30A2\u30A4\u30EA\u30B9: a circular iris opens from the centre with an accent ring on its edge while the line settles from 114% */
jzReg('enter', 'iris', { apply: function (m) {
    var L = m.L, G = en1_geo(m, false), r = G.rect, fs = G.fs, H = en1_head(m);
    var cx = r.left + r.width / 2, cy = r.top + r.height / 2, R = Math.sqrt(r.width * r.width + r.height * r.height) / 2 + fs * 0.25;
    m.parts.sc.push('var s1=1.14-0.14*(1-Math.pow(1-P,4));f=[f[0]*s1,f[1]*s1];');
    var ev = 'var e=o4(P),rad=' + jzN(R) + '*e;';
    if (!G.masked) {
        // a tiny circle grown by mask expansion (exact circle in AE)
        var mk = L.property('ADBE Mask Parade').addProperty('ADBE Mask Atom'), sh = new Shape(), r0 = 1, kk = r0 * 0.5523;
        sh.vertices = [[cx, cy - r0], [cx + r0, cy], [cx, cy + r0], [cx - r0, cy]];
        sh.inTangents = [[-kk, 0], [0, -kk], [kk, 0], [0, kk]]; sh.outTangents = [[kk, 0], [0, kk], [-kk, 0], [0, -kk]]; sh.closed = true;
        mk.property('ADBE Mask Shape').setValue(sh);
        try { mk.name = 'JZ In Iris'; } catch (e0) {}
        jzSetExpr(mk.property('ADBE Mask Opacity'), H + ev + 'rad>0.5?100:0');
        jzSetExpr(mk.property('ADBE Mask Offset'), H + ev + 'time>=DL+IN?1e4:Math.max(0,rad-' + jzN(r0) + ')');
    }
    if (G.arranged || en1_crowded(m, G)) return;
    // the ellipse is set up before the stroke is added (AE invalidates the ellipse reference once a sibling is added)
    var S = jzNoGhost(en1_shape(m, 'JZ In Iris')), grp = jzGrp(S, 'ring'), el = jzAddEllipse(grp, 10, 10);
    jzSetExpr(el.property('ADBE Vector Ellipse Size'), H + ev + '[rad*2,rad*2]');
    jzAddStroke(grp, m.ctx.sc.accent || '#ffffff', Math.max(2 * m.u, fs * 0.03));
    var gx = jzGX(grp);
    gx.property('ADBE Vector Position').setValue([cx, cy]);
    jzSetExpr(gx.property('ADBE Vector Group Opacity'), H + ev + 'rad>0.5?(1-sm(0.3,0.8,P))*100:0');
} });

/* ---- diagWipe \u2014 \u659C\u3081\u30EF\u30A4\u30D7: a slanted accent block sweeps over the line, then retracts and leaves the text behind it */
jzReg('enter', 'diagWipe', { apply: function (m) {
    var L = m.L, G = en1_geo(m, false), r = G.rect, fs = G.fs, vert = G.vert, dir = en1_dir(m, 17), kS = 0.5, H = en1_head(m);
    // (u, v): u along the reading direction, v across it
    var U0 = vert ? r.top : r.left, U1 = U0 + (vert ? r.height : r.width), V0 = vert ? r.left : r.top, V1 = V0 + (vert ? r.width : r.height);
    var mg = fs * 0.16, hv = (V1 - V0) / 2 + mg, half = (U1 - U0) / 2 + mg + kS * hv, uc = (U0 + U1) / 2, vc = (V0 + V1) / 2, big = Math.max(m.W, m.H) * 4;
    var ev = 'var eL=o4(P/0.5),eT=io3((P-0.18)/0.74),hf=' + jzN(half) + ',Lx=-hf+2*hf*eL,Tx=-hf+2*hf*eT;';
    if (!G.masked) {
        // text visible behind the block's trailing edge: an axis-aligned rect whose front edge (at the slant's front-most point) moves by expansion
        var f0 = -half + kS * hv, a0, a1;
        if (dir > 0) { a0 = uc - big; a1 = uc + f0; } else { a0 = uc - f0; a1 = uc + big; }
        var mk = vert ? en1_mask(L, vc - hv, a0, vc + hv, a1, null, null, 'JZ In Diag') : en1_mask(L, a0, vc - hv, a1, vc + hv, null, null, 'JZ In Diag');
        jzSetExpr(mk.property('ADBE Mask Offset'), H + ev + 'time>=DL+IN?1e4:Tx+hf');
    }
    if (G.arranged || en1_crowded(m, G)) return;
    // the rect is set up before the fill is added (AE invalidates the rect reference once a sibling is added)
    var S = jzNoGhost(en1_shape(m, 'JZ In Diag')), grp = jzGrp(S, 'block'), rc = jzAddRect(grp, 10, 10, 0), sk = Math.atan(kS) * 180 / Math.PI;
    jzSetExpr(rc.property('ADBE Vector Rect Size'), H + ev + 'var ln=Math.max(0,Lx-Tx);' + (vert ? '[' + jzN(hv * 2) + ',ln]' : '[ln,' + jzN(hv * 2) + ']'));
    jzAddFill(grp, m.ctx.sc.accent || '#ffffff');
    var gx = jzGX(grp);
    if (vert) { gx.property('ADBE Vector Skew Axis').setValue(90); gx.property('ADBE Vector Skew').setValue(dir * sk); }
    else gx.property('ADBE Vector Skew').setValue(-dir * sk);
    jzSetExpr(gx.property('ADBE Vector Position'), H + ev + 'var u=' + jzN(uc) + '+' + dir + '*(Lx+Tx)/2;' + (vert ? '[' + jzN(vc) + ',u]' : '[u,' + jzN(vc) + ']'));
    jzSetExpr(gx.property('ADBE Vector Group Opacity'), H + ev + 'Lx-Tx>=0.5?100:0');
} });

/* ---- blinds \u2014 \u30D6\u30E9\u30A4\u30F3\u30C9: thin blinds open one after another along the line (top to bottom; vertical text right to left) */
jzReg('enter', 'blinds', { apply: function (m) {
    var L = m.L, G = en1_geo(m, false), r = G.rect, fs = G.fs, vert = G.vert, mg = fs * 0.2, H = en1_head(m), k;
    if (G.masked) { jzAnimator(L, 'JZ In Fade', [['ADBE Text Opacity', 0]], m.HD + '(1-P)*100'); return; }
    var V0 = (vert ? r.left : r.top) - mg, V1 = (vert ? r.left + r.width : r.top + r.height) + mg, U0 = (vert ? r.top : r.left) - mg, U1 = (vert ? r.top + r.height : r.left + r.width) + mg;
    var pitch = Math.max(fs * 0.2, (V1 - V0) / 40), N = Math.max(1, Math.ceil((V1 - V0) / pitch));
    for (k = 0; k < N; k++) {
        var ord = N > 1 ? (vert ? N - 1 - k : k) / (N - 1) : 0, c = V0 + (k + 0.5) * pitch;
        var ev = 'var e=o4((P-' + jzN(0.55 * ord) + ')/0.45);';
        var mk = vert ? en1_mask(L, c - 0.25, U0, c + 0.25, U1, null, null, 'JZ In Blind ' + (k + 1)) : en1_mask(L, U0, c - 0.25, U1, c + 0.25, null, null, 'JZ In Blind ' + (k + 1));
        jzSetExpr(mk.property('ADBE Mask Opacity'), H + ev + 'e>0?100:0');
        jzSetExpr(mk.property('ADBE Mask Offset'), H + ev + (k ? '' : 'time>=DL+IN?1e4:') + jzN(pitch * 0.5) + '*e+0.15');
    }
} });

// ================================================================ pack enter part 2 (AE port of src/11p_enter.js)
// checker randomOrder bounceBig squashDrop rubber glitchIn echoIn whip skewIn trackIn trackOut blurStagger fadeStagger
// waveIn spiralIn zoomOut resolve magnet inkBleed neonOn cursorSweep stamp
// Per-glyph motion = text animators with Expression Selectors (amount \u2212100..100 % of the animator's value, per glyph via
// textIndex / textTotal). Graphics the browser draws around the item (tiles, bars, impact lines) are shape layers parented
// to the text layer (so they follow the layout + hold / exit transforms) and placed directly above it.

// ---------------------------------------------------------------- shared helpers (en2_*)
// extra easings used inside expression strings (JZ_FNS has cl oe ioe oc ic iq ie ob bo)
var EN2_FNS = 'function oq4(x){x=cl(x);return 1-Math.pow(1-x,4);}function oq5(x){x=cl(x);return 1-Math.pow(1-x,5);}' +
    'function oqd(x){x=cl(x);return 1-(1-x)*(1-x);}function ios(x){x=cl(x);return -(Math.cos(Math.PI*x)-1)/2;}' +
    'function sm(a,b,x){var t=cl((x-a)/(b-a));return t*t*(3-2*t);}' +
    'function hr(a,b){var x=Math.sin(a*12.9898+b*78.233+SD*0.6173)*43758.5453;return x-Math.floor(x);}\n';
// NOTE: AE seeds random() per property, so a random value that several animators must agree on (a glyph's delay, its
// direction \u2026) comes from hr(textIndex, salt) \u2014 a pure hash of the glyph index \u2014 instead of seedRandom/random().
// per-glyph order (ordLR) + stagger (stg) \u2192 q
function en2_stg(spread) { return 'var n=textTotal,o=n>1?(textIndex-1)/(n-1):0,q=cl((P-' + jzN(spread) + '*o)/' + jzN(1 - spread) + ');'; }
// compact timing header for the many small tile expressions (P only)
function en2_mini(m) {
    var c = m.c, IN = Math.max(0.02, c.inDur || 0.3), DL = (m.o.mi || 0) * (c.stagger || 0.04);
    return 'var P=Math.max(0,Math.min(1,(time-' + jzN(DL) + ')/' + jzN(IN) + '));';
}
function en2_end(m) { var c = m.c; return (m.o.mi || 0) * (c.stagger || 0.04) + Math.max(0.02, c.inDur || 0.3); }
// the browser's J.h (hash of up to 5 ints) so seeded directions (dirOf) match the web version
function en2_h(a, b, c, d, e) {
    var h = 0x9e3779b9 ^ (a | 0);
    h = jzImul(h ^ (h >>> 16), 0x85ebca6b);
    h = (h + jzImul((b | 0) + 0x632be5ab, 0xc2b2ae35)) | 0;
    h = jzImul(h ^ (h >>> 13), 0xc2b2ae35);
    h = (h + jzImul((c | 0) + 0x5bd1e995, 0x27d4eb2f)) | 0;
    h = jzImul(h ^ (h >>> 15), 0x165667b1);
    h = (h + jzImul((d | 0) + 0x1b873593, 0x85ebca6b)) | 0;
    h = jzImul(h ^ (h >>> 16), 0x27d4eb2f);
    h = (h + jzImul((e | 0) + 0x68e31da4, 0x9e3779b1)) | 0;
    h ^= h >>> 15; h = jzImul(h, 0x2c1b3c6d); h ^= h >>> 12; h = jzImul(h, 0x297a2d39); h ^= h >>> 15;
    return h >>> 0;
}
function en2_dir(m, salt) { return en2_h(m.c.seed | 0, salt, 5) / 4294967296 < 0.5 ? -1 : 1; }
function en2_isHex(c) { return typeof c === 'string' && c.charAt(0) === '#' && (c.length === 7 || c.length === 4); }
function en2_pick(a, b, c) { if (en2_isHex(a)) return a; if (en2_isHex(b)) return b; if (en2_isHex(c)) return c; return '#FFFFFF'; }
function en2_same(a, b) { return String(a || '').toLowerCase() === String(b || '').toLowerCase(); }
// contrasting flash colour: accent2 / fg when the text already is the accent colour, else accent
function en2_flash(m) {
    var sc = m.ctx.sc, col = jzTextColor(m.L);
    return en2_same(col, sc.accent) ? en2_pick(sc.accent2, sc.fg) : en2_pick(sc.accent, sc.fg);
}
// approximate advance of a glyph in em
function en2_adv(ch) {
    var c = ch.charCodeAt(0);
    if (ch === ' ') return 0.3;
    if (c >= 0xFF61 && c <= 0xFF9F) return 0.5;
    return c < 0x2000 ? 0.56 : 1;
}
// glyph geometry in the text layer's own space (textIndex order): centres xs / ys, advances ws, line height lh
function en2_geom(m) {
    var L = m.L, r = jzRect(L), fs = m.size, td = null, i, j;
    try { td = L.property('ADBE Text Properties').property('ADBE Text Document').value; } catch (e0) {}
    var text = td ? String(td.text) : '', trk = td ? (td.tracking || 0) / 1000 * fs : 0;
    var lines = text.split(/\r\n|\r|\n|\u0003/), nL = lines.length, lead = fs * 1.2;
    try { if (td && !td.autoLeading && td.leading > 0) lead = td.leading; } catch (e1) {}
    var just = 'c';
    try { if (td.justification === ParagraphJustification.LEFT_JUSTIFY) just = 'l'; else if (td.justification === ParagraphJustification.RIGHT_JUSTIFY) just = 'r'; } catch (e2) {}
    var rows = [], maxW = 0, vert = nL > 1;
    for (i = 0; i < nL; i++) {
        var cs = jzChars(lines[i]), ws = [], w = 0;
        for (j = 0; j < cs.length; j++) { var a = en2_adv(cs[j]) * fs; ws.push(a); w += a + (j < cs.length - 1 ? trk : 0); }
        if (cs.length > 1) vert = false;
        rows.push({ cs: cs, ws: ws, w: w }); maxW = Math.max(maxW, w);
    }
    var k = maxW > 0 ? r.width / maxW : 1; if (!(k > 0.5 && k < 2)) k = 1;
    var lh = r.height - (nL - 1) * lead; if (!(lh > fs * 0.5 && lh < fs * 1.6)) lh = fs * 0.95;
    var cx = r.left + r.width / 2, xs = [], ys = [], wa = [], li = [], ci = [], nl = [];
    for (i = 0; i < nL; i++) {
        var R = rows[i], x = just === 'l' ? r.left : (just === 'r' ? r.left + r.width - R.w * k : cx - R.w * k / 2), y = r.top + lh / 2 + i * lead;
        for (j = 0; j < R.cs.length; j++) {
            xs.push(x + R.ws[j] * k / 2); ys.push(y); wa.push(R.ws[j] * k); x += (R.ws[j] + trk) * k;
            li.push(i); ci.push(j); nl.push(R.cs.length);
        }
    }
    return { r: r, xs: xs, ys: ys, ws: wa, li: li, ci: ci, nl: nl, nL: nL, lh: lh, vert: vert, n: xs.length, cx: cx, cy: r.top + r.height / 2, lead: lead };
}
// put a new layer (shape layer or duplicate) into the text layer's own space, directly above it
function en2_attach(m, S) {
    try { S.setParentWithJump(m.L); } catch (e) { try { S.parent = m.L; } catch (e2) {} }
    var tr = S.property('ADBE Transform Group');
    try { tr.property('ADBE Anchor Point').setValue([0, 0]); } catch (e3) {}
    tr.property('ADBE Position').setValue([0, 0]); tr.property('ADBE Scale').setValue([100, 100]); tr.property('ADBE Rotate Z').setValue(0);
    try { S.moveBefore(m.L); } catch (e4) {}
    return S;
}
function en2_text(L) { try { return String(L.property('ADBE Text Properties').property('ADBE Text Document').value.text); } catch (e) { return ''; } }
// helper shape layers (checker tiles, decode block, cursor bar, stamp lines): the browser draws them with ghost off -> jzNoGhost
function en2_shape(m, name) { var S = m.ctx.comp.layers.addShape(); S.name = name; return jzNoGhost(en2_attach(m, S)); }
// keep the text box centre fixed while the layer is scaled by fS / rotated by rS (expression strings; the layer may be anchored
// at its left / right edge): the browser scales / rotates items about their box centre (scaleAbout)
function en2_pivot(m, fS, rS) {
    var L = m.L, tr = L.property('ADBE Transform Group'), a = tr.property('ADBE Anchor Point').value, s = tr.property('ADBE Scale').value;
    var rot = tr.property('ADBE Rotate Z').value * Math.PI / 180, r = jzRect(L);
    var ux = (r.left + r.width / 2 - a[0]) * s[0] / 100, uy = (r.top + r.height / 2 - a[1]) * s[1] / 100;
    var vx = Math.cos(rot) * ux - Math.sin(rot) * uy, vy = Math.sin(rot) * ux + Math.cos(rot) * uy;
    if (Math.abs(vx) + Math.abs(vy) < 0.5) return;
    var X = jzN(vx), Y = jzN(vy);
    m.parts.pos.push('var n2f=' + fS + ',n2r=(' + (rS || '0') + ')*Math.PI/180,n2c=Math.cos(n2r),n2s=Math.sin(n2r);d=[d[0]+' + X + '-n2f*(n2c*' + X + '-n2s*' + Y + '),d[1]+' + Y + '-n2f*(n2s*' + X + '+n2c*' + Y + ')];');
}

/* ---- checker \u2014 \u5E02\u677E: accent tiles pop up in a checkerboard / left\u2192right order and shrink away, each tile uncovering its square of text */
jzReg('enter', 'checker', { selfHide: true, apply: function (m) {
    var L = m.L, r = jzRect(L), size = m.size, seed = m.c.seed | 0, acc = en2_pick(m.ctx.sc.accent, m.ctx.sc.fg);
    var mg = size * 0.15, W = r.width + mg * 2, H = r.height + mg * 2, cell = size * 0.34;
    while ((W / cell) * (H / cell) > 96) cell *= 1.25;      // the browser allows 320 tiles; masks are heavier in AE
    var nx = Math.ceil(W / cell), ny = Math.ceil(H / cell), ox = r.left + r.width / 2 - nx * cell / 2, oy = r.top + r.height / 2 - ny * cell / 2;
    var mh = en2_mini(m), S = en2_shape(m, 'JZ Checker'), ix, iy, C = jzN(cell);
    for (iy = 0; iy < ny; iy++) for (ix = 0; ix < nx; ix++) {
        var d = ((ix + iy) & 1) * 0.25 + (nx > 1 ? ix / (nx - 1) : 0) * 0.3 + jzR(seed, ix, iy, 7) * 0.08;
        var tx = ox + (ix + 0.5) * cell, ty = oy + (iy + 0.5) * cell, U = mh + 'var u=(P-' + jzN(d) + ')/0.22;';
        // text clip: the tile's square grows (outQuart) from its centre \u2014 full-size mask contracted by a negative expansion
        var mk = jzMaskRect(L, tx - cell / 2 - 0.5, ty - cell / 2 - 0.5, tx + cell / 2 + 0.5, ty + cell / 2 + 0.5);
        mk.property('ADBE Mask Offset').expression = U + 'u<=0?0:-' + C + '/2*Math.pow(1-Math.min(1,u),4)';
        mk.property('ADBE Mask Opacity').expression = U + 'u>0?100:0';
        // accent tile on top: pops in, then shrinks away (inCubic) before the text square is complete
        var g = jzGrp(S, 't' + ix + '_' + iy);
        jzAddRect(g, cell, cell, 0); jzAddFill(g, acc, 90);
        jzGX(g).property('ADBE Vector Position').setValue([tx, ty]);
        jzGX(g).property('ADBE Vector Scale').expression = U + 'var k=(u<=0||u>=0.75)?0:(1-Math.pow(u/0.75,3))*Math.min(1,u*6);[k*100,k*100]';
    }
    // once the entrance is over the text is no longer clipped (holds / exits may move glyphs outside the tile grid)
    var mo = jzMaskRect(L, r.left, r.top, r.left + r.width, r.top + r.height), E = jzN(en2_end(m));
    mo.property('ADBE Mask Offset').expression = 'time>=' + E + '?1e4:0';
    mo.property('ADBE Mask Opacity').expression = 'time>=' + E + '?100:0';
} });

/* ---- randomOrder \u2014 \u30E9\u30F3\u30C0\u30E0\u9806: glyphs pop in (scale 1.45 \u2192 1) in a random order, flashing the accent colour as they land */
jzReg('enter', 'randomOrder', { selfHide: true, apply: function (m) {
    var L = m.L, q = m.HD + EN2_FNS + 'var u=cl((P-hr(textIndex,61)*0.62)/0.38);';
    jzAnimator(L, 'JZ In Pop', [['ADBE Text Scale 3D', [300, 300, 100]]], q + 'u<=0||u>=1?0:0.45*(1-oq5(u))/2*100');
    jzAnimator(L, 'JZ In Flash', [['ADBE Text Fill Color', jzHex(en2_flash(m))]], q + 'u>0&&u<0.42?100:0');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + 'u<=0?100:(1-cl(u*6))*100');
} });

/* ---- bounceBig \u2014 \u5927\u30B8\u30E3\u30F3\u30D7: each glyph leaps up from below, lands with a squash and bounces twice */
jzReg('enter', 'bounceBig', { selfHide: true, apply: function (m) {
    var L = m.L, h = m.size;
    var q = m.HD + en2_stg(0.35) + 'var y=0,s=0;if(q<0.5){s=q/0.5;y=7.666*s*s-9.266*s+1.6;}else if(q<0.76){s=(q-0.5)/0.26;y=-0.96*s*(1-s);}else if(q<0.9){s=(q-0.76)/0.14;y=-0.24*s*(1-s);}' +
        'var imp=Math.exp(-Math.pow((q-0.5)/0.035,2))+0.6*Math.exp(-Math.pow((q-0.76)/0.03,2)),sy=1-0.3*imp,sx=1+0.24*imp,on=q>0&&q<1;';
    jzAnimator(L, 'JZ In Jump', [['ADBE Text Position 3D', [0, h * 2, 0]]], q + 'on?[0,(y+(1-sy)/2)/2*100,0]:[0,0,0]');
    jzAnimator(L, 'JZ In Squash', [['ADBE Text Scale 3D', [300, 300, 100]]], q + 'on?[(sx-1)/2*100,(sy-1)/2*100,0]:[0,0,0]');
    jzAnimator(L, 'JZ In Tilt', [['ADBE Text Rotation', 28]], q + m.seedR + 'var rs=random(-1,1);on&&q<0.5?rs*Math.sin(Math.PI*s)*100:0');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + 'q<=0?100:(1-cl(q*7))*100');
} });

/* ---- squashDrop \u2014 \u6F70\u308C\u3066\u7740\u5730: glyphs fall in stretched, squash flat on landing and wobble back */
jzReg('enter', 'squashDrop', { selfHide: true, apply: function (m) {
    var L = m.L, h = m.size;
    var q = m.HD + en2_stg(0.4) + 'var dy=0,sy=1,t=0;if(q<0.22){t=q/0.22;dy=-(1-t*t)*1.7;sy=1+0.4*t;}else{t=(q-0.22)/0.78;sy=1-0.58*Math.exp(-3*t)*Math.cos(t*6.2)*(1-t);}' +
        'var sx=1/Math.pow(sy,0.8),on=q>0&&q<1;';
    jzAnimator(L, 'JZ In Fall', [['ADBE Text Position 3D', [0, h * 2, 0]]], q + 'on?[0,(dy+(1-sy)/2)/2*100,0]:[0,0,0]');
    jzAnimator(L, 'JZ In Squash', [['ADBE Text Scale 3D', [300, 300, 100]]], q + 'on?[(sx-1)/2*100,(sy-1)/2*100,0]:[0,0,0]');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + 'q<=0?100:(1-cl(q*10))*100');
} });

/* ---- rubber \u2014 \u30B4\u30E0\u4F38\u3073: glyphs snap open from a thin sliver like rubber (damped horizontal stretch, volume kept) */
jzReg('enter', 'rubber', { selfHide: true, apply: function (m) {
    var L = m.L;
    var q = m.HD + en2_stg(0.4) + 'var f=Math.pow(1-q,2)*Math.cos(q*3.5*Math.PI),sx=Math.max(0.03,1-f),sy=Math.max(0.72,Math.min(1.45,1+(1-sx)*0.45)),on=q>0&&q<1;';
    jzAnimator(L, 'JZ In Rubber', [['ADBE Text Scale 3D', [300, 300, 100]]], q + 'on?[(sx-1)/2*100,(sy-1)/2*100,0]:[0,0,0]');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + 'q<=0?100:(1-cl(q*6))*100');
} });

/* ---- glitchIn \u2014 \u30B0\u30EA\u30C3\u30C1\u51FA\u73FE: glyphs flicker in at random, jittering, recoloured and stretched, over sliding glitch bands */
jzReg('enter', 'glitchIn', { selfHide: true, apply: function (m) {
    var L = m.L, sc = m.ctx.sc, size = m.size, cols = [], k, all = [sc.accent, sc.ghostA, sc.ghostB, sc.accent2];
    for (k = 0; k < all.length; k++) if (en2_isHex(all[k])) cols.push(all[k]);
    var base = m.HD + EN2_FNS + 'var st=0.38+0.52*hr(textIndex,71),u=P/st,on=P<st,F=Math.floor(time*12),i=textIndex;';
    jzAnimator(L, 'JZ In Glitch Hide', [['ADBE Text Opacity', 0]], base + 'on?(hr(i,F*5+72)>0.25*cl(P*12)+0.75*u?100:0):0');
    jzAnimator(L, 'JZ In Glitch Jitter', [['ADBE Text Position 3D', [size * 0.5, size * 0.14, 0]]], base + 'var j=on?1-u:0;[(hr(i,F*5+73)*2-1)*j*100,(hr(i,F*5+74)*2-1)*j*100,0]');
    jzAnimator(L, 'JZ In Glitch Stretch', [['ADBE Text Scale 3D', [300, 100, 100]]], base + 'on&&hr(i,F*5+77)<0.35?(0.55+1.35*hr(i,F*5+78)-1)/2*100:0');
    for (k = 0; k < cols.length; k++)
        jzAnimator(L, 'JZ In Glitch Colour ' + (k + 1), [['ADBE Text Fill Color', jzHex(cols[k])]], base + 'on&&hr(i,F*5+75)<0.55&&Math.floor(hr(i,F*5+76)*' + cols.length + ')==' + k + '?100:0');
    // horizontal glitch bands (the browser's hBands): stepped square-wave displacement of rows
    var ww = jzEffect(L, 'ADBE Wave Warp', 'JZ In Glitch Bands');
    jzEP(ww, 1, 2); jzEP(ww, 4, 0); jzEP(ww, 5, 0); jzEP(ww, 6, 1);
    jzEX(ww, 2, m.HD + 'posterizeTime(12);seedRandom(SD+Math.floor(time*12)*3,true);var a=random(),b=random();P>0&&P<0.85&&a<0.6?b*SZ*0.45*Math.pow(1-P,1.2):0');
    jzEX(ww, 3, m.HD + 'posterizeTime(12);seedRandom(SD+Math.floor(time*12)+9,true);random(SZ*0.25,SZ*0.9)');
    jzEX(ww, 7, m.HD + 'posterizeTime(12);seedRandom(SD+Math.floor(time*12)+5,true);random(0,360)');
} });

/* ---- echoIn \u2014 \u6B8B\u50CF\u96C6\u675F: three accent outline copies of every glyph collapse onto it while the glyph fades in */
jzReg('enter', 'echoIn', { selfHide: true, apply: function (m) {
    var L = m.L, sc = m.ctx.sc, col = en2_pick(sc.accent, sc.fg), size = m.size, nLay = 0;
    try { nLay = m.ctx.comp.numLayers; } catch (e0) {}
    var nc = nLay > 30 ? 1 : (nLay > 18 ? 2 : 3), q = m.HD + EN2_FNS + en2_stg(0.35), prev = L, k;
    var sw = Math.max(1.4 * m.u, size * 0.02);
    // the copies are made before the lyric's own animators are added (they only carry the layout's placement animators)
    for (k = 1; k <= nc; k++) {
        var D = L.duplicate();
        D.name = 'JZ Echo ' + k;
        jzTextDoc(D, function (td) { td.applyFill = false; td.applyStroke = true; td.strokeColor = jzHex(col); td.strokeWidth = sw; });
        en2_attach(m, D); D.moveAfter(prev); prev = D;
        var ek = q + 'var k=1-oq4(q),on=q>0&&q<1;';
        jzAnimator(D, 'JZ Echo Scale', [['ADBE Text Scale 3D', [300, 300, 100]]], ek + 'on?' + jzN(k * 0.3) + '*k/2*100:0');
        jzAnimator(D, 'JZ Echo Fade', [['ADBE Text Opacity', 0]], ek + 'on?(1-' + jzN(0.95 - k * 0.2) + '*Math.min(1,k*1.8)*cl(q*6))*100:100');
    }
    jzAnimator(L, 'JZ In Echo Grow', [['ADBE Text Scale 3D', [300, 300, 100]]], q + 'q>0&&q<1?-0.12*(1-oq4(q))/2*100:0');
    jzAnimator(L, 'JZ In Echo Fade', [['ADBE Text Opacity', 0]], q + 'q<=0?100:(1-cl(q*2.2))*100');
} });

/* ---- whip \u2014 \u30DB\u30A4\u30C3\u30D7: the line whips in from the side (outExpo), leaning into the move and swinging back, with a speed streak */
jzReg('enter', 'whip', { apply: function (m) {
    var L = m.L, dir = en2_dir(m, 19), g = en2_geom(m), r = g.r, s = jzXf(L, 'ADBE Scale').value, size = m.size;
    var D = Math.max(size * 4.5, r.width * 0.9) * Math.abs(s[0]) / 100;
    m.parts.pos.push('var n2e=oe(P);d=[d[0]+' + jzN(dir * D) + '*(1-n2e),d[1]];');
    m.parts.op.push('f*=cl(P*8);');
    var sk = m.HD + EN2_FNS + 'var e=oe(P),sk=(' + dir + ')*34*Math.pow(1-e,0.7)-(' + dir + ')*14*Math.sin(Math.PI*cl((P-0.3)/0.7))*(1-sm(0.6,1,P));';
    jzAnimator(L, 'JZ In Whip Lean', [['ADBE Text Skew', 70]], sk + 'P>=1?0:sk/70*100');
    // the browser shears the whole item: glyphs on other lines slide sideways by tan(skew) * their height from the centre
    var dy = [], mx = 0, i;
    for (i = 0; i < g.n; i++) { dy.push(g.ys[i] - g.cy); mx = Math.max(mx, Math.abs(g.ys[i] - g.cy)); }
    if (mx > size * 0.2) {
        var K = mx * 1.2;
        jzAnimator(L, 'JZ In Whip Shear', [['ADBE Text Position 3D', [K, 0, 0]]], sk + 'var Y=' + jzArrExpr(dy) + ';var y=Y[textIndex-1]||0;P>=1?0:Math.tan(sk*Math.PI/180)*y/' + jzN(K) + '*100');
    }
    // speed streak: directional blur along the move while it is fast
    var db = jzEffect(L, 'ADBE Motion Blur', 'JZ In Whip Streak');
    jzEP(db, 1, 90); jzEX(db, 2, m.HD + '(1-oe(P))*SZ*0.9');
} });

/* ---- skewIn \u2014 \u30B9\u30AD\u30E5\u30FC: glyphs slide in slanted from the side and straighten up with a little overshoot (pivot at the foot) */
jzReg('enter', 'skewIn', { selfHide: true, apply: function (m) {
    var L = m.L, dir = en2_dir(m, 23), size = m.size, h = size;
    var q = m.HD + EN2_FNS + en2_stg(0.45) + 'var sk=Math.max(-66,Math.min(66,(' + dir + ')*60*(1-ob(q,1.6)))),on=q>0&&q<1;';
    jzAnimator(L, 'JZ In Skew', [['ADBE Text Skew', 66]], q + 'on?sk/66*100:0');
    jzAnimator(L, 'JZ In Skew Slide', [['ADBE Text Position 3D', [size * 2, 0, 0]]], q + 'var dx=-Math.tan(sk*Math.PI/180)*' + jzN(h / 2) + '+(' + dir + ')*(1-oq5(q))*' + jzN(size * 0.7) + ';on?dx/' + jzN(size * 2) + '*100:0');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + 'q<=0?100:(1-cl(q*3))*100');
} });

// per-glyph spread factor for tracking moves: index in its line \u2212 line centre (vertical text: line \u2212 centre, along y)
function en2_trackAnim(m, name, em) {
    var g = en2_geom(m), f = [], mx = 0, i;
    for (i = 0; i < g.n; i++) { var v = g.vert ? g.li[i] - (g.nL - 1) / 2 : g.ci[i] - (g.nl[i] - 1) / 2; f.push(v); mx = Math.max(mx, Math.abs(v)); }
    if (mx <= 0) return;
    var K = mx * Math.abs(em) * m.size, sg = em < 0 ? -1 : 1;
    jzAnimator(m.L, name, [['ADBE Text Position 3D', [K, K, 0]]], m.HD + 'var F=' + jzArrExpr(f) + ';var v=(F[textIndex-1]||0)/' + jzN(mx) + '*' + sg + '*Math.pow(1-P,5)*100;' + (g.vert ? '[0,v,0]' : '[v,0,0]'));
}

/* ---- trackIn \u2014 \u5B57\u9593\u53CE\u675F: wide letter-spacing closes in (outQuint) while the line fades up */
jzReg('enter', 'trackIn', { apply: function (m) {
    if (jzCount(en2_text(m.L)) <= 1) m.parts.pos.push('var n2e=1-Math.pow(1-P,5);d=[d[0]+(value[0]-thisComp.width/2)*0.9*(1-n2e),d[1]];');
    else en2_trackAnim(m, 'JZ In Track', 1.6);
    m.parts.op.push('f*=oc(cl(P*1.7));');
} });

/* ---- trackOut \u2014 \u5B57\u9593\u62E1\u5F35: glyphs start piled up (negative spacing, see-through) and spread out to their places */
jzReg('enter', 'trackOut', { apply: function (m) {
    if (jzCount(en2_text(m.L)) <= 1) m.parts.pos.push('var n2e=1-Math.pow(1-P,5);d=[d[0]+(thisComp.width/2-value[0])*(1-n2e),d[1]+(thisComp.height/2-value[1])*(1-n2e)];');
    else en2_trackAnim(m, 'JZ In Track', -0.72);
    jzAnimator(m.L, 'JZ In Track Fade', [['ADBE Text Opacity', 0]], m.HD + '0.45*Math.pow(1-P,5)*100');
    m.parts.op.push('f*=cl(P*2.5);');
} });

/* ---- blurStagger \u2014 \u30D6\u30E9\u30FC\u6BB5\u5DEE: glyphs come into focus one after another, settling down from a larger, blurred state */
jzReg('enter', 'blurStagger', { selfHide: true, apply: function (m) {
    var L = m.L, size = m.size, bl = Math.min(42 * m.u, size * 0.14);
    var q = m.HD + en2_stg(0.55) + 'var e=oc(q),on=q>0&&q<1;';
    jzAnimator(L, 'JZ In Focus', [['ADBE Text Blur', [bl, bl]]], q + 'on?(1-e)*100:0');
    jzAnimator(L, 'JZ In Settle', [['ADBE Text Scale 3D', [300, 300, 100]], ['ADBE Text Position 3D', [0, -size * 0.16 / 0.175, 0]]], q + 'on?0.175*(1-e)*100:0');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + 'q<=0?100:(1-Math.pow(e,0.6))*100');
} });

/* ---- fadeStagger \u2014 \u5B57\u3054\u3068\u30D5\u30A7\u30FC\u30C9: a soft glyph-by-glyph fade (in-out sine) rising a touch into place */
jzReg('enter', 'fadeStagger', { selfHide: true, apply: function (m) {
    var L = m.L, q = m.HD + EN2_FNS + en2_stg(0.62) + 'var on=q>0&&q<1;';
    jzAnimator(L, 'JZ In Rise', [['ADBE Text Position 3D', [0, m.size * 0.07, 0]]], q + 'on?(1-oc(q))*100:0');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + 'q<=0?100:(1-ios(q))*100');
} });

/* ---- waveIn \u2014 \u6CE2\u7ACB\u3061: glyphs appear left\u2192right riding a damped travelling wave (bob + rock) */
jzReg('enter', 'waveIn', { selfHide: true, apply: function (m) {
    var L = m.L, q = m.HD + 'var o=textTotal>1?(textIndex-1)/(textTotal-1):0,a=cl(P*2.4-o*1.3),dm=Math.pow(1-P,1.6),ph=P*3.6*Math.PI-(textIndex-1)*0.8;';
    jzAnimator(L, 'JZ In Wave', [['ADBE Text Position 3D', [0, m.size * 0.66, 0]]], q + '[0,-Math.sin(ph)*dm*100,0]');
    jzAnimator(L, 'JZ In Rock', [['ADBE Text Rotation', 13]], q + 'Math.cos(ph)*dm*100');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + '(1-a)*100');
} });

/* ---- spiralIn \u2014 \u87BA\u65CB\u96C6\u5408: glyphs swirl in on a shrinking spiral around the line's centre, spinning and growing into place */
jzReg('enter', 'spiralIn', { selfHide: true, apply: function (m) {
    var L = m.L, g = en2_geom(m), dir = en2_dir(m, 29), size = m.size, X = [], Y = [], mr = 0, i;
    for (i = 0; i < g.n; i++) { X.push(g.xs[i] - g.cx); Y.push(g.ys[i] - g.cy); mr = Math.max(mr, Math.sqrt(X[i] * X[i] + Y[i] * Y[i])); }
    var K = mr * 2.7 + size * 1.05, KN = jzN(K);
    var q = m.HD + EN2_FNS + en2_stg(0.15) + 'var X=' + jzArrExpr(X) + ',Y=' + jzArrExpr(Y) + ';var px=X[textIndex-1]||0,py=Y[textIndex-1]||0,e=oc(q),k=1-e,on=q>0&&q<1;' +
        'var ang=(' + dir + ')*k*200*Math.PI/180,c=Math.cos(ang),s=Math.sin(ang),r=1+0.7*k,th=hr(textIndex,31)*Math.PI*2+ang,orb=' + jzN(size) + '*k;' +
        'var nx=(c*px-s*py)*r+Math.cos(th)*orb,ny=(s*px+c*py)*r+Math.sin(th)*orb;';
    jzAnimator(L, 'JZ In Spiral', [['ADBE Text Position 3D', [K, K, 0]]], q + 'on?[(nx-px)/' + KN + '*100,(ny-py)/' + KN + '*100,0]:[0,0,0]');
    jzAnimator(L, 'JZ In Spin', [['ADBE Text Rotation', 320]], q + 'on?(' + dir + ')*k*100:0');
    jzAnimator(L, 'JZ In Grow', [['ADBE Text Scale 3D', [300, 300, 100]]], q + 'on?-0.55*k/2*100:0');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + 'q<=0?100:(1-cl(q*5))*100');
} });

/* ---- zoomOut \u2014 \u5DE8\u5927\u2192\u7B49\u500D: the line slams down from 4.4\u00D7 to its size (outExpo) with a tiny undershoot */
jzReg('enter', 'zoomOut', { apply: function (m) {
    var fS = '(1+3.4*(1-oe(P))-0.035*Math.sin(Math.PI*cl((P-0.22)/0.58)))';
    m.parts.sc.push('var n2z=' + fS + ';f=[f[0]*n2z,f[1]*n2z];');
    m.parts.op.push('f*=cl(P*6);');
    en2_pivot(m, fS, '0');
} });

/* ---- magnet \u2014 \u78C1\u77F3: glyphs hover far out (half-lit), get yanked in, overshoot and wobble with a small bump */
jzReg('enter', 'magnet', { selfHide: true, apply: function (m) {
    var L = m.L, size = m.size;
    var q = m.HD + EN2_FNS + 'var i=textIndex,d=textTotal>1?hr(i,81)*0.3:0,ang=hr(i,82)*Math.PI*2,dist=1.3+1.5*hr(i,83),r0=hr(i,84)*2-1;' +
        'var u=cl((P-d)/0.7),f=0,a=1,t=0;if(u<0.36){f=1-0.16*oc(u/0.36);a=0.5*cl(u*8);}else if(u<0.6){t=(u-0.36)/0.24;f=0.84*(1-ic(t));a=0.5+0.5*cl(t*3);}' +
        'else{t=(u-0.6)/0.4;f=-0.1*Math.sin(t*Math.PI*2)*(1-t);}var s=1+0.14*Math.exp(-Math.pow((u-0.6)/0.05,2)),on=u>0&&u<1;';
    jzAnimator(L, 'JZ In Pull', [['ADBE Text Position 3D', [size * 3, size * 3, 0]]], q + 'on?[Math.cos(ang)*dist*f/3*100,Math.sin(ang)*dist*f/3*100,0]:[0,0,0]');
    jzAnimator(L, 'JZ In Twist', [['ADBE Text Rotation', 55]], q + 'on?r0*f*100:0');
    jzAnimator(L, 'JZ In Bump', [['ADBE Text Scale 3D', [300, 300, 100]]], q + 'on?(s-1)/2*100:0');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + 'u<=0?100:(1-a)*100');
} });

/* ---- inkBleed \u2014 \u306B\u3058\u307F: glyphs bloom out of a blurred blot (random order) with a soft halo of their own colour */
jzReg('enter', 'inkBleed', { selfHide: true, apply: function (m) {
    var L = m.L, size = m.size, bl = Math.min(36 * m.u, size * 0.16);
    var q = m.HD + EN2_FNS + 'var d=textTotal>1?hr(textIndex,91)*0.45:0,u=cl((P-d)/0.55),e=oc(u),on=u>0&&u<1;';
    jzAnimator(L, 'JZ In Bloom', [['ADBE Text Scale 3D', [300, 300, 100]]], q + 'on?(0.5+0.5*ob(u,1.1)-1)/2*100:0');
    jzAnimator(L, 'JZ In Bleed', [['ADBE Text Blur', [bl, bl]]], q + 'on?Math.pow(1-e,1.3)*100:0');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + 'u<=0?100:(1-cl(u*3.2))*100');
    var ds = jzEffect(L, 'ADBE Drop Shadow', 'JZ In Bleed Halo'), hh = m.HD + EN2_FNS + 'var h=1-sm(0.2,0.88,P);';
    jzEP(ds, 1, jzHex(jzTextColor(L))); jzEP(ds, 3, 0); jzEP(ds, 4, 0);
    jzEX(ds, 2, hh + '255*0.55*h');
    jzEX(ds, 5, hh + 'SZ*0.6*h');
} });

/* ---- resolve \u2014 \u89E3\u8AAD: a decoder front runs through the line \u2014 an accent block on the current glyph, scrambled dim glyphs
   ahead of it, freshly decoded glyphs flash the accent colour (browser: random signs \u2192 AE: Character Offset scramble) */
jzReg('enter', 'resolve', { selfHide: true, apply: function (m) {
    var L = m.L, sc = m.ctx.sc, g = en2_geom(m), hi = en2_pick(sc.accent, sc.fg), dim = en2_pick(sc.sub, sc.fg);
    var q = m.HD + 'var N=textTotal,i=textIndex-1,f=P*(N+1.8),fr=Math.floor(f),on=P>=0.03;';
    jzAnimator(L, 'JZ In Decode Hide', [['ADBE Text Opacity', 0]], q + '!on?100:(i<fr?0:(i==fr?100:(i<=fr+4?50:100)))');
    jzAnimator(L, 'JZ In Decode Scramble', [['ADBE Text Character Offset', 40]], q + 'posterizeTime(12);seedRandom(textIndex*17+SD+Math.floor(time*12)*29,true);var r=random(20,100);on&&i>fr&&i<=fr+4?r:0');
    jzAnimator(L, 'JZ In Decode Dim', [['ADBE Text Fill Color', jzHex(dim)]], q + 'on&&i>fr&&i<=fr+4?100:0');
    jzAnimator(L, 'JZ In Decode Flash', [['ADBE Text Fill Color', jzHex(hi)]], q + 'on&&i<fr&&f-i-1<0.6?100:0');
    if (!g.n) return;
    // the accent block sitting on the glyph being decoded
    var W = [], i;
    for (i = 0; i < g.n; i++) W.push(g.ws[i] * 0.9);
    // the rect is set up before the fill is added (AE invalidates the rect reference once a sibling is added)
    var S = en2_shape(m, 'JZ Decode Block'), gr = jzGrp(S, 'block'), rc = jzAddRect(gr, m.size * 0.9, m.size * 0.98, 0);
    var bh = m.HD + 'var N=' + g.n + ',f=P*(N+1.8),fr=Math.min(N-1,Math.floor(f));';
    rc.property('ADBE Vector Rect Size').expression = bh + 'var W=' + jzArrExpr(W) + ';[W[fr],' + jzN(m.size * 0.98) + ']';
    rc.property('ADBE Vector Rect Position').expression = bh + 'var X=' + jzArrExpr(g.xs) + ',Y=' + jzArrExpr(g.ys) + ';[X[fr],Y[fr]]';
    jzAddFill(gr, hi);
    jzSetExpr(jzXf(S, 'ADBE Opacity'), bh + 'P>=0.03&&Math.floor(f)<N?100:0');
} });

/* ---- neonOn \u2014 \u30CD\u30AA\u30F3\u70B9\u706F: dim tubes (10 %) flicker on glyph by glyph while a glow swells and settles */
jzReg('enter', 'neonOn', { selfHide: true, apply: function (m) {
    var L = m.L, sc = m.ctx.sc, col = jzTextColor(L), gc = jzLum(col) > 0.35 ? col : en2_pick(sc.accent, col);
    jzAnimator(L, 'JZ In Neon', [['ADBE Text Opacity', 0]], m.HD + EN2_FNS + 'var i=textIndex,ig=0.3+0.45*hr(i,101),F=Math.floor(time*12),on=hr(i,F*3+102)<0.25+0.75*cl((P-ig+0.22)/0.34);' +
        'P>=ig+0.12?0:(P<ig-0.22?(1-0.1*cl(P*8))*100:(on?0:90))');
    var ds = jzEffect(L, 'ADBE Drop Shadow', 'JZ In Neon Glow'), gl = m.HD + EN2_FNS + 'var gw=sm(0.1,0.4,P)*(1-sm(0.62,0.92,P));';
    jzEP(ds, 1, jzHex(gc)); jzEP(ds, 3, 0); jzEP(ds, 4, 0);
    jzEX(ds, 2, gl + 'gw>0.01?242:0');
    jzEX(ds, 5, gl + 'Math.min(' + jzN(80 * m.u) + ',SZ*0.32)*gw*2');
} });

/* ---- cursorSweep \u2014 \u30AB\u30FC\u30BD\u30EB\u6383\u5F15: an accent bar sweeps across the line; glyphs pop out behind it (growing, sliding back) */
jzReg('enter', 'cursorSweep', { selfHide: true, apply: function (m) {
    var L = m.L, g = en2_geom(m), r = g.r, size = m.size, vert = g.vert, bw = size * 0.28, i;
    var U0 = (vert ? r.top : r.left) - bw, U1 = (vert ? r.top + r.height : r.left + r.width) + bw * 1.5, GU = [], HS = [];
    for (i = 0; i < g.n; i++) { GU.push(vert ? g.ys[i] : g.xs[i]); HS.push((vert ? size : g.ws[i]) / 2); }
    var ps = 'var pos=' + jzN(U0) + '+' + jzN(U1 - U0) + '*oqd(P/0.85);';
    var q = m.HD + EN2_FNS + ps + 'var G=' + jzArrExpr(GU) + ',H=' + jzArrExpr(HS) + ';var gu=G[textIndex-1]||0,hs=H[textIndex-1]||1;' +
        'var u=cl((pos-' + jzN(bw / 2) + '-(gu-hs))/(hs*2+' + jzN(size * 0.2) + ')),e=oq5(u),on=u>0&&u<1;';
    jzAnimator(L, 'JZ In Sweep Slide', [['ADBE Text Position 3D', vert ? [0, size * 0.3, 0] : [size * 0.3, 0, 0]]], q + 'on?(1-e)*100:0');
    jzAnimator(L, 'JZ In Sweep Grow', [['ADBE Text Scale 3D', [300, 300, 100]]], q + 'on?-0.3*(1-e)/2*100:0');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + 'u<=0?100:(1-cl(u*3))*100');
    // the cursor bar (thins out at the end)
    // the rect is set up before the fill is added (AE invalidates the rect reference once a sibling is added)
    var S = en2_shape(m, 'JZ Cursor'), gr = jzGrp(S, 'bar'), mg = size * 0.15, rc = jzAddRect(gr, 10, 10, 0);
    var bh = m.HD + EN2_FNS + ps + 'var t=' + jzN(bw) + '*(1-sm(0.78,0.94,P));';
    rc.property('ADBE Vector Rect Size').expression = bh + (vert ? '[' + jzN(r.width + mg * 2) + ',Math.max(0.01,t)]' : '[Math.max(0.01,t),' + jzN(r.height + mg * 2) + ']');
    rc.property('ADBE Vector Rect Position').expression = bh + (vert ? '[' + jzN(r.left + r.width / 2) + ',pos]' : '[pos,' + jzN(r.top + r.height / 2) + ']');
    jzAddFill(gr, en2_pick(m.ctx.sc.accent, m.ctx.sc.fg));
    jzSetExpr(jzXf(S, 'ADBE Opacity'), bh + 'time>=DL&&t>=0.5?100:0');
} });

/* ---- stamp \u2014 \u30B9\u30BF\u30F3\u30D7: the line drops onto the page like a rubber stamp (big + tilted \u2192 impact), shudders, impact lines burst out */
jzReg('enter', 'stamp', { apply: function (m) {
    var L = m.L, dir = en2_dir(m, 37), size = m.size, r = jzRect(L);
    var fS = '(P<0.42?2.3-1.3*iq(P/0.42):1-0.05*Math.exp(-9*(P-0.42)/0.58)*(1-(P-0.42)/0.58))';
    var rS = '(P<0.42?' + jzN(-22 * dir) + '*(1-iq(P/0.42)):0)';
    m.parts.sc.push('var n2s=' + fS + ';f=[f[0]*n2s,f[1]*n2s];');
    m.parts.rot.push('r+=' + rS + ';');
    m.parts.op.push('f*=P<0.42?cl(P/0.42*3):1;');
    en2_pivot(m, fS, rS);
    // shudder after the impact (stepped, decaying)
    m.parts.pos.push('if(P>=0.42&&P<1){var n2t=(P-0.42)/0.58,n2d=(1-n2t)*(1-n2t),n2F=Math.floor(time*12),n2x=Math.sin(n2F*12.9898+SD*0.6173+1.7)*43758.5453,n2y=Math.sin(n2F*78.233+SD*0.6173+4.1)*43758.5453;n2x=(n2x-Math.floor(n2x))*2-1;n2y=(n2y-Math.floor(n2y))*2-1;d=[d[0]+n2x*SZ*0.05*n2d,d[1]+n2y*SZ*0.03*n2d];}');
    // impact lines around the box (item space: parented to the text layer)
    var N = jzCount(en2_text(L)) <= 1 ? 6 : 12, S = en2_shape(m, 'JZ Stamp Lines'), Ln = size * 0.42, lw = Math.max(2 * m.u, size * 0.05);
    var cx = r.left + r.width / 2, cy = r.top + r.height / 2, rx = r.width / 2 + size * 0.28, ry = r.height / 2 + size * 0.28, k;
    var lh = m.HD + EN2_FNS + 'var lt=cl((P-0.42)/0.58/0.7),e=oq4(lt);';
    for (k = 0; k < N; k++) {
        var an = (k / N) * Math.PI * 2 + 0.26, c = Math.cos(an), s = Math.sin(an), gr = jzGrp(S, 'line ' + (k + 1));
        jzAddPath(gr, [[0, 0], [Ln, 0]], false);
        jzAddStroke(gr, en2_pick(m.ctx.sc.accent, m.ctx.sc.fg), lw);
        var gx = jzGX(gr);
        gx.property('ADBE Vector Rotation').setValue(an * 180 / Math.PI);
        gx.property('ADBE Vector Position').expression = lh + '[' + jzN(cx + c * rx) + '+' + jzN(c * rx) + '*0.08*e+' + jzN(c * Ln * 0.6) + '*e,' + jzN(cy + s * ry) + '+' + jzN(s * ry) + '*0.08*e+' + jzN(s * Ln * 0.6) + '*e]';
        gx.property('ADBE Vector Scale').expression = lh + '[(1-0.6*e)*100,100]';
    }
    jzSetExpr(jzXf(S, 'ADBE Opacity'), lh + 'P>=0.42&&lt<1?(1-lt)*100:0');
} });

// ================================================================ pack enterB part 1 (AE port of src/11p_enterB.js)
// springIn pendulum rollIn slingshot rockSettle bounceBall snapRail fanOpen cylinder shuffle stopMotion ripple zipper zoomAlt
// tiltUp stickerPeel crumple noteUnfold tornJoin splitFlap overexpose glint loupe filmFeed backlight lightLeak
// Per-glyph motion = text animators with Expression Selectors (amount \u2212100..100 % of the animator's value per glyph, via
// textIndex / textTotal). Graphics the browser draws around the item (pins, rails, balls, lenses \u2026) are shape layers parented
// to the text layer (they follow the layout + hold / exit transforms). Clips the browser does per glyph are made with
// linked copies of the text layer ("twins", parented to it, only alive during the entrance) + masks or track mattes.
// Helper graphics the browser draws with ghost off (main pass only) are marked jzNoGhost; twins that stand for the lyric itself are not.

// ---------------------------------------------------------------- shared helpers (eb1_*)
// easings not in JZ_FNS: o4 outQuart, o5 outQuint, io3 inOutCubic, ios inOutSine, sm smoothstep(a, b, x), spr damped spring (1 \u2192 0)
var EB1_FNS = 'function o4(x){x=cl(x);return 1-Math.pow(1-x,4);}function o5(x){x=cl(x);return 1-Math.pow(1-x,5);}' +
    'function io3(x){x=cl(x);return x<0.5?4*x*x*x:1-Math.pow(-2*x+2,3)/2;}function ios(x){x=cl(x);return -(Math.cos(Math.PI*x)-1)/2;}' +
    'function sm(a,b,x){x=cl((x-a)/(b-a));return x*x*(3-2*x);}' +
    'function spr(t,k,f){t=cl(t);return Math.exp(-k*t)*Math.cos(f*Math.PI*t)*(1-t*t*t);}\n';
// entrance timing of this layer (same numbers as jzHead)
function eb1_T(m) { var c = m.c; return { IN: Math.max(0.02, c.inDur || 0.3), DL: (m.o.mi || 0) * (c.stagger || 0.04) }; }
// compact header for helper layers (IN, DL, SZ, SD, P + easings)
function eb1_head(m) { var t = eb1_T(m); return 'var IN=' + jzN(t.IN) + ',DL=' + jzN(t.DL) + ',SZ=' + jzN(m.size) + ',SD=' + ((m.c.seed % 99991) + (m.o.mi || 0) * 101) + ';' + JZ_FNS + EB1_FNS + 'var P=cl((time-DL)/IN);\n'; }
function eb1_hd(m) { return m.HD + EB1_FNS; }
// browser stg(p, ordLR | ordRL, spread) inside an Expression Selector -> q
function eb1_q(spread, rl) { return 'var n=textTotal,o=n>1?' + (rl ? '1-' : '') + '(textIndex-1)/(n-1):0,q=cl((P-' + jzN(spread) + '*o)/' + jzN(1 - spread) + ');'; }
function eb1_arr(a) { var s = [], i; for (i = 0; i < a.length; i++) s.push(jzN(a[i])); return '[' + s.join(',') + ']'; }
function eb1_isHex(c) { return typeof c === 'string' && c.charAt(0) === '#' && (c.length === 7 || c.length === 4); }
function eb1_pick(a, b, c) { if (eb1_isHex(a)) return a; if (eb1_isHex(b)) return b; if (eb1_isHex(c)) return c; return '#FFFFFF'; }
// the browser's J.h / J.r / J.rs (so seeded directions and per-glyph randoms match the web version)
function eb1_h(a, b, c, d, e) {
    var h = 0x9e3779b9 ^ (a | 0);
    h = jzImul(h ^ (h >>> 16), 0x85ebca6b);
    h = (h + jzImul((b | 0) + 0x632be5ab, 0xc2b2ae35)) | 0;
    h = jzImul(h ^ (h >>> 13), 0xc2b2ae35);
    h = (h + jzImul((c | 0) + 0x5bd1e995, 0x27d4eb2f)) | 0;
    h = jzImul(h ^ (h >>> 15), 0x165667b1);
    h = (h + jzImul((d | 0) + 0x1b873593, 0x85ebca6b)) | 0;
    h = jzImul(h ^ (h >>> 16), 0x27d4eb2f);
    h = (h + jzImul((e | 0) + 0x68e31da4, 0x9e3779b1)) | 0;
    h ^= h >>> 15; h = jzImul(h, 0x2c1b3c6d); h ^= h >>> 12; h = jzImul(h, 0x297a2d39); h ^= h >>> 15;
    return h >>> 0;
}
function eb1_r(a, b, c, d) { return eb1_h(a, b, c, d) / 4294967296; }
function eb1_rs(a, b, c, d) { return eb1_r(a, b, c, d) * 2 - 1; }
function eb1_dir(m, salt) { return eb1_r(m.c.seed | 0, salt, 5) < 0.5 ? -1 : 1; }
function eb1_isSp(ch) { return ch === ' ' || ch === '\u3000' || ch === '\t'; }
function eb1_adv(ch) {
    var c = ch.charCodeAt(0);
    if (ch === ' ') return 0.3;
    if (c >= 0xFF61 && c <= 0xFF9F) return 0.5;
    return c < 0x2000 ? 0.56 : 1;
}
// glyph geometry in the text layer's own space, one entry per AE character (textIndex order, spaces included):
//   G.g[i] {i, li (line), ci (index in line), sp, k (index among non-spaces), x, y (centre), w, h}; G.r source rect; G.fs; G.vert (one glyph per line)
//   G.lines[{cy, x0, x1, n}]; G.n = textTotal; G.nk = glyphs without spaces; G.arranged (layout animators / path); G.masked
function eb1_geo(m) {
    var L = m.L, TP = L.property('ADBE Text Properties'), td = TP.property('ADBE Text Document').value, r = jzRect(L), i, j;
    var fs = td.fontSize || m.size, text = String(td.text), trk = (td.tracking || 0) / 1000 * fs;
    var lines = text.split(/\r\n|\r|\n|\u0003/), nL = lines.length, lead = fs * 1.2;
    try { if (!td.autoLeading && td.leading > 0) lead = td.leading; } catch (e0) {}
    var just = 'c';
    try { if (td.justification === ParagraphJustification.LEFT_JUSTIFY) just = 'l'; else if (td.justification === ParagraphJustification.RIGHT_JUSTIFY) just = 'r'; } catch (e1) {}
    var G = { fs: fs, lead: lead, nL: nL, r: r, n: 0, nk: 0, g: [], lines: [], vert: nL > 1, arranged: false, masked: false };
    try { G.arranged = TP.property('ADBE Text Animators').numProperties > 0; } catch (e2) {}
    try { if (TP.property('ADBE Text Path Options').property('ADBE Text Path').value > 0) G.arranged = true; } catch (e3) {}
    try { G.masked = L.property('ADBE Mask Parade').numProperties > 0; } catch (e4) {}
    var rows = [], maxW = 0;
    for (i = 0; i < nL; i++) {
        var cs = jzChars(lines[i]), ws = [], w = 0;
        for (j = 0; j < cs.length; j++) { var a = eb1_adv(cs[j]) * fs; ws.push(a); w += a + (j < cs.length - 1 ? trk : 0); }
        if (cs.length > 1) G.vert = false;
        rows.push({ cs: cs, ws: ws, w: w }); if (w > maxW) maxW = w;
    }
    var k = maxW > 0 ? r.width / maxW : 1; if (!(k > 0.5 && k < 2)) k = 1;
    var lh = r.height - (nL - 1) * lead; if (!(lh > fs * 0.5 && lh < fs * 1.6)) lh = fs * 0.95;
    var cx = r.left + r.width / 2;
    for (i = 0; i < nL; i++) {
        var R = rows[i], x = just === 'l' ? r.left : (just === 'r' ? r.left + r.width - R.w * k : cx - R.w * k / 2), y = r.top + lh / 2 + i * lead;
        var ln = { cy: y, x0: x, x1: x + R.w * k, n: 0 };
        for (j = 0; j < R.cs.length; j++) {
            var sp = eb1_isSp(R.cs[j]);
            G.g.push({ i: G.n, li: i, ci: j, sp: sp, k: sp ? -1 : G.nk, x: x + R.ws[j] * k / 2, y: y, w: Math.max(1, R.ws[j] * k), h: fs });
            x += (R.ws[j] + trk) * k; G.n++;
            if (!sp) { G.nk++; ln.n++; }
        }
        G.lines.push(ln);
    }
    G.cx = cx; G.cy = r.top + r.height / 2;
    return G;
}
// the lyric's main copy (not a faded / outline copy): helper graphics only go there
function eb1_main(m) {
    try {
        var td = m.L.property('ADBE Text Properties').property('ADBE Text Document').value;
        if (td.applyFill === false) return false;
        return jzXf(m.L, 'ADBE Opacity').value >= 85;
    } catch (e) { return true; }
}
function eb1_matted(L) { try { return L.trackMatteType !== TrackMatteType.NO_TRACK_MATTE; } catch (e) { return false; } }
// helper layers / twins are skipped for very long lines, for one-glyph-per-layer layouts of long lyrics and when the comp is crowded
function eb1_crowded(m, G) {
    if (G && (G.n > 60 || (G.nk <= 1 && jzCount(m.c.text || '') > 14))) return true;
    try { return m.ctx.comp.numLayers > 60; } catch (e) { return false; }
}
// the layer new helper layers go above (above the lyric's own track matte when it has one)
function eb1_top(m) { var L = m.L; if (eb1_matted(L)) { try { return m.ctx.comp.layer(L.index - 1); } catch (e) {} } return L; }
// helper shape layer in the text layer's own space (parented to it, right above it), only alive during the entrance unless keep
function eb1_shape(m, name, keep, below) {
    var L = m.L, S = m.ctx.comp.layers.addShape(), t = eb1_T(m);
    S.name = name;
    try { if (below) S.moveAfter(L); else S.moveBefore(eb1_top(m)); } catch (e0) {}
    S.parent = L;
    var tr = S.property('ADBE Transform Group');
    tr.property('ADBE Anchor Point').setValue([0, 0]); tr.property('ADBE Position').setValue([0, 0]);
    tr.property('ADBE Scale').setValue([100, 100]); tr.property('ADBE Rotate Z').setValue(0);
    if (!keep) { try { S.outPoint = Math.max(S.inPoint + 0.05, Math.min(S.outPoint, t.DL + t.IN + 0.1)); } catch (e1) {} }
    return S;
}
function eb1_sub(g, name) { var s = jzVecs(g).addProperty('ADBE Vector Group'); if (name) s.name = name; return s; }
// linked copy of the text layer: parented to L (follows every move of it), takes L's final text style by expression
// (the treatment is applied later), shows only during the entrance (opacity expression can be replaced)
function eb1_twin(m, tag) {
    var L = m.L, D = L.duplicate(), t = eb1_T(m), i, mns = ['ADBE Position', 'ADBE Scale', 'ADBE Rotate Z', 'ADBE Opacity', 'ADBE Anchor Point'];
    try { D.name = String(L.name).substr(0, 20) + ' ' + (tag || 'JZ In B'); } catch (e0) {}
    var tr = D.property('ADBE Transform Group');
    for (i = 0; i < mns.length; i++) { try { if (tr.property(mns[i]).expression) tr.property(mns[i]).expression = ''; } catch (e1) {} }
    D.parent = L;
    tr.property('ADBE Scale').setValue([100, 100]); tr.property('ADBE Rotate Z').setValue(0);
    jzSetExpr(tr.property('ADBE Position'), 'parent.transform.anchorPoint');
    jzSetExpr(tr.property('ADBE Opacity'), eb1_head(m) + 'time>=DL&&time<DL+IN?value:0');
    try { D.property('ADBE Text Properties').property('ADBE Text Document').expression = 'parent.text.sourceText.style'; } catch (e2) {}
    try { D.outPoint = Math.max(D.inPoint + 0.05, Math.min(D.outPoint, t.DL + t.IN + 0.1)); } catch (e3) {}
    return D;
}
// comp position of a point of L's own space (L's static transform)
function eb1_toComp(L, x, y) {
    var tr = L.property('ADBE Transform Group'), a = tr.property('ADBE Anchor Point').value, p = tr.property('ADBE Position').value;
    var s = tr.property('ADBE Scale').value, r = tr.property('ADBE Rotate Z').value * Math.PI / 180;
    var ux = (x - a[0]) * s[0] / 100, uy = (y - a[1]) * s[1] / 100;
    return [p[0] + Math.cos(r) * ux - Math.sin(r) * uy, p[1] + Math.sin(r) * ux + Math.cos(r) * uy];
}
// keep the point (px, py) of L's own space fixed while the layer is scaled by [fx, fy] and rotated by rot degrees on top of its
// own transform (expression strings) -> snippet for m.parts.pos (the browser scales / rotates items about their own box)
function eb1_pivot(m, fx, fy, rot, px, py) {
    var tr = m.L.property('ADBE Transform Group'), a = tr.property('ADBE Anchor Point').value, s = tr.property('ADBE Scale').value;
    var r0 = tr.property('ADBE Rotate Z').value, rr = r0 * Math.PI / 180;
    var ux = (px - a[0]) * s[0] / 100, uy = (py - a[1]) * s[1] / 100;
    if (Math.abs(ux) + Math.abs(uy) < 0.5) return '';
    var vx = Math.cos(rr) * ux - Math.sin(rr) * uy, vy = Math.sin(rr) * ux + Math.cos(rr) * uy;
    return 'var pv_a=(' + jzN(r0) + '+(' + (rot || '0') + '))*Math.PI/180,pv_c=Math.cos(pv_a),pv_s=Math.sin(pv_a),pv_x=(' + (fx || '1') + ')*' + jzN(ux) + ',pv_y=(' + (fy || '1') + ')*' + jzN(uy) + ';' +
        'd=[d[0]+' + jzN(vx) + '-(pv_c*pv_x-pv_s*pv_y),d[1]+' + jzN(vy) + '-(pv_s*pv_x+pv_c*pv_y)];';
}
// add a stroke to the text document (width 0, grown by a Stroke Width animator) when it has none
function eb1_addStroke(L, hex) {
    var td = L.property('ADBE Text Properties').property('ADBE Text Document').value;
    if (td.applyStroke && td.strokeWidth > 0) return false;
    jzTextDoc(L, function (d) {
        d.applyStroke = true; d.strokeWidth = 0;
        try { d.strokeColor = hex ? jzHex(hex) : d.fillColor; } catch (e) {}
        try { d.strokeOverFill = false; } catch (e2) {}
    });
    return true;
}

/* ================================================================ PHYSICS */

/* ---- springIn \u2014 \u30D0\u30CD: every glyph is fired up from below on a loose spring: overshoots the line, dips back, settles */
jzReg('enter', 'springIn', { selfHide: true, apply: function (m) {
    var L = m.L, G = eb1_geo(m), D = G.fs * 1.7, vert = G.vert;
    var q = eb1_hd(m) + eb1_q(0.45) + 'var f=spr(q,4.2,3),v=(spr(q+0.01,4.2,3)-f)/0.01,st=1+Math.min(0.5,Math.abs(v)*0.075),cp=1/Math.sqrt(st),on=q>0&&q<1;';
    jzAnimator(L, 'JZ In Spring', [['ADBE Text Position 3D', vert ? [-D, 0, 0] : [0, D, 0]]], q + 'var a=on?f*100:0;[a,a,0]');
    jzAnimator(L, 'JZ In Stretch', [['ADBE Text Scale 3D', [200, 200, 100]]], q + 'on?' + (vert ? '[(st-1)*100,(cp-1)*100,0]' : '[(cp-1)*100,(st-1)*100,0]') + ':[0,0,0]');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + 'q<=0?100:(1-cl(q*6))*100');
} });

/* ---- pendulum \u2014 \u632F\u308A\u5B50: glyphs hang from a pin above them and swing into place like hanging tags */
jzReg('enter', 'pendulum', { selfHide: true, apply: function (m) {
    var L = m.L, G = eb1_geo(m), fs = G.fs, dir = eb1_dir(m, 41), Lp = fs * 0.95, i;
    var sw = 'var th=' + dir + '*62*spr(q,3.1,2.4),rr=th*Math.PI/180;';
    var q = eb1_hd(m) + eb1_q(0.45) + sw;
    jzAnimator(L, 'JZ In Swing', [['ADBE Text Rotation', 100]], q + 'q<=0||q>=1?0:th');
    jzAnimator(L, 'JZ In Hang', [['ADBE Text Position 3D', [Lp, Lp, 0]]], q + 'q<=0||q>=1?[0,0,0]:[-Math.sin(rr)*100,(Math.cos(rr)-1)*100,0]');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + 'q<=0?100:(1-cl(q*5))*100');
    if (!eb1_main(m) || G.arranged || eb1_crowded(m, G)) return;
    // the pins and strings (browser postLocal): one group per glyph, pivoting at its pin
    var S = jzNoGhost(eb1_shape(m, 'JZ In Pins')), H = eb1_head(m), lw = Math.max(1.2 * m.u, m.size * 0.012), sl = Lp - fs * 0.5, pr = Math.max(2 * m.u, m.size * 0.035);
    for (i = 0; i < G.n; i++) {
        var g = G.g[i]; if (g.sp) continue;
        var o = G.n > 1 ? i / (G.n - 1) : 0, hq = H + 'var q=cl((P-' + jzN(0.45 * o) + ')/0.55);' + sw;
        var grp = jzGrp(S, 'pin ' + (i + 1)), gx = jzGX(grp);
        var pin = eb1_sub(grp, 'pin'); jzAddEllipse(pin, pr * 2, pr * 2); jzAddFill(pin, m.ctx.sc.accent || '#FFFFFF');
        var str = eb1_sub(grp, 'string'); jzAddRect(str, lw, sl, 0, 0, sl / 2); jzAddFill(str, m.ctx.sc.sub || m.ctx.sc.fg || '#FFFFFF');
        jzGX(str).property('ADBE Vector Group Opacity').setValue(80);
        gx.property('ADBE Vector Position').setValue([g.x, g.y - Lp]);
        jzSetExpr(gx.property('ADBE Vector Rotation'), hq + 'q<=0||q>=1?0:th');
        jzSetExpr(gx.property('ADBE Vector Group Opacity'), hq + 'q<=0||q>=1?0:(1-sm(0.45,0.9,q))*cl(q*5)*100');
    }
} });

/* ---- rollIn \u2014 \u8EE2\u304C\u308A: glyphs roll in along the line like wheels (rotation coupled to the distance travelled) */
jzReg('enter', 'rollIn', { selfHide: true, apply: function (m) {
    var L = m.L, G = eb1_geo(m), vert = G.vert, dir = eb1_dir(m, 43), D = G.fs * 2.3 * (0.75 + 0.4 * m.ctx.fx.motion), rr = [], i;
    for (i = 0; i < G.n; i++) rr.push(Math.max(4, Math.min(G.g[i].w, G.fs) * 0.5));
    // the glyph at the front of the train starts first, so the wheels never roll through each other
    var q = eb1_hd(m) + eb1_q(0.22, dir < 0) + 'var R=' + eb1_arr(rr) + ',rr=R[textIndex-1]||SZ*0.5,off=' + dir + '*' + jzN(D) + '*(1-ob(q,1.25)),rot=-off/rr*180/Math.PI,k=0.72+0.28*oc(q),on=q>0&&q<1;';
    jzAnimator(L, 'JZ In Roll', [['ADBE Text Position 3D', vert ? [0, D, 0] : [D, 0, 0]]], q + 'var a=on?off/' + jzN(D) + '*100:0;[a,a,0]');
    jzAnimator(L, 'JZ In Turn', [['ADBE Text Rotation', 720]], q + 'on?' + (vert ? '-' : '') + 'rot/7.2:0');
    jzAnimator(L, 'JZ In Size', [['ADBE Text Scale 3D', [0, 0, 100]]], q + 'on?(1-k)*100:0');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + 'q<=0?100:(1-cl(q*5))*100');
} });

/* ---- slingshot \u2014 \u30D1\u30C1\u30F3\u30B3: the line is drawn back on two elastic bands, released, overshoots and settles */
jzReg('enter', 'slingshot', { apply: function (m) {
    var L = m.L, G = eb1_geo(m), vert = G.vert, r = G.r, fs = G.fs, pull = m.size * 0.7 + (vert ? r.width : r.height) * 0.35;
    var sc = jzXf(L, 'ADBE Scale').value, s0 = Math.abs((vert ? sc[0] : sc[1]) / 100) || 1, pullW = pull * s0;
    var ev = EB1_FNS + 'var sg_f,sg_al=1,sg_ac=1,sg_a=1;if(P<0.36){var sg_t=P/0.36,sg_e=oc(sg_t);sg_f=0.2+0.8*sg_e;sg_a=cl(sg_t*3);sg_al=1-0.14*sg_e;sg_ac=1+0.07*sg_e;}' +
        'else{var sg_t=(P-0.36)/0.64,sg_v=(spr(sg_t+0.01,4.6,2.6)-spr(sg_t,4.6,2.6))/0.01;sg_f=spr(sg_t,4.6,2.6);sg_al=1+Math.min(0.4,Math.abs(sg_v)*0.05);sg_ac=1/Math.sqrt(sg_al);}' +
        'var sg_x=' + (vert ? 'sg_al' : 'sg_ac') + ',sg_y=' + (vert ? 'sg_ac' : 'sg_al') + ';';
    m.parts.sc.push(ev + 'f=[f[0]*sg_x,f[1]*sg_y];');
    m.parts.pos.push(ev + eb1_pivot(m, 'sg_x', 'sg_y', '0', G.cx, G.cy) + 'd=[d[0]' + (vert ? '+sg_f*' + jzN(pullW) : '') + ',d[1]' + (vert ? '' : '+sg_f*' + jzN(pullW)) + '];');
    m.parts.op.push(ev + 'f*=sg_a;');
    if (!eb1_main(m) || G.nk <= 1 || eb1_crowded(m, G)) return;
    // the two elastic bands: from fixed posts beside the rest position to the corners of the moving line
    var S = jzNoGhost(eb1_shape(m, 'JZ In Bands')), H = eb1_head(m), mg = m.size * 0.35, lw = Math.max(1.5 * m.u, m.size * 0.03) / s0, col = m.ctx.sc.accent || '#FFFFFF', k;
    var x0 = r.left, x1 = r.left + r.width, y0 = r.top, y1 = r.top + r.height, cxx = (x0 + x1) / 2, cyy = (y0 + y1) / 2;
    var posts = vert ? [[cxx, y0 - mg], [cxx, y1 + mg]] : [[x0 - mg, cyy], [x1 + mg, cyy]];
    var ends = vert ? [[x1, y0 + (y1 - y0) * 0.15], [x1, y1 - (y1 - y0) * 0.15]] : [[x0 + (x1 - x0) * 0.12, y1], [x1 - (x1 - x0) * 0.12, y1]];
    var bd = 'var t2=(P-0.36)/0.64,bd=P<0.36?cl(P/0.36*3):1-sm(0.03,0.2,t2);';
    for (k = 0; k < 2; k++) {
        var W0 = eb1_toComp(L, posts[k][0], posts[k][1]);
        var pts = 'var A=parent.fromComp([' + jzN(W0[0]) + ',' + jzN(W0[1]) + ']),ax=A[0],ay=A[1],bx=' + jzN(ends[k][0]) + ',by=' + jzN(ends[k][1]) + ';';
        var grp = jzGrp(S, 'band ' + (k + 1)), gx = jzGX(grp);
        var dot = eb1_sub(grp, 'post'); jzAddEllipse(dot, lw * 3.2, lw * 3.2); jzAddFill(dot, col);
        // (each shape item is fully set up before the next item is added to the same contents: AE invalidates older references)
        var band = eb1_sub(grp, 'band'), rc = jzAddRect(band, 10, lw, 0);
        // group sits on the post, the band rect points at the line's corner
        jzSetExpr(rc.property('ADBE Vector Rect Size'), H + pts + '[Math.sqrt((bx-ax)*(bx-ax)+(by-ay)*(by-ay)),' + jzN(lw) + ']');
        jzSetExpr(rc.property('ADBE Vector Rect Position'), H + pts + '[Math.sqrt((bx-ax)*(bx-ax)+(by-ay)*(by-ay))/2,0]');
        jzAddFill(band, col);
        jzSetExpr(gx.property('ADBE Vector Position'), H + pts + '[ax,ay]');
        jzSetExpr(jzGX(band).property('ADBE Vector Rotation'), H + pts + 'Math.atan2(by-ay,bx-ax)*180/Math.PI');
        jzSetExpr(gx.property('ADBE Vector Group Opacity'), H + bd + 'bd*100');
    }
} });

/* ---- rockSettle \u2014 \u3050\u3089\u3050\u3089\u7740\u5730: glyphs drop in tilted, land on a corner and rock side to side until they sit flat */
jzReg('enter', 'rockSettle', { selfHide: true, apply: function (m) {
    var L = m.L, G = eb1_geo(m), seed = m.c.seed | 0, TH = [], HW = [], i, K = G.fs * 2.6;
    for (i = 0; i < G.n; i++) { TH.push((eb1_r(seed, i, 45) < 0.5 ? -1 : 1) * (16 + 10 * eb1_r(seed, i, 46))); HW.push(G.g[i].w / 2); }
    var q = eb1_hd(m) + eb1_q(0.4) + 'var TH=' + eb1_arr(TH) + ',HW=' + eb1_arr(HW) + ',t0=TH[textIndex-1]||0,hw=HW[textIndex-1]||SZ*0.5,hy=' + jzN(G.fs * 0.5) + ',th,dy=0;' +
        'if(q<0.3){var t=q/0.3;th=t0;dy=-(1-t*t)*hy*4;}else{var t=(q-0.3)/0.7;th=t0*Math.pow(1-t,1.5)*Math.cos(Math.PI*(1.4*t+1.9*t*t));}' +
        'var rr=th*Math.PI/180,c=Math.cos(rr),s=Math.sin(rr),cx=th>=0?hw:-hw,cy=hy,dx=cx-(cx*c-cy*s),dy2=dy+cy-(cx*s+cy*c),on=q>0&&q<1;';
    jzAnimator(L, 'JZ In Rock', [['ADBE Text Rotation', 100]], q + 'on?th:0');
    jzAnimator(L, 'JZ In Pivot', [['ADBE Text Position 3D', [K, K, 0]]], q + 'on?[dx/' + jzN(K) + '*100,dy2/' + jzN(K) + '*100,0]:[0,0,0]');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + 'q<=0?100:(1-cl(q*8))*100');
} });

/* ---- bounceBall \u2014 \u30D0\u30A6\u30F3\u30C9\u30DC\u30FC\u30EB: karaoke ball hops from glyph to glyph, each landing kicks its glyph into place */
jzReg('enter', 'bounceBall', { selfHide: true, apply: function (m) {
    var L = m.L, G = eb1_geo(m), vert = G.vert, fs = G.fs, n = G.nk, HT = [], i;
    if (!n) return;
    var hit = function (k) { return n > 1 ? 0.16 + 0.6 * k / (n - 1) : 0.45; };
    for (i = 0; i < G.n; i++) HT.push(G.g[i].sp ? -1 : hit(G.g[i].k));
    var q = eb1_hd(m) + 'var HT=' + eb1_arr(HT) + ',h=HT[textIndex-1],u=(P-h)/0.2,on=h>=0&&P>=h&&u<1,sq=0.55+0.45*ob(u,2.4),st=1+0.3*(1-o5(u));';
    jzAnimator(L, 'JZ In Squash', [['ADBE Text Scale 3D', [200, 200, 100]]], q + 'on?' + (vert ? '[(sq-1)*100,(st-1)*100,0]' : '[(st-1)*100,(sq-1)*100,0]') + ':[0,0,0]');
    jzAnimator(L, 'JZ In Land', [['ADBE Text Position 3D', vert ? [fs / 2, 0, 0] : [0, fs / 2, 0]]], q + 'var a=on?(1-sq)*100:0;[a,a,0]');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + 'h>=0&&P<h?100:0');
    if (!eb1_main(m) || G.arranged || eb1_crowded(m, G)) return;
    // the ball: contact point on top of each glyph (right edge for vertical text)
    var R = Math.max(3 * m.u, m.size * 0.13), X = [], Y = [];
    for (i = 0; i < G.n; i++) { var g = G.g[i]; if (g.sp) continue; if (vert) { X.push(g.x + g.w * 0.5 + R); Y.push(g.y); } else { X.push(g.x); Y.push(g.y - fs * 0.5 - R); } }
    var S = jzNoGhost(eb1_shape(m, 'JZ In Ball')), grp = jzGrp(S, 'ball'), gx = jzGX(grp);
    jzAddEllipse(grp, R * 2, R * 2); jzAddFill(grp, m.ctx.sc.accent || '#FFFFFF');
    var uX = vert ? 1 : 0, uY = vert ? 0 : -1, fX = vert ? 0 : 1, fY = vert ? 1 : 0, Z = jzN(m.size);
    var bb = eb1_head(m) + 'var X=' + eb1_arr(X) + ',Y=' + eb1_arr(Y) + ',n=' + n + ',S=' + Z + ',H=S*0.6,ux=' + uX + ',uy=' + uY + ',fx=' + fX + ',fy=' + fY + ',solo=n==1;' +
        'function hit(k){return n>1?0.16+0.6*k/(n-1):0.45;}var ax,ay,bx,by,s,al=1;' +
        'if(P<hit(0)){var back=solo?0:1.3,t0=solo?hit(0)*0.2:0;bx=X[0];by=Y[0];ax=solo?bx:bx-fx*S*back+ux*S*0.5;ay=solo?by:by-fy*S*back+uy*S*0.5;s=(P-t0)/(hit(0)-t0);al=cl(s*6);}' +
        'else if(P>=hit(n-1)){ax=X[n-1];ay=Y[n-1];bx=ax+fx*S*1.4-ux*S*0.2;by=ay+fy*S*1.4-uy*S*0.2;s=(P-hit(n-1))/0.2;al=1-sm(0.4,1,s);}' +
        'else{var k=1;while(k<n-1&&P>=hit(k))k++;ax=X[k-1];ay=Y[k-1];bx=X[k];by=Y[k];s=(P-hit(k-1))/(hit(k)-hit(k-1));}' +
        'var arc=solo&&P<hit(0)?S*0.85*(1-s*s):4*H*s*(1-s);';
    jzSetExpr(gx.property('ADBE Vector Position'), bb + '[ax+(bx-ax)*s+ux*arc,ay+(by-ay)*s+uy*arc]');
    jzSetExpr(gx.property('ADBE Vector Group Opacity'), bb + 's<=0||s>1?0:al*100');
} });

/* ---- snapRail \u2014 \u30B9\u30CA\u30C3\u30D7\u6574\u5217: glyphs float off the line at random heights; an accent rail draws in and they all snap onto it */
jzReg('enter', 'snapRail', { selfHide: true, apply: function (m) {
    var L = m.L, G = eb1_geo(m), vert = G.vert, seed = m.c.seed | 0, DD = [], TS = [], SG = [], OF = [], RT = [], i, K = m.size;
    for (i = 0; i < G.n; i++) {
        DD.push(eb1_r(seed, i, 53) * 0.2); TS.push(0.46 + eb1_r(seed, i, 54) * 0.06); SG.push(eb1_r(seed, i, 51) < 0.5 ? -1 : 1);
        OF.push((eb1_r(seed, i, 51) < 0.5 ? -1 : 1) * (0.3 + 0.6 * eb1_r(seed, i, 52))); RT.push(eb1_rs(seed, i, 55) * 16);
    }
    var q = eb1_hd(m) + 'var i=textIndex-1,DD=' + eb1_arr(DD) + ',TS=' + eb1_arr(TS) + ',SG=' + eb1_arr(SG) + ',OF=' + eb1_arr(OF) + ',RT=' + eb1_arr(RT) + ';' +
        'var a=cl((P-DD[i])/0.2),ts=TS[i],sg=SG[i],o0=OF[i]*SZ,r0=RT[i],off=0,rot=0,sq=1;' +
        'if(P<ts){var k=1-0.14*(P/ts);off=o0*k;rot=r0*k;}else{var t=(P-ts)/0.13;if(t<1){var e=ic(t);off=o0*0.86*(1-e);rot=r0*0.86*(1-e);}' +
        'else{var u=cl((t-1)/1.6);if(u<1){off=-sg*SZ*0.06*Math.sin(Math.PI*u)*(1-u);sq=1-0.16*Math.sin(Math.PI*cl(u*2))*(1-u);}}}var on=a>0;';
    jzAnimator(L, 'JZ In Float', [['ADBE Text Position 3D', vert ? [K, 0, 0] : [0, K, 0]]], q + 'var v=on?off/' + jzN(K) + '*100:0;[v,v,0]');
    jzAnimator(L, 'JZ In Tilt', [['ADBE Text Rotation', 100]], q + 'on?rot:0');
    jzAnimator(L, 'JZ In Snap', [['ADBE Text Scale 3D', [200, 200, 100]]], q + 'var c=1/Math.sqrt(sq);on?' + (vert ? '[(sq-1)*100,(c-1)*100,0]' : '[(c-1)*100,(sq-1)*100,0]') + ':[0,0,0]');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + '(1-a)*100');
    if (!eb1_main(m) || G.nk <= 1 || G.arranged || eb1_crowded(m, G)) return;
    // the rail(s): one accent bar per line, just under it (left of a vertical column)
    var S = jzNoGhost(eb1_shape(m, 'JZ In Rail')), H = eb1_head(m), t = Math.max(2 * m.u, m.size * 0.028), acc = m.ctx.sc.accent || '#FFFFFF', r = G.r;
    var ev = 'var grow=o4(P/0.4),fl=Math.exp(-Math.pow((P-0.6)/0.05,2)),gone=sm(0.68,0.95,P),th=' + jzN(t) + '*(1+fl*1.2);';
    var rails = [];
    if (vert) rails.push({ c: r.top + r.height / 2, half: r.height / 2 + m.size * 0.15, v: G.cx - m.size * 0.62 });
    else for (i = 0; i < G.lines.length; i++) { var ln = G.lines[i]; if (ln.n) rails.push({ c: (ln.x0 + ln.x1) / 2, half: (ln.x1 - ln.x0) / 2 + m.size * 0.15, v: ln.cy + m.size * 0.62 }); }
    for (i = 0; i < rails.length; i++) {
        var R = rails[i], grp = jzGrp(S, 'rail ' + (i + 1)), rc = jzAddRect(grp, 10, 10, 0), len = jzN(R.half) + '*grow*(1-gone)*2';
        jzSetExpr(rc.property('ADBE Vector Rect Size'), H + ev + (vert ? '[th,' + len + ']' : '[' + len + ',th]'));   // before the fill is added (keeps rc valid)
        jzAddFill(grp, acc);
        jzGX(grp).property('ADBE Vector Position').setValue(vert ? [R.v, R.c] : [R.c, R.v]);
        jzSetExpr(jzGX(grp).property('ADBE Vector Group Opacity'), H + ev + 'grow>0&&gone<1?100:0');
    }
} });

/* ---- fanOpen \u2014 \u6247\u958B\u304D: glyphs start stacked like the ribs of a closed folding fan and sweep open around a pivot */
jzReg('enter', 'fanOpen', { apply: function (m) {
    var L = m.L, G = eb1_geo(m), vert = G.vert, r = G.r, sz = m.size, i;
    // pivot under the start of the line (left of the top of a vertical column)
    var R0 = sz * 0.8 + (vert ? r.width : r.height) / 2;
    var pv = vert ? [r.left - R0, r.top + sz * 0.5] : [r.left + sz * 0.5, r.top + r.height + R0];
    var VX = [], VY = [], DA = [], aMin = 1e9, big = 0;
    for (i = 0; i < G.n; i++) {
        var vx = G.g[i].x - pv[0], vy = G.g[i].y - pv[1];
        VX.push(vx); VY.push(vy); aMin = Math.min(aMin, Math.atan2(vy, vx)); big = Math.max(big, Math.sqrt(vx * vx + vy * vy));
    }
    var a0 = aMin - 0.45;
    for (i = 0; i < G.n; i++) DA.push(a0 - Math.atan2(VY[i], VX[i]));
    var K = big * 2.2 + 1;
    var q = eb1_hd(m) + 'var i=textIndex-1,VX=' + eb1_arr(VX) + ',VY=' + eb1_arr(VY) + ',DA=' + eb1_arr(DA) + ',e=ob(P,1.15),dd=(DA[i]||0)*(1-e),c=Math.cos(dd),s=Math.sin(dd),vx=VX[i]||0,vy=VY[i]||0;';
    jzAnimator(L, 'JZ In Fan', [['ADBE Text Position 3D', [K, K, 0]]], q + '[(c*vx-s*vy-vx)/' + jzN(K) + '*100,(s*vx+c*vy-vy)/' + jzN(K) + '*100,0]');
    jzAnimator(L, 'JZ In Rib', [['ADBE Text Rotation', 360]], q + 'dd*180/Math.PI/3.6');
    m.parts.op.push('f*=cl(P*3.5);');
} });

/* ---- cylinder \u2014 \u5186\u7B52\u56DE\u8EE2: the line is printed on a turning drum: glyphs wrap round from the back, then the drum unrolls flat */
jzReg('enter', 'cylinder', { apply: function (m) {
    var L = m.L, G = eb1_geo(m), vert = G.vert, r = G.r, i;
    var U0 = vert ? r.top : r.left, U1 = U0 + (vert ? r.height : r.width), uc = (U0 + U1) / 2, R = Math.max(m.size * 0.6, (U1 - U0) / 2) / 1.2;
    var U = [], mx = 0;
    for (i = 0; i < G.n; i++) { var u = (vert ? G.g[i].y : G.g[i].x) - uc; U.push(u); mx = Math.max(mx, Math.abs(u)); }
    var K = R + mx + 1, bg = eb1_pick(m.ctx.sc.bg, '#000000');
    var q = eb1_hd(m) + 'var U=' + eb1_arr(U) + ',u=U[textIndex-1]||0,R=' + jzN(R) + ',phi=' + eb1_dir(m, 61) + '*(1-o4(P))*2.4,fl=sm(0.5,1,P),A=cl(P*6);' +
        'var th=u/R+phi,cu=Math.cos(th),hide=cu<=0.04&&fl<0.5,sn=R*Math.sin(th),nu=sn+(u-sn)*fl,k0=Math.max(0.04,cu),k=k0+(1-k0)*fl,c1=cl(cu*1.6),c2=cl(cu);';
    jzAnimator(L, 'JZ In Drum', [['ADBE Text Position 3D', vert ? [0, K, 0] : [K, 0, 0]]], q + 'var a=(nu-u)/' + jzN(K) + '*100;[a,a,0]');
    jzAnimator(L, 'JZ In Wrap', [['ADBE Text Scale 3D', vert ? [100, 0, 100] : [0, 100, 100]]], q + '(1-k)*100');
    jzAnimator(L, 'JZ In Side', [['ADBE Text Fill Color', jzHex(bg)]], q + '(1-(c2+(1-c2)*fl))*60');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + 'hide?100:(1-A*(c1+(1-c1)*fl))*100');
} });

/* ---- shuffle \u2014 \u30B7\u30E3\u30C3\u30D5\u30EB: glyphs appear in shuffled slots, then trade places along over/under arcs like a shell game */
jzReg('enter', 'shuffle', { apply: function (m) {
    var L = m.L, G = eb1_geo(m), vert = G.vert, seed = m.c.seed | 0, ids = [], arr = [], i, k;
    for (i = 0; i < G.n; i++) if (!G.g[i].sp) { ids.push(i); arr.push(i); }
    var n = ids.length;
    for (k = n - 1; k > 0; k--) { var j = Math.floor(eb1_r(seed, k, 71) * (k + 1)), tt = arr[k]; arr[k] = arr[j]; arr[j] = tt; }
    var same = true; for (k = 0; k < n; k++) if (arr[k] !== ids[k]) same = false;
    if (n > 1 && same) arr.push(arr.shift());
    var EX = [], EY = [], NX = [], NY = [], HM = [], SG = [], RQ = [], big = 1;
    for (i = 0; i < G.n; i++) { EX.push(0); EY.push(0); NX.push(0); NY.push(0); HM.push(0); SG.push(0); RQ.push(eb1_r(seed, i, 72)); }
    for (k = 0; k < n; k++) {
        var t = ids[k], s = arr[k], ex = G.g[s].x - G.g[t].x, ey = G.g[s].y - G.g[t].y, d = Math.sqrt(ex * ex + ey * ey);
        if (d < 0.5) continue;
        var sg = (vert ? ey : ex) > 0 ? 1 : -1, vs = vert ? -1 : 1;
        EX[t] = ex; EY[t] = ey; NX[t] = -ey / d * sg * vs; NY[t] = ex / d * sg * vs; HM[t] = Math.min(m.size * 0.85, d * 0.4); SG[t] = sg;
        big = Math.max(big, Math.abs(ex) + HM[t], Math.abs(ey) + HM[t]);
    }
    var K = big * 1.1;
    var q = eb1_hd(m) + 'var i=textIndex-1,EX=' + eb1_arr(EX) + ',EY=' + eb1_arr(EY) + ',NX=' + eb1_arr(NX) + ',NY=' + eb1_arr(NY) + ',HM=' + eb1_arr(HM) + ',SG=' + eb1_arr(SG) + ',RQ=' + eb1_arr(RQ) + ';' +
        'var q=cl((cl((P-0.16)/0.84)-0.3*RQ[i])/0.7),e=io3(q),mv=HM[i]>0,h=HM[i]*Math.sin(Math.PI*e),on=q<1;';
    jzAnimator(L, 'JZ In Swap', [['ADBE Text Position 3D', [K, K, 0]]], q + 'on&&mv?[(EX[i]*(1-e)+NX[i]*h)/' + jzN(K) + '*100,(EY[i]*(1-e)+NY[i]*h)/' + jzN(K) + '*100,0]:[0,0,0]');
    jzAnimator(L, 'JZ In Arc', [['ADBE Text Scale 3D', [200, 200, 100]]], q + 'var s=!on?1:(mv?1+SG[i]*0.14*Math.sin(Math.PI*e):0.8+0.2*ob(q,2));(s-1)*100');
    m.parts.op.push('f*=cl(P/0.12);');
} });

/* ---- stopMotion \u2014 \u30B3\u30DE\u64AE\u308A: glyphs jump toward their place in a handful of held key poses, each slightly off */
jzReg('enter', 'stopMotion', { selfHide: true, apply: function (m) {
    var L = m.L, K = m.size * 1.3;
    var q = eb1_hd(m) + 'var i=textIndex,n=textTotal,o=n>1?(i-1)/(n-1):0,q=cl((P-0.45*(hh(i*13+SD)*0.5+o*0.5))/0.55),k=Math.min(4,Math.floor(q*5)),on=q>0&&k<4,f=1-oc(k/4);' +
        'function j(s){return hh(i*31+k*7+s+SD)*2-1;}var an=hh(i*17+SD+1)*Math.PI*2,D=SZ*(0.7+0.5*hh(i*19+SD+2));';
    jzAnimator(L, 'JZ In Pose', [['ADBE Text Position 3D', [K, K, 0]]], q + 'on?[(Math.cos(an)*D*f+j(84)*SZ*0.05)/' + jzN(K) + '*100,(Math.sin(an)*D*f+j(85)*SZ*0.05)/' + jzN(K) + '*100,0]:[0,0,0]');
    jzAnimator(L, 'JZ In Tilt', [['ADBE Text Rotation', 30]], q + 'on?(j(86)*22*f+(hh(i*23+SD+87)*2-1)*6*f)/0.3:0');
    jzAnimator(L, 'JZ In Size', [['ADBE Text Scale 3D', [200, 200, 100]]], q + 'on?j(88)*14*f:0');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + 'q<=0?100:0');
} });

/* ---- ripple \u2014 \u6CE2\u7D0B: a ring wave spreads from the middle; each glyph surfaces as the crest passes and bobs once */
jzReg('enter', 'ripple', { selfHide: true, apply: function (m) {
    var L = m.L, G = eb1_geo(m), r = G.r, i, VX = [], VY = [], DD = [];
    var Rmax = Math.sqrt(r.width * r.width + r.height * r.height) / 2 + m.size * 0.3, K = m.size * 0.3;
    for (i = 0; i < G.n; i++) { var vx = G.g[i].x - G.cx, vy = G.g[i].y - G.cy; VX.push(vx); VY.push(vy); DD.push(Math.sqrt(vx * vx + vy * vy)); }
    var q = eb1_hd(m) + 'var i=textIndex-1,VX=' + eb1_arr(VX) + ',VY=' + eb1_arr(VY) + ',DD=' + eb1_arr(DD) + ',d=DD[i]||0,q=cl((P-0.68*d/' + jzN(Rmax) + ')/0.32),on=q>0&&q<1;' +
        'var w=Math.sin(q*Math.PI*2)*Math.pow(1-q,2),pu=SZ*0.28*w,s=1+0.4*Math.sin(q*Math.PI)*(1-q);';
    jzAnimator(L, 'JZ In Wave', [['ADBE Text Position 3D', [K, K, 0]]], q + 'on&&d>1?[(VX[i]||0)/d*pu/' + jzN(K) + '*100,(VY[i]||0)/d*pu/' + jzN(K) + '*100,0]:[0,0,0]');
    jzAnimator(L, 'JZ In Bob', [['ADBE Text Scale 3D', [200, 200, 100]]], q + 'on?(s-1)*100:0');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + 'q<=0?100:(1-cl(q*5))*100');
    if (!eb1_main(m) || G.arranged || eb1_crowded(m, G)) return;
    // two accent rings spreading from the centre
    var S = jzNoGhost(eb1_shape(m, 'JZ In Rings')), H = eb1_head(m), lw = Math.max(1.5 * m.u, m.size * 0.022), k;
    for (k = 0; k < 2; k++) {
        var ev = 'var f=cl(P/0.68-' + jzN(k * 0.12) + '),rd=' + jzN(Rmax * 1.05) + '*f;';
        var grp = jzGrp(S, 'ring ' + (k + 1)), gx = jzGX(grp), el = jzAddEllipse(grp, 10, 10);
        jzSetExpr(el.property('ADBE Vector Ellipse Size'), H + ev + '[rd*2,rd*2]');    // before the stroke is added (keeps el valid)
        var st = jzAddStroke(grp, m.ctx.sc.accent || '#FFFFFF', lw);
        jzSetExpr(st.property('ADBE Vector Stroke Width'), H + ev + jzN(lw) + '*(1-f*0.5)');
        gx.property('ADBE Vector Position').setValue([G.cx, G.cy]);
        jzSetExpr(gx.property('ADBE Vector Group Opacity'), H + ev + 'f<=0||rd<1?0:(1-f)*' + (k ? 50 : 85));
    }
} });

/* ---- zipper \u2014 \u30B8\u30C3\u30D1\u30FC: a zip slider runs along the line; ahead of it the glyphs are split up/down like open teeth */
jzReg('enter', 'zipper', { apply: function (m) {
    var L = m.L, G = eb1_geo(m), vert = G.vert, r = G.r, sz = m.size, i, UU = [], SG = [];
    var U0 = vert ? r.top : r.left, U1 = U0 + (vert ? r.height : r.width), A = sz * 0.8, solo = G.nk <= 1;
    for (i = 0; i < G.n; i++) { UU.push(vert ? G.g[i].y : G.g[i].x); SG.push(((solo ? (m.o.mi || 0) : G.g[i].ci) % 2) ? 1 : -1); }
    var sl = 'var s=' + jzN(U0 - sz * 0.3) + '+' + jzN(U1 + sz * 0.4 - (U0 - sz * 0.3)) + '*ios(cl(P/0.92)),fd=cl(P*5);';
    var q = eb1_hd(m) + sl + 'var UU=' + eb1_arr(UU) + ',SG=' + eb1_arr(SG) + ',i=textIndex-1,o=cl(((UU[i]||0)-s)/' + jzN(sz * 2.4) + '+0.25),sg=SG[i]||1;';
    jzAnimator(L, 'JZ In Teeth', [['ADBE Text Position 3D', vert ? [A, 0, 0] : [0, A, 0]]], q + 'var a=o>0?sg*Math.pow(o,0.8)*100:0;[a,a,0]');
    jzAnimator(L, 'JZ In Tilt', [['ADBE Text Rotation', 14]], q + 'o>0?' + (vert ? '-' : '') + 'sg*o*100:0');
    jzAnimator(L, 'JZ In Fade', [['ADBE Text Opacity', 0]], q + 'o>0?(1-fd)*100:0');
    if (!eb1_main(m) || solo || G.arranged || eb1_crowded(m, G)) return;
    // the slider (+ pull tab) on every line
    var S = jzNoGhost(eb1_shape(m, 'JZ In Zip')), H = eb1_head(m), w = sz * 0.3, h = sz * 0.46, tab = sz * 0.2, acc = m.ctx.sc.accent || '#FFFFFF', vs = [];
    if (vert) vs.push(G.cx); else for (i = 0; i < G.lines.length; i++) if (G.lines[i].n) vs.push(G.lines[i].cy);
    for (i = 0; i < vs.length; i++) {
        var grp = jzGrp(S, 'slider ' + (i + 1)), gx = jzGX(grp);
        if (vert) { jzAddRect(grp, h, w, w * 0.2); jzAddRect(grp, tab, tab * 0.5, 0, h / 2 + tab / 2, 0); }
        else { jzAddRect(grp, w, h, w * 0.2); jzAddRect(grp, tab * 0.5, tab, 0, 0, h / 2 + tab / 2); }
        jzAddFill(grp, acc);
        jzSetExpr(gx.property('ADBE Vector Position'), H + sl + (vert ? '[' + jzN(vs[i]) + ',s]' : '[s,' + jzN(vs[i]) + ']'));
        jzSetExpr(gx.property('ADBE Vector Group Opacity'), H + sl + 'fd*(1-sm(0.82,0.98,P))*100');
    }
} });

/* ---- zoomAlt \u2014 \u4EA4\u4E92\u30BA\u30FC\u30E0: depth alternates: odd glyphs dive in from in front of the lens, even ones grow from far away */
jzReg('enter', 'zoomAlt', { selfHide: true, apply: function (m) {
    var L = m.L, G = eb1_geo(m), PA = [], i, bl = Math.min(30, m.size * 0.1), solo = G.nk <= 1;
    for (i = 0; i < G.n; i++) PA.push(((solo ? (m.o.mi || 0) : G.g[i].ci)) % 2);
    var q = eb1_hd(m) + eb1_q(0.35) + 'var PA=' + eb1_arr(PA) + ',ev=PA[textIndex-1]==0,e=o5(q),on=q>0&&q<1,s=ev?2.4+(1-2.4)*e:Math.max(0.02,ob(q,2.2)),a=ev?cl(q*2.5):cl(q*4);';
    jzAnimator(L, 'JZ In Depth', [['ADBE Text Scale 3D', [300, 300, 100]]], q + 'on?(s-1)*50:0');
    jzAnimator(L, 'JZ In Focus', [['ADBE Text Blur', [bl, bl]]], q + 'on&&ev?(1-e)*100:0');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + 'q<=0?100:(1-a)*100');
} });

/* ---- tiltUp \u2014 \u8D77\u7ACB: the line lies flat on the floor and swings up on a hinge along its bottom edge */
jzReg('enter', 'tiltUp', { apply: function (m) {
    var L = m.L, G = eb1_geo(m), r = G.r, sz = m.size, bg = eb1_pick(m.ctx.sc.bg, '#000000');
    var kk = EB1_FNS + 'var tu_k=Math.max(0.03,Math.cos(86*spr(P,3.2,1.25)*Math.PI/180));';
    var y1 = r.top + r.height;
    m.parts.sc.push(kk + 'f=[f[0],f[1]*tu_k];');
    m.parts.pos.push(kk + eb1_pivot(m, '1', 'tu_k', '0', G.cx, y1));
    m.parts.op.push('f*=cl(P*6);');
    jzAnimator(L, 'JZ In Floor', [['ADBE Text Fill Color', jzHex(bg)]], eb1_hd(m) + kk + '(1-tu_k)*55');
    if (!eb1_main(m) || eb1_crowded(m, G)) return;
    // accent floor line under the hinge (counter-scaled against the layer's squash)
    var S = jzNoGhost(eb1_shape(m, 'JZ In Hinge')), H = eb1_head(m), mg = sz * 0.2, t = Math.max(1.5 * m.u, sz * 0.025), grp = jzGrp(S, 'floor'), gx = jzGX(grp);
    jzAddRect(grp, r.width + mg * 2, t, 0); jzAddFill(grp, m.ctx.sc.accent || '#FFFFFF');
    jzSetExpr(gx.property('ADBE Vector Position'), H + kk + '[' + jzN(G.cx) + ',' + jzN(y1) + '+' + jzN(t * 1.5) + '/tu_k]');
    jzSetExpr(gx.property('ADBE Vector Scale'), H + kk + '[100,100/tu_k]');
    jzSetExpr(gx.property('ADBE Vector Group Opacity'), H + '(1-sm(0.35,0.8,P))*cl(P*6)*100');
} });

/* ================================================================ PAPER */

/* ---- crumple \u2014 \u304F\u3057\u3083\u623B\u308A: each glyph arrives as a crumpled ball and springs open flat
   (browser: every stroke piece is pulled in and twisted; here: glyph scale + twist per glyph and a decaying Turbulent Displace crinkle) */
jzReg('enter', 'crumple', { selfHide: true, apply: function (m) {
    var L = m.L, q = eb1_hd(m) + eb1_q(0.45) + 'var e=ob(q,1.6),on=q>0&&q<1;';
    jzAnimator(L, 'JZ In Ball', [['ADBE Text Scale 3D', [0, 0, 100]]], q + 'on?(1-(0.3+0.7*e))*100:0');
    jzAnimator(L, 'JZ In Twist', [['ADBE Text Rotation', 120]], q + 'on?(hh(textIndex*29+SD)*2-1)*(1-e)*100:0');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + 'q<=0?100:(1-cl(q*4))*100');
    var td = jzEffect(L, 'ADBE Turbulent Displace', 'JZ In Crumple');
    jzEP(td, 1, 1); jzEP(td, 3, Math.max(8, m.size * 0.22)); jzEP(td, 5, 2); jzEP(td, 6, (m.c.seed % 360));
    jzEX(td, 2, eb1_hd(m) + 'P>=1?0:SZ*0.5*Math.pow(1-sm(0,0.95,P),1.5)');
} });

/* ================================================================ LIGHT */

/* ---- overexpose \u2014 \u9732\u51FA\u30AA\u30FC\u30D0\u30FC: the line flashes in blown-out white (bloom + glow), then the exposure is pulled down to its real colour */
jzReg('enter', 'overexpose', { apply: function (m) {
    var L = m.L, G = eb1_geo(m), sc = m.ctx.sc, sz = m.size;
    var hot = jzLum(eb1_pick(sc.bg, '#000000')) < 0.5 ? '#FFFFFF' : eb1_pick(sc.accent, sc.fg);
    var ex = EB1_FNS + 'var ox_e=P<0.12?1:1-ios((P-0.12)/0.88);', hd = eb1_hd(m) + ex;
    m.parts.op.push('f*=P<0.12?oc(P/0.12):1;');
    m.parts.sc.push(ex + 'f=[f[0]*(1+0.06*ox_e),f[1]*(1+0.06*ox_e)];');
    m.parts.pos.push(ex + eb1_pivot(m, '1+0.06*ox_e', '1+0.06*ox_e', '0', G.cx, G.cy));
    jzAnimator(L, 'JZ In Burn', [['ADBE Text Fill Color', jzHex(hot)]], hd + 'ox_e*96');
    eb1_addStroke(L, hot);
    jzAnimator(L, 'JZ In Bloom', [['ADBE Text Stroke Width', sz * 0.045], ['ADBE Text Stroke Color', jzHex(hot)]], hd + 'ox_e>0.02?ox_e*100:0');
    var bl = Math.min(14, sz * 0.04);
    jzAnimator(L, 'JZ In Soft', [['ADBE Text Blur', [bl, bl]]], hd + 'ox_e*ox_e*100');
    // the glow (browser: a hot shadow with no offset)
    var ds = jzEffect(L, 'ADBE Drop Shadow', 'JZ In Glow');
    jzEP(ds, 1, jzHex(hot)); jzEP(ds, 3, 135); jzEP(ds, 4, 0);
    jzEX(ds, 2, hd + '229.5*ox_e'); jzEX(ds, 5, hd + jzN(Math.min(90, sz * 0.5)) + '*ox_e');
    var gl = jzEffect(L, 'ADBE Glo2', 'JZ In Flare');
    jzEP(gl, 2, 25);
    jzEX(gl, 3, hd + jzN(Math.min(60, sz * 0.35)) + '*ox_e'); jzEX(gl, 4, hd + '0.8*ox_e*ox_e');
} });

/* ---- backlight \u2014 \u9006\u5149: a halo rises behind the line first, the glyphs read as dark silhouettes against it, then light up */
jzReg('enter', 'backlight', { apply: function (m) {
    var L = m.L, G = eb1_geo(m), sc = m.ctx.sc, sz = m.size, r = G.r;
    var bg = eb1_pick(sc.bg, '#000000'), col = jzTextColor(L), glow = eb1_pick(sc.accent, sc.fg);
    var ev = EB1_FNS + 'var bl_r=oc(P/0.4),bl_l=ios(cl((P-0.3)/0.6)),bl_h=bl_r*(1-sm(0.45,1,P));', hd = eb1_hd(m) + ev;
    jzAnimator(L, 'JZ In Silhouette', [['ADBE Text Fill Color', jzHex(jzMixHex(bg, col, 0.12))]], hd + '(1-bl_l)*100');
    m.parts.op.push('f*=cl(P*5);');
    m.parts.sc.push(ev + 'f=[f[0]*(1+0.035*(1-bl_l)),f[1]*(1+0.035*(1-bl_l))];');
    m.parts.pos.push(ev + eb1_pivot(m, '1+0.035*(1-bl_l)', '1+0.035*(1-bl_l)', '0', G.cx, G.cy));
    var ds = jzEffect(L, 'ADBE Drop Shadow', 'JZ In Rim');
    jzEP(ds, 1, jzHex(glow)); jzEP(ds, 3, 135); jzEP(ds, 4, 0);
    jzEX(ds, 2, hd + '242*bl_h'); jzEX(ds, 5, hd + jzN(Math.min(110, sz * 0.55)) + '*(0.5+0.5*bl_r)');
    if (!eb1_main(m) || eb1_crowded(m, G)) return;
    // the halo behind the line (browser: radial gradient) = soft accent ellipse under the text
    var R = Math.sqrt(r.width * r.width + r.height * r.height) / 2 + sz * 0.8, S = jzNoGhost(eb1_shape(m, 'JZ In Halo', false, true)), H = eb1_head(m) + ev;
    var grp = jzGrp(S, 'halo'); jzAddEllipse(grp, R * 1.3, R * 1.3); jzAddFill(grp, glow);
    jzGX(grp).property('ADBE Vector Position').setValue([G.cx, G.cy]);
    var gb = jzEffect(S, 'ADBE Gaussian Blur 2', 'JZ Halo Soft'); jzEP(gb, 1, R * 0.45);
    jzSetExpr(jzXf(S, 'ADBE Opacity'), H + 'cl(P*5)*bl_h*26');
} });

/* ---- lightLeak \u2014 \u5149\u6F0F\u308C: a warm light leak drifts along the line; each glyph burns in hot where it passes and cools to its colour */
jzReg('enter', 'lightLeak', { selfHide: true, apply: function (m) {
    var L = m.L, G = eb1_geo(m), vert = G.vert, r = G.r, sc = m.ctx.sc, sz = m.size, i, F = [];
    var warm = eb1_pick(sc.accent, sc.fg), dark = jzLum(eb1_pick(sc.bg, '#000000')) < 0.5, core = jzMixHex(warm, '#FFFFFF', dark ? 0.45 : 0.2);
    var U0 = vert ? r.top : r.left, U1 = U0 + (vert ? r.height : r.width), span = Math.max(1, U1 - U0), bl = Math.min(20, sz * 0.06);
    for (i = 0; i < G.n; i++) F.push(((vert ? G.g[i].y : G.g[i].x) - U0) / span);
    var q = eb1_hd(m) + 'var F=' + eb1_arr(F) + ',q=cl((P-0.55*(F[textIndex-1]||0))/0.45),on=q>0&&q<1,ht=1-oc(q),a1=Math.min(1,ht*1.5);';
    jzAnimator(L, 'JZ In Warm', [['ADBE Text Fill Color', jzHex(warm)]], q + 'on?a1*100:0');
    jzAnimator(L, 'JZ In Core', [['ADBE Text Fill Color', jzHex(core)]], q + 'on&&ht>0.6?(ht-0.6)/0.4*a1*100:0');
    jzAnimator(L, 'JZ In Swell', [['ADBE Text Scale 3D', [200, 200, 100]], ['ADBE Text Blur', [bl, bl]]], q + 'on?[ht*7,ht*7,0]:[0,0,0]');
    jzAnimator(L, 'JZ In Haze', [['ADBE Text Blur', [bl, bl]]], q + 'on?ht*ht*100:0');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + 'q<=0?100:(1-cl(q*4))*100');
    if (!eb1_main(m) || G.nk <= 1 || eb1_crowded(m, G)) return;
    // the leak: a soft blob sliding along the line (screen on dark backgrounds, multiply on light ones)
    var S = jzNoGhost(eb1_shape(m, 'JZ In Leak')), H = eb1_head(m), R = Math.max(sz * 1.4, (vert ? r.width : r.height) * 1.1);
    var grp = jzGrp(S, 'leak'), gx = jzGX(grp), tint = dark ? warm : jzMixHex(warm, '#FFFFFF', 0.55);
    jzAddEllipse(grp, R * 1.1, R * 1.1); jzAddFill(grp, dark ? core : tint);
    var hp = jzN(U0 - sz * 0.5) + '+' + jzN(U1 - U0 + sz) + '*cl(P/0.62)';
    jzSetExpr(gx.property('ADBE Vector Position'), H + (vert ? '[' + jzN(G.cx) + ',' + hp + ']' : '[' + hp + ',' + jzN(G.cy) + ']'));
    var gb = jzEffect(S, 'ADBE Gaussian Blur 2', 'JZ Leak Soft'); jzEP(gb, 1, R * 0.5);
    try { S.blendingMode = dark ? BlendingMode.SCREEN : BlendingMode.MULTIPLY; } catch (e) {}
    jzSetExpr(jzXf(S, 'ADBE Opacity'), H + 'cl(P*6)*(1-sm(0.55,0.95,P))*' + (dark ? 75 : 55));
} });

/* ---- loupe \u2014 \u30EB\u30FC\u30DA: a magnifying glass slides along the line; glyphs swell under the lens and are left sharp behind it */
jzReg('enter', 'loupe', { apply: function (m) {
    var L = m.L, G = eb1_geo(m), vert = G.vert, r = G.r, sz = m.size, i, UU = [], VV = [];
    var U0 = vert ? r.top : r.left, U1 = U0 + (vert ? r.height : r.width), V0 = vert ? r.left : r.top, V1 = V0 + (vert ? r.width : r.height), vc = (V0 + V1) / 2;
    var R = Math.max(sz * 0.95, (V1 - V0) * 0.62), K = R * 0.4;
    for (i = 0; i < G.n; i++) { UU.push(vert ? G.g[i].y : G.g[i].x); VV.push(vert ? G.g[i].x : G.g[i].y); }
    var lc = 'var Lc=' + jzN(U0 - R * 0.7) + '+' + jzN(U1 + R * 1.4 - (U0 - R * 0.7)) + '*ios(cl(P/0.9)),pre=0.14*cl(P*5),fin=cl(P*6);';
    var q = eb1_hd(m) + lc + 'var UU=' + eb1_arr(UU) + ',VV=' + eb1_arr(VV) + ',du=(UU[textIndex-1]||0)-Lc,dv=(VV[textIndex-1]||0)-(' + jzN(vc) + '),d=Math.sqrt(du*du+dv*dv)/' + jzN(R) + ',mm=d<1?1-d*d:0;';
    jzAnimator(L, 'JZ In Lens', [['ADBE Text Scale 3D', [200, 200, 100]]], q + 'mm*60');
    jzAnimator(L, 'JZ In Push', [['ADBE Text Position 3D', [K, K, 0]]], q + 'var pu=0.35*mm/' + jzN(K) + '*100;' + (vert ? '[dv*pu,du*pu,0]' : '[du*pu,dv*pu,0]'));
    jzAnimator(L, 'JZ In Fade', [['ADBE Text Opacity', 0]], q + 'var a=d>=1?(du>0?pre:1):fin*(du>0?pre+(1-pre)*Math.min(1,mm*3):1);(1-a)*100');
    if (!eb1_main(m) || G.arranged || eb1_crowded(m, G)) return;
    // the magnifier: accent ring, faint highlight arc, handle
    var S = jzNoGhost(eb1_shape(m, 'JZ In Loupe')), H = eb1_head(m), lw = Math.max(2 * m.u, sz * 0.05), acc = m.ctx.sc.accent || '#FFFFFF';
    var grp = jzGrp(S, 'loupe'), gx = jzGX(grp);
    var hdl = eb1_sub(grp, 'handle'); jzAddRect(hdl, R * 0.778, lw * 2, lw); jzAddFill(hdl, acc);
    jzGX(hdl).property('ADBE Vector Position').setValue([R * 0.982, R * 0.982]); jzGX(hdl).property('ADBE Vector Rotation').setValue(45);
    var ring = eb1_sub(grp, 'ring'); jzAddEllipse(ring, R * 2, R * 2); jzAddStroke(ring, acc, lw);
    var arc = eb1_sub(grp, 'glint'); jzAddEllipse(arc, R * 1.6, R * 1.6); jzAddStroke(arc, m.ctx.sc.fg || '#FFFFFF', lw * 0.5, 50);
    jzAddTrimPaths(arc, '94.4', '80.6');
    jzSetExpr(gx.property('ADBE Vector Position'), H + lc + (vert ? '[' + jzN(vc) + ',Lc]' : '[Lc,' + jzN(vc) + ']'));
    jzSetExpr(gx.property('ADBE Vector Group Opacity'), H + 'cl(P*8)*(1-sm(0.8,0.97,P))*100');
} });

/* ---- stickerPeel \u2014 \u30B7\u30FC\u30EB\u8CBC\u308A: every glyph is pressed on like a die-cut sticker: the stuck part grows from its bottom-left corner
   along the diagonal while the free part is still lifted, foreshortened toward the fold line (stuck part = a linked copy clipped by a
   track matte of growing right triangles; lifted part = a second copy in a lighter tone, compressed across the 45\u00B0 fold, clipped by
   the inverted matte) */
function eb1_tris(m, S, G, pad) {
    var H = eb1_head(m), i;
    for (i = 0; i < G.n; i++) {
        var g = G.g[i]; if (g.sp) continue;
        var hx = g.w / 2 + pad, hy = g.h / 2 + pad, o = G.n > 1 ? i / (G.n - 1) : 0;
        var hq = H + 'var q=cl((P-' + jzN(0.5 * o) + ')/0.5),c=(-0.02+1.04*io3(q))*' + jzN(2 * (hx + hy)) + ';';
        var grp = jzGrp(S, 'tri ' + (i + 1)), gx = jzGX(grp);
        jzAddPath(grp, [[0, 0], [100, 0], [0, -100]], true); jzAddFill(grp, '#FFFFFF');
        gx.property('ADBE Vector Position').setValue([g.x - hx, g.y + hy]);
        jzSetExpr(gx.property('ADBE Vector Scale'), hq + '[Math.max(0,c),Math.max(0,c)]');
        jzSetExpr(gx.property('ADBE Vector Group Opacity'), hq + 'q>0&&c>0.5?100:0');
    }
}
jzReg('enter', 'stickerPeel', { selfHide: true, apply: function (m) {
    var L = m.L, G = eb1_geo(m), sz = m.size, pad = sz * 0.12, bg = eb1_pick(m.ctx.sc.bg, '#000000'), col = jzTextColor(L), lift = jzMixHex(col, bg, 0.3), i;
    var q = eb1_hd(m) + eb1_q(0.5) + 'var on=q>0&&q<1;';
    var soft = G.arranged || G.masked || eb1_matted(L) || eb1_crowded(m, G) || !G.nk || !eb1_main(m);
    if (soft) {       // no linked copies: each glyph is laid down from its bottom-left corner
        var hw = [], K0 = sz * 1.2;
        for (i = 0; i < G.n; i++) hw.push(G.g[i].w / 2);
        var qs = q + 'var W=' + eb1_arr(hw) + ',hw=W[textIndex-1]||SZ*0.5,hy=' + jzN(G.fs / 2) + ',k=0.25+0.75*io3(q);';
        jzAnimator(L, 'JZ In Press', [['ADBE Text Scale 3D', [0, 0, 100]]], qs + 'on?(1-k)*100:0');
        jzAnimator(L, 'JZ In Corner', [['ADBE Text Position 3D', [K0, K0, 0]]], qs + 'on?[-(1-k)*hw/' + jzN(K0) + '*100,(1-k)*hy/' + jzN(K0) + '*100,0]:[0,0,0]');
        jzAnimator(L, 'JZ In Lift', [['ADBE Text Fill Color', jzHex(lift)]], qs + 'on?(1-io3(q))*100:0');
        jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], qs + 'q<=0?100:(1-cl(q*6))*100');
        return;
    }
    var D = eb1_twin(m, 'JZ In Stuck'), F = eb1_twin(m, 'JZ In Flap');
    jzAnimator(D, 'JZ In Set', [['ADBE Text Opacity', 0]], q + 'on?0:100');
    jzAnimator(F, 'JZ In Set', [['ADBE Text Opacity', 0]], q + 'on?(1-cl(q*6))*100:100');
    jzAnimator(F, 'JZ In Lift', [['ADBE Text Fill Color', jzHex(lift)], ['ADBE Text Stroke Color', jzHex(lift)]], '100');
    // the free part is lifted ~62\u00B0: seen from the front it is compressed by cos 62\u00B0 across the fold (the 45\u00B0 diagonal) toward the fold line.
    // That matrix = rotation 19.85\u00B0 \u00B7 skew 39.7\u00B0 \u00B7 scale 78.1 / 60.1 %; about the fold's middle point it moves the glyph by b\u00B7(hx+hy\u2212c)\u00B7(\u22121, 1)
    var HS = [], K = sz * 0.6, b = (1 - Math.cos(62 * Math.PI / 180)) / 2;
    for (i = 0; i < G.n; i++) HS.push(G.g[i].w / 2 + G.g[i].h / 2 + pad * 2);
    jzAnimator(F, 'JZ In Fold', [['ADBE Text Rotation', 19.85], ['ADBE Text Skew', 39.7], ['ADBE Text Scale 3D', [78.1, 60.1, 100]]], q + 'on?100:0');
    jzAnimator(F, 'JZ In Hinge', [['ADBE Text Position 3D', [K, K, 0]]], q + 'var HS=' + eb1_arr(HS) + ',hs=HS[textIndex-1]||SZ,c=(-0.02+1.04*io3(q))*2*hs,v=' + jzN(b) + '*(hs-c)/' + jzN(K) + '*100;on?[-v,v,0]:[0,0,0]');
    var M = eb1_shape(m, 'JZ In Stuck Matte'), MF = eb1_shape(m, 'JZ In Flap Matte');
    eb1_tris(m, M, G, pad); eb1_tris(m, MF, G, pad);
    try {
        D.moveBefore(L); M.moveBefore(D); F.moveBefore(M); MF.moveBefore(F);
        D.trackMatteType = TrackMatteType.ALPHA; F.trackMatteType = TrackMatteType.ALPHA_INVERTED;
    } catch (e1) { jzWarn('stickerPeel matte: ' + e1.toString()); }
    // the lyric layer itself shows each glyph once it is stuck down completely
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + 'q<1?100:0');
} });

/* ---- noteUnfold \u2014 \u624B\u7D19\u958B\u304D: every glyph is a note folded in four: the top-left quarter shows, the right half swings open on the
   vertical crease, then the bottom half swings down on the horizontal crease (quarter clips = per-glyph rect masks, the swinging
   halves = two linked copies squashed toward the creases, each clipped to its half) */
jzReg('enter', 'noteUnfold', { selfHide: true, apply: function (m) {
    var L = m.L, G = eb1_geo(m), sz = m.size, bg = eb1_pick(m.ctx.sc.bg, '#000000'), fs = G.fs, i;
    var st = eb1_q(0.45) + 'var on=q>0&&q<1,e1=oc((q-0.14)/0.38),e2=oc((q-0.5)/0.44);';
    var q = eb1_hd(m) + st;
    var soft = G.arranged || G.masked || eb1_matted(L) || eb1_crowded(m, G) || G.nk > 24 || !G.nk;
    if (soft) {       // no clips: the glyph opens from its top-left corner (half width, half height \u2192 full)
        var hw = [], K = sz;
        for (i = 0; i < G.n; i++) hw.push(G.g[i].w / 2);
        var qs = q + 'var W=' + eb1_arr(hw) + ',hw=W[textIndex-1]||SZ*0.5,hy=' + jzN(fs / 2) + ',sx=q<0.14?0.5:0.5+0.5*e1,sy=q<0.5?0.5:0.5+0.5*e2;';
        jzAnimator(L, 'JZ In Unfold', [['ADBE Text Scale 3D', [0, 0, 100]]], qs + 'on?[(1-sx)*100,(1-sy)*100,0]:[0,0,0]');
        jzAnimator(L, 'JZ In Corner', [['ADBE Text Position 3D', [K, K, 0]]], qs + 'on?[-(1-sx)*hw/' + jzN(K) + '*100,-(1-sy)*hy/' + jzN(K) + '*100,0]:[0,0,0]');
        jzAnimator(L, 'JZ In Shade', [['ADBE Text Fill Color', jzHex(bg)]], qs + 'on?(q<0.5?1-e1:1-e2)*55:0');
        jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], qs + 'q<=0?100:(1-cl(q/0.14))*100');
        return;
    }
    var H = eb1_head(m), shade = function (Lx, ex) { jzAnimator(Lx, 'JZ In Shade', [['ADBE Text Fill Color', jzHex(bg)]], eb1_hd(m) + st + '(1-' + ex + ')*55'); };
    // the swinging right half (stage 1) and bottom half (stage 2): linked copies squashed toward the crease (glyph centre)
    var R = eb1_twin(m, 'JZ In Flap R'), B = eb1_twin(m, 'JZ In Flap B');
    jzAnimator(R, 'JZ In Swing', [['ADBE Text Scale 3D', [0, 100, 100]]], eb1_hd(m) + st + 'q>0.14&&q<0.52?[(1-Math.max(0.03,e1))*100,0,0]:[0,0,0]');
    jzAnimator(R, 'JZ In Set', [['ADBE Text Opacity', 0]], eb1_hd(m) + st + 'q>0.14&&q<0.52?0:100');
    shade(R, 'e1');
    jzAnimator(B, 'JZ In Swing', [['ADBE Text Scale 3D', [100, 0, 100]]], eb1_hd(m) + st + 'q>0.5&&q<1?[0,(1-Math.max(0.03,e2))*100,0]:[0,0,0]');
    jzAnimator(B, 'JZ In Set', [['ADBE Text Opacity', 0]], eb1_hd(m) + st + 'q>0.5&&q<1?0:100');
    shade(B, 'e2');
    // per-glyph clips (layer space): L = top-left quarter, + top-right quarter after stage 1, + bottom half when done
    var hy = fs * 0.66, mk;
    for (i = 0; i < G.n; i++) {
        var g = G.g[i]; if (g.sp) continue;
        var o = G.n > 1 ? i / (G.n - 1) : 0, hq = H + 'var q=cl((P-' + jzN(0.45 * o) + ')/0.55);', w = g.w * 0.53, x = g.x, y = g.y;
        mk = jzMaskRect(L, x - w, y - hy, x + 0.004 * fs, y + 0.004 * fs); jzSetExpr(mk.property('ADBE Mask Opacity'), hq + 'q>0?100:0');
        mk = jzMaskRect(L, x, y - hy, x + w, y + 0.004 * fs); jzSetExpr(mk.property('ADBE Mask Opacity'), hq + 'q>=0.52?100:0');
        mk = jzMaskRect(L, x - w, y, x + w, y + hy); jzSetExpr(mk.property('ADBE Mask Opacity'), hq + 'q>=1?100:0');
        mk = jzMaskRect(R, x, y - hy, x + w, y + 0.004 * fs);
        mk = jzMaskRect(B, x - w, y, x + w, y + hy);
    }
    var r = G.r;
    mk = jzMaskRect(L, r.left - fs, r.top - fs, r.left + r.width + fs, r.top + r.height + fs);
    try { mk.name = 'JZ In Open'; } catch (e0) {}
    jzSetExpr(mk.property('ADBE Mask Opacity'), H + 'time>=DL+IN?100:0');
    jzSetExpr(mk.property('ADBE Mask Offset'), H + 'time>=DL+IN?1e4:0');
    // the lyric layer: appears with a small pop in the first stage
    jzAnimator(L, 'JZ In Pop', [['ADBE Text Scale 3D', [0, 0, 100]]], q + 'q>0&&q<0.14?(1-(0.9+0.1*ob(q/0.14,2)))*100:0');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + 'q<=0?100:(1-cl(q/0.14))*100');
    if (!eb1_main(m)) return;
    // faint creases while the note is still folded
    var S = jzNoGhost(eb1_shape(m, 'JZ In Creases')), lw = Math.max(1 * m.u, sz * 0.012), cc = m.ctx.sc.sub || m.ctx.sc.fg || '#FFFFFF';
    for (i = 0; i < G.n; i++) {
        var g2 = G.g[i]; if (g2.sp) continue;
        var o2 = G.n > 1 ? i / (G.n - 1) : 0, hq2 = H + 'var q=cl((P-' + jzN(0.45 * o2) + ')/0.55),a=q<=0||q>=1?0:0.6*(1-sm(0.85,1,q))*cl(q*6)*100;';
        var ch = g2.w * 0.55, cv = fs * 0.58;
        // (the rect is set up before the fill is added: adding to the contents invalidates older item references)
        var gv = jzGrp(S, 'crease v ' + (i + 1)), rv = jzAddRect(gv, lw, cv, 0);
        jzSetExpr(rv.property('ADBE Vector Rect Size'), hq2 + '[' + jzN(lw) + ',q<0.52?' + jzN(cv) + ':' + jzN(cv * 2) + ']');
        jzAddFill(gv, cc);
        jzSetExpr(jzGX(gv).property('ADBE Vector Position'), hq2 + '[' + jzN(g2.x) + ',q<0.52?' + jzN(g2.y - cv / 2) + ':' + jzN(g2.y) + ']');
        jzSetExpr(jzGX(gv).property('ADBE Vector Group Opacity'), hq2 + 'a');
        var gh = jzGrp(S, 'crease h ' + (i + 1)), rh = jzAddRect(gh, ch, lw, 0);
        jzSetExpr(rh.property('ADBE Vector Rect Size'), hq2 + '[q<0.14?' + jzN(ch) + ':' + jzN(ch * 2) + ',' + jzN(lw) + ']');
        jzAddFill(gh, cc);
        jzSetExpr(jzGX(gh).property('ADBE Vector Position'), hq2 + '[q<0.14?' + jzN(g2.x - ch / 2) + ':' + jzN(g2.x) + ',' + jzN(g2.y) + ']');
        jzSetExpr(jzGX(gh).property('ADBE Vector Group Opacity'), hq2 + 'a');
    }
} });

/* ---- tornJoin \u2014 \u7834\u308C\u5408\u308F\u305B: the line is torn in two along a ragged seam; the halves slide in from opposite sides and butt together */
jzReg('enter', 'tornJoin', { apply: function (m) {
    var L = m.L, G = eb1_geo(m), vert = G.vert, r = G.r, sz = m.size, seed = m.c.seed | 0, i;
    var s0 = Math.abs(jzXf(L, 'ADBE Scale').value[vert ? 1 : 0] / 100) || 1, D = (sz * 2 + (vert ? r.height : r.width) * 0.25) * s0;
    var ev = EB1_FNS + 'var tj_k=1-o4(P/0.72),tj_j=sm(0.62,0.72,P)*(1-sm(0.72,1,P))*' + jzN(sz * 0.035 * s0) + '*Math.sin(P*90);';
    var soft = G.masked || eb1_matted(L) || eb1_crowded(m, G);
    m.parts.op.push('f*=cl(P*5);');
    m.parts.rot.push(ev + 'r+=-5*tj_k;');
    m.parts.pos.push(ev + eb1_pivot(m, '1', '1', '-5*tj_k', G.cx, G.cy) + (vert ? 'd=[d[0]+tj_j,d[1]-' + jzN(D) + '*tj_k];' : 'd=[d[0]-' + jzN(D) + '*tj_k,d[1]+tj_j];'));
    if (soft) return;
    // the ragged seam across the reading axis (layer space), the two halves as masks on L and on a linked copy
    var U = vert ? G.cy : G.cx, V0 = (vert ? r.left : r.top) - sz * 0.15, V1 = (vert ? r.left + r.width : r.top + r.height) + sz * 0.15;
    var far = (vert ? r.height : r.width) + sz * 6, K = 11, seam = [];
    for (i = 0; i <= K; i++) seam.push([U + eb1_rs(seed, i, 95) * sz * 0.1 + (i % 2 ? 1 : -1) * sz * 0.03, V0 + (V1 - V0) * i / K]);
    var poly = function (side, ov) {
        var pts = [], j;
        for (j = 0; j < seam.length; j++) { var u = seam[j][0] - side * ov, v = seam[j][1]; pts.push(vert ? [v, u] : [u, v]); }
        pts.push(vert ? [V1, U + side * far] : [U + side * far, V1]); pts.push(vert ? [V0, U + side * far] : [U + side * far, V0]);
        return pts;
    };
    var H = eb1_head(m), D2 = eb1_twin(m, 'JZ In Torn B');
    var addPoly = function (Lx, pts, name) {
        var mk = Lx.property('ADBE Mask Parade').addProperty('ADBE Mask Atom'), sh = new Shape();
        sh.vertices = pts; sh.closed = true; mk.property('ADBE Mask Shape').setValue(sh);
        try { mk.name = name; } catch (e0) {}
        return mk;
    };
    var mA = addPoly(L, poly(-1, 0), 'JZ In Torn A');
    jzSetExpr(mA.property('ADBE Mask Offset'), H + 'P>=0.72?1e4:0');
    addPoly(D2, poly(1, 1.2), 'JZ In Torn B');
    // the other half: slides in from the opposite side (its offset is given in L's own, rotated space) and turns the other way
    var tr = D2.property('ADBE Transform Group');
    tr.property('ADBE Anchor Point').setValue([G.cx, G.cy]);
    jzSetExpr(tr.property('ADBE Position'), H + ev + 'var a=-parent.transform.rotation*Math.PI/180,sc=parent.transform.scale,wx=' + (vert ? '0' : '2*' + jzN(D) + '*tj_k') + ',wy=' + (vert ? '2*' + jzN(D) + '*tj_k' : '0') + ';' +
        '[' + jzN(G.cx) + '+(Math.cos(a)*wx-Math.sin(a)*wy)/(sc[0]/100),' + jzN(G.cy) + '+(Math.sin(a)*wx+Math.cos(a)*wy)/(sc[1]/100)]');
    jzSetExpr(tr.property('ADBE Rotate Z'), H + ev + '10*tj_k');
    jzSetExpr(tr.property('ADBE Opacity'), H + 'time>=DL&&P<0.72?cl(P*5)*100:0');
    if (!eb1_main(m) || G.nk <= 1) return;
    // paper fibres along both torn edges
    var fib = jzMixHex(eb1_pick(m.ctx.sc.fg, '#FFFFFF'), eb1_pick(m.ctx.sc.bg, '#000000'), 0.35), lw = Math.max(1 * m.u, sz * 0.018), sp = [];
    for (i = 0; i < seam.length; i++) sp.push(vert ? [seam[i][1], seam[i][0]] : [seam[i][0], seam[i][1]]);
    var k2;
    for (k2 = 0; k2 < 2; k2++) {
        var S = jzNoGhost(eb1_shape(m, 'JZ In Fibres ' + (k2 + 1))), grp = jzGrp(S, 'seam');
        jzAddPath(grp, sp, false); jzAddStroke(grp, fib, lw);
        jzSetExpr(jzXf(S, 'ADBE Opacity'), H + 'cl(P*5)*(1-sm(0.7,0.95,P))*80');
        if (k2) { try { S.parent = D2; S.moveBefore(D2); } catch (e2) {} }
    }
} });

/* ---- splitFlap \u2014 \u30D1\u30BF\u30D1\u30BF: each glyph flaps through a few random characters before landing on its own
   (browser: top / bottom half-leaves of old and new character; here: the leaf squashes to the hinge and back, the character changes
   at the hinge through a Character Offset, a hairline gap splits every glyph) */
jzReg('enter', 'splitFlap', { selfHide: true, apply: function (m) {
    var L = m.L, G = eb1_geo(m), sz = m.size, bg = eb1_pick(m.ctx.sc.bg, '#000000'), SP = [], i;
    for (i = 0; i < G.n; i++) SP.push(G.g[i].sp ? 1 : 0);
    var q = eb1_hd(m) + eb1_q(0.4) + 'var i=textIndex-1,SP=' + eb1_arr(SP) + ',F=3+(i%2),x=q*F,j=Math.min(F-1,Math.floor(x)),fr=x-j,on=q>0&&q<1&&!SP[i],ch=fr<0.5?j:j+1,' +
        'cs=fr<0.5?Math.cos(fr*Math.PI):-Math.cos(fr*Math.PI),sy=Math.max(0.03,cs);';
    // the character on the leaf: step 0 = blank, step F = the glyph itself, else a random sign
    jzAnimator(L, 'JZ In Flap', [['ADBE Text Character Offset', 100]], q + 'on&&ch>0&&ch<F?8+Math.floor(hh(textIndex*31+ch*7+SD)*50):0');
    jzAnimator(L, 'JZ In Leaf', [['ADBE Text Scale 3D', [100, 0, 100]]], q + 'on?[0,(1-sy)*100,0]:[0,0,0]');
    jzAnimator(L, 'JZ In Shade', [['ADBE Text Fill Color', jzHex(bg)]], q + 'on?(1-sy)*50:0');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + 'q<=0||(on&&ch<=0)?100:0');
    if (G.arranged || eb1_crowded(m, G) || G.nk > 40) return;
    // the hairline gap between the two flaps of every glyph
    var S = jzNoGhost(eb1_shape(m, 'JZ In Gap')), H = eb1_head(m), t = Math.max(1 * m.u, sz * 0.018);
    for (i = 0; i < G.n; i++) {
        var g = G.g[i]; if (g.sp) continue;
        var o = G.n > 1 ? i / (G.n - 1) : 0, grp = jzGrp(S, 'gap ' + (i + 1));
        jzAddRect(grp, g.w * 1.04, t, 0); jzAddFill(grp, bg);
        jzGX(grp).property('ADBE Vector Position').setValue([g.x, g.y]);
        jzSetExpr(jzGX(grp).property('ADBE Vector Group Opacity'), H + 'var q=cl((P-' + jzN(0.4 * o) + ')/0.6);q>0&&q<1?100:0');
    }
} });

/* ---- glint \u2014 \u30B0\u30EA\u30F3\u30C8: the line waits as a faint ghost; a slanted glint sweeps across and leaves it lit behind the highlight
   (lit part and highlight = linked copies clipped by slanted shape-layer track mattes that ride with the glint) */
jzReg('enter', 'glint', { apply: function (m) {
    var L = m.L, G = eb1_geo(m), vert = G.vert, r = G.r, sz = m.size, sc = m.ctx.sc, kS = 0.45;
    var col = jzTextColor(L), hot = (jzLum(col) > 0.7 || jzLum(eb1_pick(sc.bg, '#000000')) > 0.5) ? eb1_pick(sc.accent, sc.fg) : '#FFFFFF';
    var U0 = vert ? r.top : r.left, U1 = U0 + (vert ? r.height : r.width), V0 = (vert ? r.left : r.top) - sz * 0.3, V1 = (vert ? r.left + r.width : r.top + r.height) + sz * 0.3, vc = (V0 + V1) / 2;
    var bw = sz * 0.55, sl = kS * (V1 - V0) / 2, big = (U1 - U0) + sz * 8, hv = (V1 - V0) / 2;
    var bx = 'var B=' + jzN(U0 - bw - sl) + '+' + jzN(U1 + bw + sl - (U0 - bw - sl)) + '*ios(cl(P/0.9));';
    var soft = eb1_matted(L) || eb1_crowded(m, G) || !eb1_main(m);
    if (soft) {       // per glyph: ghost until the glint passes it, hot while it is under the glint
        var UU = [], VV = [], i;
        for (i = 0; i < G.n; i++) { UU.push(vert ? G.g[i].y : G.g[i].x); VV.push(vert ? G.g[i].x : G.g[i].y); }
        var q = eb1_hd(m) + bx + 'var UU=' + eb1_arr(UU) + ',VV=' + eb1_arr(VV) + ',w=(UU[textIndex-1]||0)+' + jzN(kS) + '*((VV[textIndex-1]||0)-(' + jzN(vc) + ')),z=(B-w)/' + jzN(bw) + ';';
        jzAnimator(L, 'JZ In Glint', [['ADBE Text Fill Color', jzHex(hot)]], q + 'z>0&&z<1?(1-z)*100*(1-sm(0.85,1,P)):0');
        jzAnimator(L, 'JZ In Ghost', [['ADBE Text Opacity', 0]], q + 'P>=1?0:(w<B?0:(1-0.2*cl(P*6))*100)');
        return;
    }
    var H = eb1_head(m), dv0 = -hv, dv1 = hv;
    var P2 = function (du, dv) { return vert ? [dv, du] : [du, dv]; };
    var band = function (grp, a, b) { jzAddPath(grp, [P2(a - kS * dv0, dv0), P2(b - kS * dv0, dv0), P2(b - kS * dv1, dv1), P2(a - kS * dv1, dv1)], true); jzAddFill(grp, '#FFFFFF'); };
    var at = H + bx + (vert ? '[' + jzN(vc) + ',B]' : '[B,' + jzN(vc) + ']');
    // lit part: everything behind the glint's leading edge
    var T1 = eb1_twin(m, 'JZ In Lit'), M1 = eb1_shape(m, 'JZ In Lit Matte'), g1 = jzGrp(M1, 'lit');
    band(g1, -big, 0);
    jzSetExpr(jzGX(g1).property('ADBE Vector Position'), at);
    // the highlight: a hot copy seen through two slanted bands (soft outer band, bright core)
    var T3 = jzNoGhost(eb1_twin(m, 'JZ In Hot'));    // the highlight is drawn in the main pass only
    jzAnimator(T3, 'JZ In Hot', [['ADBE Text Fill Color', jzHex(hot)], ['ADBE Text Stroke Color', jzHex(hot)]], '100');
    jzSetExpr(jzXf(T3, 'ADBE Opacity'), H + 'time>=DL&&time<DL+IN?(1-sm(0.85,1,P))*100:0');
    var M3 = jzNoGhost(eb1_shape(m, 'JZ In Hot Matte')), g3 = jzGrp(M3, 'glint');
    var go = eb1_sub(g3, 'outer'); band(go, -bw, bw * 0.15); jzGX(go).property('ADBE Vector Group Opacity').setValue(45);
    var gi = eb1_sub(g3, 'core'); band(gi, -bw * 0.45, 0);
    jzSetExpr(jzGX(g3).property('ADBE Vector Position'), at);
    try {
        T1.moveBefore(L); M1.moveBefore(T1); T3.moveBefore(M1); M3.moveBefore(T3);
        T1.trackMatteType = TrackMatteType.ALPHA; T3.trackMatteType = TrackMatteType.ALPHA;
    } catch (e1) { jzWarn('glint matte: ' + e1.toString()); }
    // the lyric layer waits as a faint ghost under the lit copy
    m.parts.op.push('f*=time>=DL+IN?1:0.2*cl(P*6);');
} });

/* ---- filmFeed \u2014 \u30D5\u30A3\u30EB\u30E0\u9001\u308A: projector losing its loop: the frame rolls through the gate with a frame line, the lamp flickers, then it locks
   (the text rolls inside a fixed gate mask through a text animator; a linked copy is the frame above it) */
jzReg('enter', 'filmFeed', { apply: function (m) {
    var L = m.L, G = eb1_geo(m), r = G.r, sz = m.size, FH = r.height + sz * 0.7, g0 = G.cy - FH / 2, g1 = G.cy + FH / 2;
    var s0 = Math.abs(jzXf(L, 'ADBE Scale').value[0] / 100) || 1, F = jzN(FH);
    var ev = 'var ff_e=oc(P/0.82),ff_o=((' + F + '*2.35*(1-ff_e))%' + F + '+' + F + ')%' + F + ',ff_l=1-sm(0.55,0.95,P),ff_n=Math.floor(time*24);';
    m.parts.op.push(EB1_FNS + ev + 'f*=cl(P*7)*(1-0.45*ff_l*hh(ff_n*3+SD+99));');
    m.parts.pos.push(EB1_FNS + ev + 'd=[d[0]+(hh(ff_n*7+SD+98)*2-1)*' + jzN(sz * 0.02 * s0) + '*ff_l,d[1]];');
    var soft = G.masked || eb1_matted(L) || eb1_crowded(m, G), H = eb1_head(m);
    if (soft) {       // no gate: the frame only settles with a small stepped roll
        jzAnimator(L, 'JZ In Roll', [['ADBE Text Position 3D', [0, sz * 0.4, 0]]], eb1_hd(m) + ev + 'var a=ff_o/' + F + '*100;[a,a,0]');
        return;
    }
    // the gate: fixed in the layer's own space (glyphs move inside it), opens when the entrance is over
    var mk = jzMaskRect(L, r.left - sz * 2, g0, r.left + r.width + sz * 2, g1);
    try { mk.name = 'JZ In Gate'; } catch (e0) {}
    jzSetExpr(mk.property('ADBE Mask Offset'), H + 'time>=DL+IN?1e4:0');
    // the frame above (a linked copy one frame height higher, same gate)
    var D = eb1_twin(m, 'JZ In Frame');
    jzSetExpr(jzXf(D, 'ADBE Opacity'), H + 'time>=DL&&time<DL+IN?parent.transform.opacity:0');
    jzAnimator(D, 'JZ In Roll', [['ADBE Text Position 3D', [0, FH, 0]]], H + ev + 'var a=(ff_o-' + F + ')/' + F + '*100;[a,a,0]');
    jzAnimator(L, 'JZ In Roll', [['ADBE Text Position 3D', [0, FH, 0]]], H + ev + 'var a=ff_o/' + F + '*100;[a,a,0]');
    if (!eb1_main(m)) return;
    // the frame line rolling with the film (behind the text)
    var S = jzNoGhost(eb1_shape(m, 'JZ In Frame Line', false, true)), grp = jzGrp(S, 'frame line'), sc = m.ctx.sc;
    jzAddRect(grp, r.width + sz * 1.2, sz * 0.12, 0); jzAddFill(grp, jzMixHex(eb1_pick(sc.bg, '#000000'), eb1_pick(sc.fg, '#FFFFFF'), 0.18));
    jzSetExpr(jzGX(grp).property('ADBE Vector Position'), H + ev + '[' + jzN(G.cx) + ',' + jzN(g0) + '+ff_o]');
    jzSetExpr(jzXf(S, 'ADBE Opacity'), H + ev + 'ff_o>=0.5&&P<1?parent.transform.opacity*(1-sm(0.6,0.82,P)):0');
} });

// ================================================================ pack enter B part 2 (AE port of src/11p_enterB.js)
// heatHaze crtOn interlace loadingBar dither odometer matrixRain hatchFill brushReveal inkDrop quarters invertBox
// printRegister echoCount liquidFill windBlown strokeOrder clockWipe shadowFirst bubbles tokoroten
// Per-glyph motion = text animators with Expression Selectors. Clips = layer masks on the text (static shapes opened by
// mask expansion) or, where a mask cannot draw the shape (dither lattice, bristle strips, clock pies, a wavy liquid level),
// an alpha track matte shape layer parented to the text. Helper graphics (bars, hands, bubbles \u2026) are shape layers parented
// to the text layer, placed right above it and gone once the entrance is over. Copies ("twins") are duplicates of the text
// layer made before the entrance's own animators are added, parented to it and gone once the entrance is over.

// ---------------------------------------------------------------- shared helpers (eb2_*)
// easings not in JZ_FNS: o4 outQuart, o5 outQuint, io4 inOutQuart, ios inOutSine, sm smoothstep(a, b, x)
var EB2_FNS = 'function o4(x){x=cl(x);return 1-Math.pow(1-x,4);}function o5(x){x=cl(x);return 1-Math.pow(1-x,5);}' +
    'function io4(x){x=cl(x);return x<0.5?8*x*x*x*x:1-Math.pow(-2*x+2,4)/2;}function ios(x){x=cl(x);return -(Math.cos(Math.PI*x)-1)/2;}' +
    'function sm(a,b,x){x=cl((x-a)/(b-a));return x*x*(3-2*x);}\n';
var EB2_BAYER = [0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5];
var EB2_OWNSTROKE = 'var s0=0;try{s0=text.sourceText.style.strokeWidth;}catch(err){}';   // > 0.5: a treatment gave the text its own stroke

function eb2_T(m) { var c = m.c; return { IN: Math.max(0.02, c.inDur || 0.3), DL: (m.o.mi || 0) * (c.stagger || 0.04) }; }
// compact header for masks / helper layers: IN, DL, SZ, P + easings
function eb2_head(m) { var t = eb2_T(m); return 'var IN=' + jzN(t.IN) + ',DL=' + jzN(t.DL) + ',SZ=' + jzN(m.size) + ';' + JZ_FNS + EB2_FNS + 'var P=cl((time-DL)/IN);\n'; }
// browser stg(p, ordLR(i, n), spread) inside an Expression Selector -> q
function eb2_q(spread) { return 'var n=textTotal,o=n>1?(textIndex-1)/(n-1):0,q=cl((P-' + jzN(spread) + '*o)/' + jzN(1 - spread) + ');'; }
function eb2_arr(a) { var s = []; for (var i = 0; i < a.length; i++) s.push(jzN(a[i])); return '[' + s.join(',') + ']'; }
function eb2_pt(x, y) { return '[' + jzN(x) + ',' + jzN(y) + ']'; }
// the browser's J.h / J.r so seeded choices match the web version
function eb2_h(a, b, c, d, e) {
    var h = 0x9e3779b9 ^ (a | 0);
    h = jzImul(h ^ (h >>> 16), 0x85ebca6b);
    h = (h + jzImul((b | 0) + 0x632be5ab, 0xc2b2ae35)) | 0;
    h = jzImul(h ^ (h >>> 13), 0xc2b2ae35);
    h = (h + jzImul((c | 0) + 0x5bd1e995, 0x27d4eb2f)) | 0;
    h = jzImul(h ^ (h >>> 15), 0x165667b1);
    h = (h + jzImul((d | 0) + 0x1b873593, 0x85ebca6b)) | 0;
    h = jzImul(h ^ (h >>> 16), 0x27d4eb2f);
    h = (h + jzImul((e | 0) + 0x68e31da4, 0x9e3779b1)) | 0;
    h ^= h >>> 15; h = jzImul(h, 0x2c1b3c6d); h ^= h >>> 12; h = jzImul(h, 0x297a2d39); h ^= h >>> 15;
    return h >>> 0;
}
function eb2_r(a, b, c, d, e) { return eb2_h(a, b, c, d, e) / 4294967296; }
function eb2_dir(m, salt) { return eb2_r(m.c.seed | 0, salt, 5) < 0.5 ? -1 : 1; }
function eb2_isHex(c) { return typeof c === 'string' && c.charAt(0) === '#' && (c.length === 7 || c.length === 4); }
function eb2_pick(a, b, c) { if (eb2_isHex(a)) return a; if (eb2_isHex(b)) return b; if (eb2_isHex(c)) return c; return '#FFFFFF'; }
// hotOf: white on a dark background, else the accent
function eb2_hot(m) { var sc = m.ctx.sc, bg = eb2_pick(sc.bg, '#000000'); return jzLum(bg) < 0.5 ? '#FFFFFF' : eb2_pick(sc.accent, sc.fg); }
function eb2_td(L) { try { return L.property('ADBE Text Properties').property('ADBE Text Document').value; } catch (e) { return null; } }
function eb2_isSp(ch) { return /^[\s\u3000]$/.test(ch); }
// helper layers are skipped for very long lines, one-glyph-per-layer layouts of long lyrics and crowded comps
function eb2_crowded(m, G) {
    if (G && (G.N > 60 || (G.N <= 1 && jzCount(m.c.text || '') > 14))) return true;
    try { return m.ctx.comp.numLayers > 60; } catch (e) { return false; }
}
// browser isMain / isPrimary: the lyric's opaque, filled item (primary: more than one glyph)
function eb2_main(m, G) {
    var td = eb2_td(m.L);
    if (td && td.applyFill === false) return false;
    try { if (jzXf(m.L, 'ADBE Opacity').value < 85) return false; } catch (e) {}
    return !eb2_crowded(m, G);
}
function eb2_prim(m, G) { var td = eb2_td(m.L); return eb2_main(m, G) && !!td && jzCount(String(td.text)) > 1; }
function eb2_layers(m) { try { return m.ctx.comp.numLayers; } catch (e) { return 0; } }
function eb2_meas(T, L, s, just) {
    var d = L.property('ADBE Text Properties').property('ADBE Text Document').value;
    d.text = s; d.justification = just;
    T.property('ADBE Text Properties').property('ADBE Text Document').setValue(d);
    var q = T.sourceRectAtTime(0, false);
    return { l: q.left, r: q.left + q.width };
}
// Geometry of the lyric in the text layer's own space (call BEFORE adding animators).
//   G.fs, G.lead, G.rect, G.vert (one glyph per line), G.nL, G.lines[{cy, x0, x1, chars}],
//   G.g[] per AE character (textIndex order, spaces included): {i, li, sp, x0, x1, cx, cy, w, h}, G.N = textTotal,
//   G.arranged (layout animators / text on a path), G.masked (the layout masks this layer). measure: exact glyph boxes.
function eb2_geo(m, measure) {
    var L = m.L, TP = L.property('ADBE Text Properties'), td = TP.property('ADBE Text Document').value, i, j;
    var fs = td.fontSize, lead = (!td.autoLeading && td.leading > 0) ? td.leading : fs * 1.2;
    var lines = String(td.text).split(/\r\n|\r|\n|\u0003/), nL = lines.length, r = jzRect(L);
    var G = { fs: fs, lead: lead, nL: nL, rect: r, g: [], lines: [], N: 0, vert: nL > 1, arranged: false, masked: false };
    try { G.arranged = TP.property('ADBE Text Animators').numProperties > 0; } catch (e0) {}
    try { if (TP.property('ADBE Text Path Options').property('ADBE Text Path').value > 0) G.arranged = true; } catch (e1) {}
    try { G.masked = L.property('ADBE Mask Parade').numProperties > 0; } catch (e2) {}
    var inkH = r.height - (nL - 1) * lead; if (!(inkH > fs * 0.3 && inkH < fs * 2)) inkH = fs * 0.9;
    var cy0 = r.top + inkH / 2, trk = (td.tracking || 0) / 1000 * fs;
    for (i = 0; i < nL; i++) { var cs0 = jzChars(lines[i]); if (cs0.length > 1) G.vert = false; G.lines.push({ chars: cs0, cy: cy0 + i * lead, x0: r.left, x1: r.left + r.width }); }
    var T = null;
    if (measure && !G.arranged && jzCount(td.text) <= 60) { try { T = m.ctx.comp.layers.addText('x'); } catch (e3) { T = null; } }
    var LEFT = ParagraphJustification.LEFT_JUSTIFY;
    try {
        for (i = 0; i < nL; i++) {
            var ln = G.lines[i], cs = ln.chars, n = cs.length;
            if (!n) continue;
            var F = null, F0 = null, off = 0, prev = 0;
            if (T && !G.vert) {
                F = eb2_meas(T, L, lines[i], td.justification); ln.x0 = F.l; ln.x1 = F.r;
                if (n > 1) { F0 = eb2_meas(T, L, lines[i], LEFT); off = F.l - F0.l; }
                prev = F.l - trk;
            } else if (!G.vert) {
                var wEst = r.width / Math.max(1, n), lx = r.left + (r.width - wEst * n) / 2;
                ln.x0 = lx; ln.x1 = lx + wEst * n; prev = lx - trk;
            }
            for (j = 0; j < n; j++) {
                var sp = eb2_isSp(cs[j]), x0, x1;
                if (G.vert) { x0 = r.left; x1 = r.left + r.width; }
                else if (T) {
                    x0 = prev + trk;
                    x1 = (j === n - 1) ? F.r : off + eb2_meas(T, L, cs.slice(0, j + 1).join(''), LEFT).r;
                    if (x1 < x0) x1 = x0;
                    prev = x1;
                } else { x0 = prev + trk; x1 = x0 + (r.width + trk) / Math.max(1, n) - trk; prev = x1; }
                var gi = { i: G.N, li: i, sp: sp, x0: x0, x1: x1, cx: (x0 + x1) / 2, cy: ln.cy, w: Math.max(1, x1 - x0), h: fs };
                if (G.vert) gi.w = Math.min(r.width, fs);
                G.g.push(gi); G.N++;
            }
        }
    } catch (e4) { jzWarn('eb2 geometry: ' + e4.toString()); }
    if (T) { try { T.remove(); } catch (e5) {} }
    return G;
}
// reading lines as [{li, u0, u1, v}] (u along the reading direction, v = line centre across it); vertical text = one column
function eb2_lines(G) {
    var r = G.rect, out = [], i, j;
    if (G.vert) return [{ li: 0, u0: r.top, u1: r.top + r.height, v: r.left + r.width / 2 }];
    for (i = 0; i < G.nL; i++) {
        var u0 = 1e9, u1 = -1e9;
        for (j = 0; j < G.N; j++) { var g = G.g[j]; if (g.li !== i || g.sp) continue; if (g.x0 < u0) u0 = g.x0; if (g.x1 > u1) u1 = g.x1; }
        if (u1 > u0) out.push({ li: i, u0: u0, u1: u1, v: G.lines[i].cy });
    }
    return out;
}
// rect mask in the layer's space with optional opacity / expansion expressions
function eb2_mask(Lx, x0, y0, x1, y1, opEx, exEx, name) {
    var mk = jzMaskRect(Lx, x0, y0, x1, y1);
    try { if (name) mk.name = name; } catch (e) {}
    if (opEx) jzSetExpr(mk.property('ADBE Mask Opacity'), opEx);
    if (exEx) jzSetExpr(mk.property('ADBE Mask Offset'), exEx);
    return mk;
}
// circle mask of radius r0 (grown by expansion) / closed polygon mask
function eb2_circ(Lx, cx, cy, r0, name) {
    var mk = Lx.property('ADBE Mask Parade').addProperty('ADBE Mask Atom'), sh = new Shape(), k = r0 * 0.5523;
    sh.vertices = [[cx, cy - r0], [cx + r0, cy], [cx, cy + r0], [cx - r0, cy]];
    sh.inTangents = [[-k, 0], [0, -k], [k, 0], [0, k]]; sh.outTangents = [[k, 0], [0, k], [-k, 0], [0, -k]]; sh.closed = true;
    mk.property('ADBE Mask Shape').setValue(sh);
    try { if (name) mk.name = name; } catch (e) {}
    return mk;
}
function eb2_poly(Lx, pts, name) {
    var mk = Lx.property('ADBE Mask Parade').addProperty('ADBE Mask Atom'), sh = new Shape();
    sh.vertices = pts; sh.closed = true;
    mk.property('ADBE Mask Shape').setValue(sh);
    try { if (name) mk.name = name; } catch (e) {}
    return mk;
}
// helper shape layer drawn in the text layer's own space (parented to it, right above it); keep = full length (mattes)
function eb2_shape(m, name, keep) {
    var L = m.L, S = m.ctx.comp.layers.addShape(), t = eb2_T(m);
    try { S.name = name; } catch (e0) {}
    try { S.moveBefore(L); } catch (e1) {}
    S.parent = L;
    var tr = S.property('ADBE Transform Group');
    tr.property('ADBE Anchor Point').setValue([0, 0]); tr.property('ADBE Position').setValue([0, 0]);
    tr.property('ADBE Scale').setValue([100, 100]); tr.property('ADBE Rotate Z').setValue(0);
    // helper graphics are drawn in the browser's main pass only (ghost off); mattes (keep) clip the lyric in the ghosts too
    if (!keep) { try { S.outPoint = Math.max(S.inPoint + 0.05, Math.min(S.outPoint, t.DL + t.IN + 0.1)); } catch (e2) {} jzNoGhost(S); }
    return S;
}
// a copy of the lyric layer that lives only during the entrance: parented to L (follows every move of it), made before the
// entrance's own animators are added. posEx: position expression (default: exactly on L); opEx: extra opacity factor.
function eb2_twin(m, tag, below, posEx, opEx) {
    var L = m.L, D = L.duplicate(), t = eb2_T(m), i, mns = ['ADBE Position', 'ADBE Scale', 'ADBE Rotate Z', 'ADBE Opacity'];
    try { D.name = String(L.name).substr(0, 20) + ' ' + tag; } catch (e0) {}
    var tr = D.property('ADBE Transform Group');
    for (i = 0; i < mns.length; i++) { try { if (tr.property(mns[i]).expression) tr.property(mns[i]).expression = ''; } catch (e1) {} }
    D.parent = L;
    tr.property('ADBE Scale').setValue([100, 100]); tr.property('ADBE Rotate Z').setValue(0);
    jzSetExpr(tr.property('ADBE Position'), posEx || 'parent.transform.anchorPoint');
    jzSetExpr(tr.property('ADBE Opacity'), eb2_head(m) + 'time>=DL&&time<DL+IN?value' + (opEx ? '*(' + opEx + ')' : '') + ':0');
    try { D.outPoint = Math.max(D.inPoint + 0.05, Math.min(D.outPoint, t.DL + t.IN + 0.1)); } catch (e2) {}
    if (below) { try { D.moveAfter(L); } catch (e3) {} }
    return D;
}
// alpha track matte: L shows only inside S (S right above L). AE 23+ links mattes with setTrackMatte
function eb2_canMatte(m) {
    try { var t = m.L.trackMatteType; return !t || t === TrackMatteType.NO_TRACK_MATTE; } catch (e) { return false; }
}
function eb2_matte(m, S) {
    var L = m.L, ok = false;
    try { S.moveBefore(L); } catch (e0) {}
    if (typeof L.setTrackMatte === 'function') { try { L.setTrackMatte(S, TrackMatteType.ALPHA); ok = true; } catch (e1) { ok = false; } }
    if (!ok) { try { L.trackMatteType = TrackMatteType.ALPHA; } catch (e2) { jzWarn('eb2 matte: ' + e2.toString()); } }
}
// matte group that shows everything once the entrance is over (holds / exits may move glyphs anywhere)
function eb2_fullMatte(m, S, cx, cy) {
    var g = jzGrp(S, 'after entrance'), H = eb2_head(m);
    jzAddRect(g, 40000, 40000, 0, cx, cy); jzAddFill(g, '#FFFFFF');
    jzSetExpr(jzGX(g).property('ADBE Vector Group Opacity'), H + 'time>=DL+IN?100:0');
}
// add a stroke to the text document (width 0, animated by a Stroke Width animator) when it has none; returns true if added
function eb2_addStroke(L) {
    var td = eb2_td(L);
    if (!td || (td.applyStroke && td.strokeWidth > 0)) return false;
    jzTextDoc(L, function (d) {
        d.applyStroke = true; d.strokeWidth = 0;
        try { d.strokeColor = d.applyFill ? d.fillColor : d.strokeColor; } catch (e) {}
        try { d.strokeOverFill = false; } catch (e2) {}
    });
    return true;
}
// keep the text box centre fixed while the layer is scaled by fx / fy (expression strings; pre: variables they need)
function eb2_pivot(m, fx, fy, pre) {
    var L = m.L, tr = L.property('ADBE Transform Group'), a = tr.property('ADBE Anchor Point').value, s = tr.property('ADBE Scale').value;
    var rot = tr.property('ADBE Rotate Z').value * Math.PI / 180, r = jzRect(L);
    var ux = (r.left + r.width / 2 - a[0]) * s[0] / 100, uy = (r.top + r.height / 2 - a[1]) * s[1] / 100;
    if (Math.abs(ux) + Math.abs(uy) < 0.5) return;
    var c = Math.cos(rot), sn = Math.sin(rot);
    m.parts.pos.push((pre || '') + 'var b2px=(1-(' + fx + '))*' + jzN(ux) + ',b2py=(1-(' + fy + '))*' + jzN(uy) + ';d=[d[0]+' + jzN(c) + '*b2px-' + jzN(sn) + '*b2py,d[1]+' + jzN(sn) + '*b2px+' + jzN(c) + '*b2py];');
}
// Transform effect (layer space): anchor at (cx, cy); position / rotation / scale expressions (already with header)
function eb2_xform(Lx, name, cx, cy, posEx, rotEx, sxEx, syEx) {
    var e = jzEffect(Lx, 'ADBE Geometry2', name);
    jzEP(e, 1, [cx, cy]); jzEP(e, 2, [cx, cy]);
    if (posEx) jzEX(e, 2, posEx);
    if (sxEx || syEx) { jzEP(e, 3, 0); if (syEx) jzEX(e, 4, syEx); if (sxEx) jzEX(e, 5, sxEx); }
    if (rotEx) jzEX(e, 8, rotEx);
    return e;
}
// Venetian Blinds as a print pattern (completion %, direction deg, width px)
function eb2_blinds(Lx, name, comp, dir, w) {
    var vb = jzEffect(Lx, 'ADBE Venetian Blinds', name);
    jzEP(vb, 1, comp); jzEP(vb, 2, dir); jzEP(vb, 3, w); jzEP(vb, 4, 0);
    return vb;
}
// wavy water surface: two sine Wave Warps with vertical displacement (same numbers on every layer that shows the surface)
function eb2_waves(Lx, E, sz, ph) {
    var k, W = [[1, sz * 1.7, 'time*401+' + jzN(ph * 57.2958)], [0.45, sz * 1.7 / 2.3, '-time*286']];
    for (k = 0; k < 2; k++) {
        var ww = jzEffect(Lx, 'ADBE Wave Warp', 'JZ In Surface ' + (k + 1));
        jzEP(ww, 1, 1); jzEP(ww, 3, W[k][1]); jzEP(ww, 4, 90); jzEP(ww, 5, 0); jzEP(ww, 6, 1);
        jzEX(ww, 2, E + 'amp*' + jzN(W[k][0]));
        jzEX(ww, 7, W[k][2]);
    }
}
function eb2_wipe(m, angle, completionEx) {
    var lw = jzEffect(m.L, 'ADBE Linear Wipe', 'JZ In Wipe');
    jzEP(lw, 2, angle); jzEP(lw, 3, 0); jzEX(lw, 1, completionEx);
}

/* ---- heatHaze \u2014 \u967D\u708E: the line shimmers in through heat haze \u2014 thin slices wobble sideways and calm down */
jzReg('enter', 'heatHaze', { apply: function (m) {
    var L = m.L, G = eb2_geo(m, false), r = G.rect, sz = m.size, vert = G.vert;
    var h = (vert ? r.width : r.height) + sz * 0.6, n = jzClamp(Math.round(h / (sz * 0.085)), 8, 14), band = h / n;
    // browser: band k shifts by amp*sin(k*0.9+t*17)*(0.55+0.45*sin(k*0.37+t*6)) -> two sine waves across the slices
    var A = m.HD + EB2_FNS + 'var A=SZ*0.36*(1-ios(P));', W = [[0.72, band * 2 * Math.PI / 0.9, 'time*974'], [0.3, band * 2 * Math.PI / 0.37, '90-time*344']], k;
    for (k = 0; k < 2; k++) {
        var ww = jzEffect(L, 'ADBE Wave Warp', 'JZ In Haze ' + (k + 1));
        jzEP(ww, 1, 1); jzEP(ww, 3, W[k][1]); jzEP(ww, 4, vert ? 90 : 0); jzEP(ww, 5, 0); jzEP(ww, 6, 1);
        jzEX(ww, 2, A + 'A*' + jzN(W[k][0]));
        jzEX(ww, 7, W[k][2]);
    }
    m.parts.op.push('f*=oc(P*2.4);');
} });

/* ---- crtOn \u2014 CRT\u96FB\u6E90: a bright dot stretches into a scan line, which opens vertically into the (hot \u2192 own colour) text */
jzReg('enter', 'crtOn', { apply: function (m) {
    var L = m.L, r = jzRect(L), sz = m.size, cx = r.left + r.width / 2, cy = r.top + r.height / 2, hot = eb2_hot(m), H = m.HD + EB2_FNS, k;
    // the squash is a Transform effect about the box centre (the scan line parented to the layer must not be squashed)
    eb2_xform(L, 'JZ In CRT', cx, cy, null, null, H + '(1.18-0.18*oc((P-0.24)/0.5))*100', H + 'Math.max(0.012,ob((P-0.24)/0.5,1.6))*100');
    m.parts.op.push('f*=P<0.24?0:1;');
    jzAnimator(L, 'JZ In CRT Hot', [['ADBE Text Fill Color', jzHex(hot)]], H + '(1-sm(0.3,0.95,P))*100');
    if (!eb2_prim(m)) return;
    var S = eb2_shape(m, 'JZ In CRT Line');
    var E = eb2_head(m) + 'var la=(1-sm(0.3,0.55,P))*cl(P*12),w=' + jzN(r.width + sz * 0.6) + '*o4(P/0.3),th=' + jzN(Math.max(2 * m.u, sz * 0.045)) + '*(1+2*sm(0.2,0.3,P));';
    for (k = 0; k < 2; k++) {
        var grp = jzGrp(S, k ? 'line' : 'glow'), rc = jzAddRect(grp, 10, 10, 0);
        jzSetExpr(rc.property('ADBE Vector Rect Size'), E + '[Math.max(0.01,w),th*' + (k ? 1 : 4) + ']');   // before the fill is added (keeps rc valid)
        jzAddFill(grp, hot);
        jzGX(grp).property('ADBE Vector Position').setValue([cx, cy]);
        jzSetExpr(jzGX(grp).property('ADBE Vector Group Opacity'), E + 'la*' + (k ? 100 : 25));
    }
} });

/* ---- interlace \u2014 \u30A4\u30F3\u30BF\u30FC\u30EC\u30FC\u30B9: the even scanlines are drawn top to bottom, then a second field fills the odd ones */
jzReg('enter', 'interlace', { apply: function (m) {
    var L = m.L, G = eb2_geo(m, false), r = G.rect, sz = m.size, hr = Math.max(2.5 * m.u, sz * 0.065), pad = sz * 0.25, k;
    var V0 = r.top - pad, V1 = r.top + r.height + pad, span = V1 - V0, n = Math.min(260, Math.ceil(span / hr));
    var x0 = r.left - sz, x1 = r.left + r.width + sz, big = span + sz * 20, H = eb2_head(m), ff = 'var f1=cl(P/0.52),f2=cl((P-0.48)/0.52);';
    if (G.masked) { eb2_wipe(m, 180, H + ff + '(1-(f1+f2)/2)*100'); }
    else {
        // ((y < field-2 front) OR even line) AND (y < field-1 front); the fronts are rect masks opened by expansion
        eb2_mask(L, x0, V0 - big, x1, V0, null, H + ff + 'time>=DL+IN?1e4:f2*' + jzN(span), 'JZ In Field 2');
        var xs = x0 - sz * 0.3, pts = [[xs, V0]];      // comb of the even scanlines, joined by a spine left of the text
        for (k = 0; k < n; k += 2) {
            var ya = V0 + k * hr, yb = ya + hr;
            pts.push([x1, ya]); pts.push([x1, yb]);
            if (k + 2 < n) { pts.push([x0, yb]); pts.push([x0, yb + hr]); } else pts.push([xs, yb]);
        }
        eb2_poly(L, pts, 'JZ In Field 1');
        var m3 = eb2_mask(L, x0, V0 - big, x1, V0, null, H + ff + 'time>=DL+IN?1e4:f1*' + jzN(span), 'JZ In Scan');
        try { m3.maskMode = MaskMode.INTERSECT; } catch (e0) {}
    }
    if (!eb2_prim(m, G)) return;
    var S = eb2_shape(m, 'JZ In Scanline'), gr = jzGrp(S, 'scan');
    jzAddRect(gr, r.width + sz * 0.6, Math.max(1.5 * m.u, sz * 0.02), 0);
    jzAddFill(gr, eb2_hot(m));
    var E = H + ff + 'var f=P<0.5?f1:f2,fd=P<0.5?1:1-sm(0.85,0.99,f);';
    jzSetExpr(jzGX(gr).property('ADBE Vector Position'), E + '[' + jzN(r.left + r.width / 2) + ',' + jzN(V0) + '+f*' + jzN(span) + ']');
    jzSetExpr(jzGX(gr).property('ADBE Vector Group Opacity'), E + 'f>0&&f<1?90*fd:0');
} });

/* ---- loadingBar \u2014 \u30ED\u30FC\u30C7\u30A3\u30F3\u30B0: a progress bar fills in uneven bursts (with a % counter); glyphs pop up as it passes under them */
jzReg('enter', 'loadingBar', { selfHide: true, apply: function (m) {
    var L = m.L, G = eb2_geo(m, true), r = G.rect, sz = m.size, vert = G.vert, seed = m.c.seed | 0, sc = m.ctx.sc, i, k;
    var KY = [[0.06, 0], [0.2, 0.16 + 0.08 * eb2_r(seed, 1)], [0.3, 0.24 + 0.08 * eb2_r(seed, 2)], [0.42, 0.52 + 0.1 * eb2_r(seed, 3)], [0.52, 0.6 + 0.08 * eb2_r(seed, 4)], [0.7, 1]], ks = [], F = [];
    for (i = 0; i < KY.length; i++) ks.push(eb2_pt(KY[i][0], KY[i][1]));
    var PR = 'var KY=[' + ks.join(',') + '],pr=0;for(var kk=1;kk<KY.length;kk++)if(P>=KY[kk-1][0])pr=KY[kk-1][1]+(KY[kk][1]-KY[kk-1][1])*oc((P-KY[kk-1][0])/(KY[kk][0]-KY[kk-1][0]));';
    var U0 = vert ? r.top : r.left, span = Math.max(1, vert ? r.height : r.width);
    for (i = 0; i < G.N; i++) F.push(((vert ? G.g[i].cy : G.g[i].cx) - U0) / span);
    var q = m.HD + PR + 'var F=' + eb2_arr(F) + ',q=cl((pr-(F[textIndex-1]||0))/0.12+0.35);';
    jzAnimator(L, 'JZ In Load Pop', [['ADBE Text Position 3D', vert ? [-sz * 0.25, 0, 0] : [0, sz * 0.25, 0]]], q + 'q<=0||q>=1?0:(1-ob(q,2))*100');
    jzAnimator(L, 'JZ In Load Hide', [['ADBE Text Opacity', 0]], q + 'q<=0?100:(1-cl(q*3))*100');
    if (!eb2_prim(m, G)) return;
    var th = Math.max(3 * m.u, sz * 0.07), gap = sz * 0.24, Ln = vert ? r.height : r.width, fs = Math.max(14 * m.u, sz * 0.26);
    var E = eb2_head(m) + PR + 'var out=sm(0.74,0.96,P),bA=cl(P/0.08)*(1-sm(0.88,1,P)),lo=' + jzN(Ln) + '*out,hi=' + jzN(Ln) + '*pr;';
    var S = eb2_shape(m, 'JZ In Loading'), yb = r.top + r.height + gap, xb = r.left - gap - th;
    for (k = 0; k < 2; k++) {
        // the rect is set up before the fill is added (adding to the contents invalidates older item references)
        var grp = jzGrp(S, k ? 'fill' : 'track'), rc = jzAddRect(grp, 10, 10, 0);
        var ln = 'var s0=lo,s1=Math.max(s0,' + (k ? 'hi' : jzN(Ln)) + ');';
        if (!vert) {
            jzSetExpr(rc.property('ADBE Vector Rect Size'), E + ln + '[s1-s0,' + jzN(th) + ']');
            jzSetExpr(rc.property('ADBE Vector Rect Position'), E + ln + '[' + jzN(r.left) + '+(s0+s1)/2,' + jzN(yb + th / 2) + ']');
        } else {
            jzSetExpr(rc.property('ADBE Vector Rect Size'), E + ln + '[' + jzN(th) + ',s1-s0]');
            jzSetExpr(rc.property('ADBE Vector Rect Position'), E + ln + '[' + jzN(xb + th / 2) + ',' + jzN(r.top) + '+(s0+s1)/2]');
        }
        jzAddFill(grp, k ? eb2_pick(sc.accent, sc.fg) : eb2_pick(sc.sub, sc.fg), k ? 100 : 30);
        jzSetExpr(jzGX(grp).property('ADBE Vector Group Opacity'), E + (k ? '(hi>lo?bA:0)*100' : 'bA*100'));
    }
    // the % counter (mono, sub colour), parented to the lyric
    var T = null;
    try { T = jzText(m.ctx, '100%', { size: fs, font: jzMonoF(m.ctx), color: eb2_pick(sc.sub, sc.fg), align: vert ? 'center' : 'left', x: 0, y: 0, name: 'JZ In Loading %' }); } catch (e0) { T = null; }
    if (!T) return;
    jzNoGhost(T);
    try { T.moveBefore(L); } catch (e1) {}
    T.parent = L;
    var tt = T.property('ADBE Transform Group'), t = eb2_T(m);
    tt.property('ADBE Position').setValue(vert ? [xb + th / 2, r.top + r.height + fs * 0.9] : [r.left + r.width + fs * 0.4, yb + th / 2]);
    tt.property('ADBE Scale').setValue([100, 100]); tt.property('ADBE Rotate Z').setValue(0);
    jzSetExpr(T.property('ADBE Text Properties').property('ADBE Text Document'), E + 'Math.round(pr*100)+"%"');
    jzSetExpr(tt.property('ADBE Opacity'), E + 'bA*(1-out)*100');
    try { T.outPoint = Math.max(T.inPoint + 0.05, Math.min(T.outPoint, t.DL + t.IN + 0.1)); } catch (e2) {}
} });

/* ---- dither \u2014 \u30C7\u30A3\u30B6: ordered (Bayer 4\u00D74) dither dissolve \u2014 the line fills in through a regular crosshatch of pixels */
jzReg('enter', 'dither', { apply: function (m) {
    var L = m.L, r = jzRect(L), sz = m.size, dir = eb2_dir(m, 101), H = eb2_head(m), bx, by;
    var pad = sz * 0.2, W = r.width + pad * 2, Hh = r.height + pad * 2, cs = Math.max(3 * m.u, sz * 0.075);
    while ((W / cs) * (Hh / cs) > 1400) cs *= 1.2;
    var cx = r.left + r.width / 2, cy = r.top + r.height / 2;
    if (!eb2_canMatte(m) || eb2_crowded(m)) {      // no matte possible: random block dissolve
        var bd = jzEffect(L, 'ADBE Block Dissolve', 'JZ In Dither');
        jzEP(bd, 2, cs); jzEP(bd, 3, cs); jzEP(bd, 4, 0); jzEP(bd, 5, 0);
        jzEX(bd, 1, H + '(1-cl(P*1.1))*100');
        return;
    }
    var nx = Math.ceil(W / cs), ny = Math.ceil(Hh / cs), ox = cx - nx * cs / 2, oy = cy - ny * cs / 2, ov = cs * 0.02 + 0.6;
    // one lattice (period 4 cells) per Bayer level; a cell is on once P*1.35 - 0.3*u > threshold (u = column 0..1 along dir):
    // the lattice's column count grows from the leading side (Repeater copies), rows are all there (second Repeater)
    var S = eb2_shape(m, 'JZ In Dither Matte', true);
    for (by = 0; by < 4 && by < ny; by++) for (bx = 0; bx < 4 && bx < nx; bx++) {
        var th = (EB2_BAYER[by * 4 + bx] + 0.5) / 16, nxl = Math.ceil((nx - bx) / 4), nyl = Math.ceil((ny - by) / 4);
        var ixs = dir > 0 ? bx : bx + 4 * (nxl - 1), lead = dir > 0 ? bx : nx - 1 - ixs;
        var grp = jzGrp(S, 'level ' + EB2_BAYER[by * 4 + bx]);
        jzAddRect(grp, cs + ov, cs + ov, 0, ox + (ixs + 0.5) * cs, oy + (by + 0.5) * cs);
        var r1 = jzVecs(grp).addProperty('ADBE Vector Filter - Repeater');
        jzSetExpr(r1.property('ADBE Vector Repeater Copies'), H + 'var F=(P*1.35-' + jzN(th) + ')/0.3*' + (nx - 1) + ';Math.max(0,Math.min(' + nxl + ',Math.ceil((F-' + lead + ')/4)))');
        r1.property('ADBE Vector Repeater Transform').property('ADBE Vector Repeater Position').setValue([dir * 4 * cs, 0]);
        var r2 = jzVecs(grp).addProperty('ADBE Vector Filter - Repeater');
        r2.property('ADBE Vector Repeater Copies').setValue(nyl);
        r2.property('ADBE Vector Repeater Transform').property('ADBE Vector Repeater Position').setValue([0, 4 * cs]);
        jzAddFill(grp, '#FFFFFF');
    }
    eb2_fullMatte(m, S, cx, cy);
    eb2_matte(m, S);
} });

/* ---- odometer \u2014 \u30C9\u30E9\u30E0\u56DE\u8EE2: each glyph rolls up through a few characters on its own drum and clicks to a stop */
function eb2_reel(Lx, q, fs, twin) {
    var K = fs * 0.56;
    jzAnimator(Lx, 'JZ In Reel Roll', [['ADBE Text Position 3D', [0, K, 0]]], q + 'on?Math.sin(f*Math.PI/2)*100:0');
    jzAnimator(Lx, 'JZ In Reel Drum', [['ADBE Text Scale 3D', [100, 0, 100]]], q + 'on?(1-Math.cos(f*Math.PI/2))*100:0');
    jzAnimator(Lx, 'JZ In Reel Char', [['ADBE Text Character Offset', 100]], q + 'on&&j>0?1+Math.floor(hh(textIndex*31+j*7+SD)*40):0');
    jzAnimator(Lx, 'JZ In Reel Hide', [['ADBE Text Opacity', 0]], q + (twin ? '!on?100:' : 'q<=0||(q<1&&!on)?100:') + '35*Math.abs(f)');
}
jzReg('enter', 'odometer', { selfHide: true, apply: function (m) {
    var L = m.L, G = eb2_geo(m, false), D = eb2_crowded(m, G) ? null : eb2_twin(m, 'JZ In Reel B');
    // reel of R cells (0 = the real glyph); s runs R -> 0 with a little overshoot. The lyric shows cell floor(s) coming up from
    // below, the copy cell floor(s)+1 leaving upwards; both are projected on a drum (squashed towards its rim) instead of a window mask
    var base = m.HD + EB2_FNS + eb2_q(0.45) + 'var R=3+Math.floor(hh(textIndex*13+SD)*3),s=R*(1-ob(q,1.15)),j=Math.floor(s),f=s-j;';
    eb2_reel(L, base + 'var on=q>0&&q<1&&j>=0;', G.fs, false);
    if (D) eb2_reel(D, base + 'j=j+1;f=f-1;var on=q>0&&q<1&&j<=R;', G.fs, true);
} });

/* ---- matrixRain \u2014 \u30C7\u30FC\u30BF\u964D\u4E0B: each glyph falls in as the bright head of a short stream of flickering characters */
jzReg('enter', 'matrixRain', { selfHide: true, apply: function (m) {
    var L = m.L, G = eb2_geo(m, false), fs = G.fs, sc = m.ctx.sc, hot = eb2_hot(m), acc = eb2_pick(sc.accent, sc.fg), i, k, LI = [], Ds = [];
    for (i = 0; i < G.N; i++) LI.push(G.g[i].li);
    var nl = eb2_layers(m), nT = eb2_crowded(m, G) ? 0 : (nl > 30 ? 1 : (nl > 18 ? 2 : 3));
    for (k = nT; k >= 1; k--) Ds[k - 1] = eb2_twin(m, 'JZ In Rain ' + k, true);
    // lower lines (vertical text: lower glyphs) land first, so no stream runs over a glyph that has already landed
    var base = m.HD + EB2_FNS + 'var LI=' + eb2_arr(LI) + ',NL=' + G.nL + ',ky=0.35*hh(textIndex*7+SD)+0.65*(NL>1?(NL-1-(LI[textIndex-1]||0))/(NL-1):0),' +
        'q=cl((P-0.45*ky)/0.55),D=SZ*(2.2+hh(textIndex*11+SD)),y=-D*(1-oc(q/0.62));';
    var KF = fs * 3.2, SG = 'posterizeTime(12);seedRandom(textIndex*31+SD+Math.floor(time*12)*7';
    jzAnimator(L, 'JZ In Rain Fall', [['ADBE Text Position 3D', [0, -KF, 0]]], base + 'q>0&&q<0.62?-y/' + jzN(KF) + '*100:0');
    jzAnimator(L, 'JZ In Rain Sign', [['ADBE Text Character Offset', 60]], base + SG + ',true);var rr=random(15,100);q>0&&q<0.62?rr:0');
    jzAnimator(L, 'JZ In Rain Hot', [['ADBE Text Fill Color', jzHex(hot)]], base + 'q<=0?0:(q<0.62?100:(1-oc((q-0.62)/0.38))*100)');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], base + 'q<=0?100:0');
    for (k = 1; k <= nT; k++) {
        var D = Ds[k - 1], KT = KF + fs * 0.92 * k;
        var bk = base + 'var on=q>0&&q<1,a=' + jzN((1 - k / 5) * 0.75) + '*(1-sm(0.558,1,q))*cl(q*6);';
        jzAnimator(D, 'JZ Rain Fall', [['ADBE Text Position 3D', [0, -KT, 0]]], bk + 'on?(-y+' + jzN(fs * 0.92 * k) + ')/' + jzN(KT) + '*100:0');
        jzAnimator(D, 'JZ Rain Sign', [['ADBE Text Character Offset', 60]], bk + SG + '+' + (k * 1009) + ',true);var rr=random(15,100);on?rr:0');
        jzAnimator(D, 'JZ Rain Tint', [['ADBE Text Fill Color', jzHex(acc)], ['ADBE Text Scale 3D', [90, 90, 100]]], '100');
        jzAnimator(D, 'JZ Rain Fade', [['ADBE Text Opacity', 0]], bk + 'on?(1-a)*100:100');
    }
} });

/* ---- hatchFill \u2014 \u30CF\u30C3\u30C1\u2192\u30D9\u30BF: each glyph is first printed as a hatch / halftone pattern with a hairline outline, then the ink fills in */
jzReg('enter', 'hatchFill', { selfHide: true, apply: function (m) {
    var L = m.L, sz = m.size, td = eb2_td(L), pat = ['stripes', 'hatch', 'dots'][eb2_h(m.c.seed | 0, 131) % 3];
    var D = (td && td.applyFill !== false && !eb2_crowded(m)) ? jzNoGhost(eb2_twin(m, 'JZ In Hatch', true)) : null;
    var q = m.HD + EB2_FNS + eb2_q(0.45);
    if (eb2_addStroke(L)) jzAnimator(L, 'JZ In Hatch Line', [['ADBE Text Stroke Width', Math.max(0.8 * m.u, sz * 0.014)]], q + EB2_OWNSTROKE + 's0>0.5||q<=0||q>=1?0:(1-sm(0.75,1,q))*100');
    jzAnimator(L, 'JZ In Hatch Fill', [['ADBE Text Fill Opacity', 0]], q + 'q<=0.35?100:(1-ios((q-0.35)/0.55))*100');
    jzAnimator(L, 'JZ In Hatch Settle', [['ADBE Text Scale 3D', [110, 110, 100]]], q + 'q<=0||q>=1?0:(1-oc(q/0.4))*100');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + 'q<=0?100:(1-cl(q*5))*100');
    if (!D) return;
    // the pattern print (behind): same glyphs through Venetian Blinds (stripes / cross-cut dots)
    jzAnimator(D, 'JZ Hatch Settle', [['ADBE Text Scale 3D', [110, 110, 100]]], q + 'q<=0||q>=1?0:(1-oc(q/0.4))*100');
    jzAnimator(D, 'JZ Hatch Fade', [['ADBE Text Opacity', 0]], q + 'q<=0||q>=1?100:(1-cl(q*5)*(1-sm(0.75,1,q)))*100');
    var wv = Math.max(3 * m.u, sz * 0.085);
    if (pat === 'dots') { eb2_blinds(D, 'JZ Hatch Dots A', 45, 0, wv); eb2_blinds(D, 'JZ Hatch Dots B', 45, 90, wv); }
    else if (pat === 'hatch') eb2_blinds(D, 'JZ Hatch', 62, 135, wv * 0.8);
    else eb2_blinds(D, 'JZ Hatch Stripes', 55, 45, wv);
} });

/* ---- brushReveal \u2014 \u7B46\u6255\u3044: a dry-brush pass (bristle tips in the accent colour) paints each line in, line after line */
jzReg('enter', 'brushReveal', { apply: function (m) {
    var L = m.L, G = eb2_geo(m, true), sz = m.size, seed = m.c.seed | 0, KS = 14, tail = sz * 1.1, pad = sz * 0.2, tip = sz * 0.32, i, k;
    var LS = eb2_lines(G), nL = LS.length, H = eb2_head(m), r = G.rect, vert = G.vert;
    if (!eb2_canMatte(m) || !nL || nL > 12 || eb2_crowded(m, G)) { eb2_wipe(m, vert ? 180 : 270, H + '(1-ios(P))*100'); return; }
    var T = eb2_main(m, G) ? eb2_shape(m, 'JZ In Brush Tips') : null, S = eb2_shape(m, 'JZ In Brush Matte', true), acc = eb2_pick(m.ctx.sc.accent, m.ctx.sc.fg);
    var hv = G.fs * 0.66, th = hv * 2 / KS;
    for (i = 0; i < nL; i++) {
        var ln = LS[i], ord = nL > 1 ? i / (nL - 1) : 0, spr = nL > 1 ? 0.4 : 0, a0 = ln.u0 - pad;
        var LQ = H + 'var q=cl((P-' + jzN(spr * ord) + ')/' + jzN(1 - spr) + '),F=' + jzN(a0) + '+' + jzN(ln.u1 + pad + tail - a0) + '*ios(q),a=' + jzN(a0) + ';';
        for (k = 0; k < KS; k++) {
            var lag = tail * Math.pow(eb2_r(seed, ln.li, k, 141), 1.6), vc = ln.v - hv + (k + 0.5) * th;
            var SE = LQ + 'var e=Math.max(a,F-' + jzN(lag) + ');';
            // each rect is set up before its fill is added (adding to the contents invalidates older item references)
            var g = jzGrp(S, 'strip ' + (i + 1) + '.' + (k + 1)), rc = jzAddRect(g, 10, 10, 0);
            jzSetExpr(rc.property('ADBE Vector Rect Size'), SE + (vert ? '[' + jzN(th + 0.8) + ',e-a]' : '[e-a,' + jzN(th + 0.8) + ']'));
            jzSetExpr(rc.property('ADBE Vector Rect Position'), SE + (vert ? '[' + jzN(vc) + ',(a+e)/2]' : '[(a+e)/2,' + jzN(vc) + ']'));
            jzAddFill(g, '#FFFFFF');
            if (!T) continue;
            var tg = jzGrp(T, 'tip ' + (i + 1) + '.' + (k + 1)), tr = jzAddRect(tg, 10, 10, 0), TE = SE + 'var t0=Math.max(a,e-' + jzN(tip) + ');';
            jzSetExpr(tr.property('ADBE Vector Rect Size'), TE + (vert ? '[' + jzN(th * 0.92) + ',e-t0]' : '[e-t0,' + jzN(th * 0.92) + ']'));
            jzSetExpr(tr.property('ADBE Vector Rect Position'), TE + (vert ? '[' + jzN(vc) + ',(t0+e)/2]' : '[(t0+e)/2,' + jzN(vc) + ']'));
            jzAddFill(tg, acc);
            jzSetExpr(jzGX(tg).property('ADBE Vector Group Opacity'), TE + 'q>0&&e>a&&e<=' + jzN(ln.u1 + pad + tip) + '?90*(1-sm(0.7,0.95,q)):0');
        }
    }
    eb2_fullMatte(m, S, r.left + r.width / 2, r.top + r.height / 2);
    eb2_matte(m, S);
} });

/* ---- inkDrop \u2014 \u30A4\u30F3\u30AF\u6EF4: an ink drop lands inside every glyph (random order) and spreads out in a blot that reveals it */
jzReg('enter', 'inkDrop', { apply: function (m) {
    var L = m.L, G = eb2_geo(m, true), sz = m.size, seed = m.c.seed | 0, H = eb2_head(m), i, first = true;
    if (G.masked || G.arranged || eb2_crowded(m, G) || G.N > 40) {       // no per-glyph masks: glyphs bleed in (blur + fade), random order
        var qb = m.HD + EB2_FNS + 'var q=cl((P-0.5*hh(textIndex*7+SD))/0.5);';
        jzAnimator(L, 'JZ In Ink', [['ADBE Text Blur', [sz * 0.12, sz * 0.12]], ['ADBE Text Opacity', 0], ['ADBE Text Scale 3D', [60, 60, 100]]], qb + '(1-oc(q))*100');
        return;
    }
    var T = eb2_main(m, G) ? eb2_shape(m, 'JZ In Ink Drops') : null, acc = eb2_pick(m.ctx.sc.accent, m.ctx.sc.fg);
    for (i = 0; i < G.N; i++) {
        var g = G.g[i]; if (g.sp) continue;
        var hw = g.w / 2, hh2 = g.h / 2, px = g.cx + (eb2_r(seed, i, 152) * 2 - 1) * hw * 0.4, py = g.cy + (eb2_r(seed, i, 153) * 2 - 1) * hh2 * 0.4;
        var ax = hw + Math.abs(px - g.cx), ay = hh2 + Math.abs(py - g.cy), R = Math.sqrt(ax * ax + ay * ay) + sz * 0.08;
        var QE = H + 'var q=cl((P-' + jzN(0.5 * eb2_r(seed, i, 151)) + ')/0.5);';
        var mk = eb2_circ(L, px, py, 1, 'JZ In Ink ' + (i + 1));
        jzSetExpr(mk.property('ADBE Mask Opacity'), QE + 'q>0.12||time>=DL+IN?100:0');
        jzSetExpr(mk.property('ADBE Mask Offset'), QE + (first ? 'time>=DL+IN?1e4:' : '') + jzN(R) + '*oc((q-0.12)/0.88)-1');
        first = false;
        if (!T) continue;
        var dg = jzGrp(T, 'drop ' + (i + 1)), el = jzAddEllipse(dg, 10, 10);
        jzSetExpr(el.property('ADBE Vector Ellipse Size'), QE + 'var t=q/0.12,r=' + jzN(sz * 0.07) + '*(q<0.12?ob(t,2):1+(q-0.12)*4);[2*r,2*r]');   // before the fill (keeps el valid)
        jzAddFill(dg, acc);
        jzGX(dg).property('ADBE Vector Position').setValue([px, py]);
        jzSetExpr(jzGX(dg).property('ADBE Vector Group Opacity'), QE + 'q>0&&q<0.4?(q<0.12?100:(1-(q-0.12)/0.28)*100):0');
    }
} });

/* ---- quarters \u2014 \u56DB\u65B9\u96C6\u7D50: the line is cut into four quadrants that fly in from the four corners and lock together */
jzReg('enter', 'quarters', { apply: function (m) {
    var L = m.L, G = eb2_geo(m, false), r = G.rect, sz = m.size, dir = eb2_dir(m, 161), H = eb2_head(m), k;
    var cx = r.left + r.width / 2, cy = r.top + r.height / 2, big = sz * 30, td = eb2_td(L);
    var D = sz * (td && jzCount(String(td.text)) > 1 ? 1.1 : 0.5) + Math.max(r.width, r.height) * 0.12;
    m.parts.op.push('f*=cl(P*5);');
    var Q = [[-1, -1], [1, 1], [1, -1], [-1, 1]], lays = [L];         // diagonal pairs arrive together
    if (G.masked || eb2_crowded(m, G)) {       // no quadrant copies: the whole line settles in from the first corner
        eb2_xform(L, 'JZ In Quarter Move', cx, cy, H + 'var e1=1-o5(P);[' + jzN(cx) + '-' + jzN(D) + '*e1,' + jzN(cy) + '-' + jzN(D) + '*e1]', H + jzN(dir * 9) + '*(1-o5(P))');
        return;
    }
    for (k = 1; k < 4; k++) lays.push(eb2_twin(m, 'JZ In Quarter ' + (k + 1), false, null, 'cl(P*5)'));
    for (k = 0; k < 4; k++) {
        var sx = Q[k][0], sy = Q[k][1], Lk = lays[k], ov = k ? 0.6 : 0;
        var x0 = sx < 0 ? cx - big : cx - ov, x1 = sx < 0 ? cx + ov : cx + big, y0 = sy < 0 ? cy - big : cy - ov, y1 = sy < 0 ? cy + ov : cy + big;
        eb2_mask(Lk, x0, y0, x1, y1, null, k ? null : H + 'time>=DL+IN?1e4:0', 'JZ In Quarter');
        var E = H + 'var e1=1-o5(cl((P-' + (k < 2 ? 0 : 0.2) + ')/0.8));';
        eb2_xform(Lk, 'JZ In Quarter Move', cx, cy, E + '[' + jzN(cx) + '+' + jzN(sx * D) + '*e1,' + jzN(cy) + '+' + jzN(sy * D) + '*e1]', E + jzN(dir * sx * sy * 9) + '*e1');
    }
} });

/* ---- invertBox \u2014 \u53CD\u8EE2\u629C\u3051: a solid block wipes on with the lyric knocked out of it, then drops away and leaves the plain text */
jzReg('enter', 'invertBox', { apply: function (m) {
    var L = m.L, G = eb2_geo(m, false), r = G.rect, sz = m.size, mg = sz * 0.16, sc = m.ctx.sc, H = eb2_head(m), big = sz * 20;
    var col = jzTextColor(L), bg = eb2_pick(sc.bg, '#000000');
    var x0 = r.left - mg, y0 = r.top - mg, x1 = r.left + r.width + mg, y1 = r.top + r.height + mg, w = x1 - x0, h = y1 - y0;
    var E = H + 'var e1=o4(P/0.34),e2=io4((P-0.42)/0.55);';
    if (G.masked) { eb2_wipe(m, 270, E + '(1-e1)*100'); return; }
    var K = (eb2_main(m, G) && !eb2_crowded(m, G)) ? jzNoGhost(eb2_twin(m, 'JZ In Knockout')) : null;
    // the lyric shows only above the block's top edge (where the block has already dropped away)
    eb2_mask(L, x0 - big, y0 - big, x1 + big, y0, E + 'e2>0||time>=DL+IN?100:0', E + 'time>=DL+IN?1e4:e2*' + jzN(h), 'JZ In Uncover');
    var B = eb2_shape(m, 'JZ In Block'), gr = jzGrp(B, 'block'), rc = jzAddRect(gr, 10, 10, 0);
    var BE = E + 'var bw=' + jzN(w) + '*e1,bh=' + jzN(h) + '*(1-e2);';
    jzSetExpr(rc.property('ADBE Vector Rect Size'), BE + '[bw,bh]');       // the rect is set up before the fill is added (keeps rc valid)
    jzSetExpr(rc.property('ADBE Vector Rect Position'), BE + '[' + jzN(x0) + '+bw/2,' + jzN(y1) + '-bh/2]');
    jzAddFill(gr, col);
    jzSetExpr(jzGX(gr).property('ADBE Vector Group Opacity'), BE + 'bw>=0.5&&bh>=0.5?100:0');
    if (!K) return;
    // the knocked-out lyric: a background-coloured copy on top of the block, clipped to it
    try { K.moveBefore(B); } catch (e0) {}
    jzAnimator(K, 'JZ Knockout', [['ADBE Text Fill Color', jzHex(bg)], ['ADBE Text Stroke Color', jzHex(bg)]], '100');
    eb2_mask(K, x0 - big, y0 - big, x0, y1 + big, null, E + 'e1*' + jzN(w), 'JZ Block Right');
    var mk = eb2_mask(K, x0 - big, y0, x1 + big, y1 + big, null, E + '-e2*' + jzN(h), 'JZ Block Top');
    try { mk.maskMode = MaskMode.INTERSECT; } catch (e1) {}
} });

/* ---- printRegister \u2014 \u7248\u30BA\u30EC: misregistered riso print \u2014 two halftone colour plates jolt into register in a few hard steps */
jzReg('enter', 'printRegister', { apply: function (m) {
    var L = m.L, r = jzRect(L), sz = m.size, seed = m.c.seed | 0, sc = m.ctx.sc, j;
    var cx = r.left + r.width / 2, cy = r.top + r.height / 2, a0 = eb2_r(seed, 171) * Math.PI * 2;
    var plates = [eb2_pick(sc.accent), eb2_pick(sc.accent2, sc.ghostA, sc.accent)];
    var E = eb2_head(m) + 'var x4=cl(P/0.8)*4,k=Math.min(3,Math.floor(x4)),e=P>=0.8?1:(k+ob(cl((x4-k)/0.3),2.2))/4,Dd=' + jzN(sz * 0.42) + '*(1-e);';
    var off = function (jj) { var a = a0 + jj * Math.PI * 2 / 3; return '[' + jzN(cx) + '+' + jzN(Math.cos(a)) + '*Dd,' + jzN(cy) + '+' + jzN(Math.sin(a)) + '*Dd]'; };
    var Ds = [];
    if (eb2_main(m) && !eb2_crowded(m)) { Ds.push(jzNoGhost(eb2_twin(m, 'JZ In Plate 1', true, null, 'cl(P*4)*(1-sm(0.8,1,P))'))); Ds.push(jzNoGhost(eb2_twin(m, 'JZ In Plate 2', true, null, 'cl(P*4)*(1-sm(0.8,1,P))'))); }
    eb2_xform(L, 'JZ In Register', cx, cy, E + off(0));
    m.parts.op.push('f*=cl(P*4);');
    var wv = Math.max(3 * m.u, sz * 0.075);
    for (j = 0; j < Ds.length; j++) {
        var D = Ds[j];
        jzAnimator(D, 'JZ Plate Ink', [['ADBE Text Fill Color', jzHex(plates[j])], ['ADBE Text Stroke Color', jzHex(plates[j])]], '100');
        if (j === 0) { eb2_blinds(D, 'JZ Plate Dots A', 45, 0, wv); eb2_blinds(D, 'JZ Plate Dots B', 45, 90, wv); }
        else eb2_blinds(D, 'JZ Plate Lines', 50, 90, wv * 0.9);
        eb2_xform(D, 'JZ Plate Offset', cx, cy, E + off(j + 1));
    }
} });

/* ---- echoCount \u2014 \u30AB\u30A6\u30F3\u30C8\u30A4\u30F3: three stepped copies trail behind the line and drop off one per beat (3, 2, 1, on) */
jzReg('enter', 'echoCount', { apply: function (m) {
    var L = m.L, sz = m.size, dir = eb2_dir(m, 181), sc = m.ctx.sc, col = eb2_pick(sc.accent, sc.sub, sc.fg), k;
    var X = 'var b2x4=cl(P/0.88)*4,b2b=Math.min(3,Math.floor(b2x4)),b2f=b2x4-b2b,b2n=P>=0.88?0:3-b2b,b2u=cl((P-0.9)/0.09);';
    var fS = '(P>=1?1:1+0.14*Math.exp(-b2f*7)*(P<0.88?1:0)+(P>=0.88?0.1*Math.exp(-(P-0.88)*40)*(1-b2u*b2u*(3-2*b2u)):0))';
    m.parts.sc.push(X + 'var b2s=' + fS + ';f=[f[0]*b2s,f[1]*b2s];');
    eb2_pivot(m, fS, fS, X);
    m.parts.op.push('f*=cl(P*10);');
    if (eb2_crowded(m)) return;
    for (k = 3; k >= 1; k--) {        // the copy nearest to the line is directly behind it
        var dx = k * dir * sz * 0.16, dy = k * sz * 0.11;
        var D = eb2_twin(m, 'JZ In Count ' + k, true, 'var a=parent.transform.anchorPoint;[a[0]+' + jzN(dx) + ',a[1]+' + jzN(dy) + ']',
            'cl(P*10)*' + jzN(0.75 * Math.pow(0.72, k - 1)) + '*(P<0.88&&' + k + '<=3-Math.min(3,Math.floor(cl(P/0.88)*4))?1:0)');
        jzAnimator(D, 'JZ Count Ink', [['ADBE Text Fill Color', jzHex(col)], ['ADBE Text Stroke Color', jzHex(col)]], '100');
    }
} });

/* ---- liquidFill \u2014 \u6C34\u4F4D\u4E0A\u6607: the glyphs fill up like glasses \u2014 a wavy liquid level rises through hairline outlines */
jzReg('enter', 'liquidFill', { apply: function (m) {
    var L = m.L, G = eb2_geo(m, false), r = G.rect, sz = m.size, seed = m.c.seed | 0, ph = eb2_r(seed, 201) * Math.PI * 2, td = eb2_td(L);
    var mg = sz * 0.3, X0 = r.left - mg, X1 = r.left + r.width + mg, Y0 = r.top - mg, Y1 = r.top + r.height + mg, cx = r.left + r.width / 2, bot = Y1 + sz * 20;
    var E = eb2_head(m) + 'var e=ios(cl(P/0.94)),amp=' + jzN(sz * 0.08) + '*(1-sm(0.75,1,P)),lvl=' + jzN(Y1) + '+(' + jzN(Y0 - Y1) + '-amp*2)*e,oa=cl(P*6)*(1-sm(0.8,1,P));';
    if (!eb2_canMatte(m) || eb2_crowded(m, G)) { eb2_wipe(m, 0, E + '(1-e)*100'); return; }
    // hairline outline of the glass (behind), only while filling
    if (td && td.applyFill !== false) {
        var D = jzNoGhost(eb2_twin(m, 'JZ In Glass', true, null, '0.45*cl(P*6)*(1-sm(0.8,1,P))')), c0 = td.fillColor;
        jzTextDoc(D, function (d) { d.applyFill = false; d.applyStroke = true; d.strokeColor = c0; d.strokeWidth = Math.max(1 * m.u, sz * 0.014); try { d.strokeOverFill = true; } catch (e0) {} });
    }
    // the surface line (accent) rides the level with the same waves as the matte
    if (eb2_prim(m, G)) {
        var T = eb2_shape(m, 'JZ In Liquid Line'), lg = jzGrp(T, 'surface');
        jzAddPath(lg, [[X0, 0], [X1, 0]], false);
        jzAddStroke(lg, eb2_pick(m.ctx.sc.accent, m.ctx.sc.fg), Math.max(1.5 * m.u, sz * 0.025));
        jzSetExpr(jzGX(lg).property('ADBE Vector Position'), E + '[0,lvl]');
        jzSetExpr(jzGX(lg).property('ADBE Vector Group Opacity'), E + 'e>0.02?oa*100:0');
        eb2_waves(T, E, sz, ph);
    }
    var S = eb2_shape(m, 'JZ In Liquid Matte', true), g = jzGrp(S, 'liquid'), rc = jzAddRect(g, 10, 10, 0);
    jzSetExpr(rc.property('ADBE Vector Rect Size'), E + '[' + jzN(X1 - X0) + ',Math.max(0,' + jzN(bot) + '-lvl)]');   // before the fill (keeps rc valid)
    jzSetExpr(rc.property('ADBE Vector Rect Position'), E + '[' + jzN(cx) + ',(lvl+' + jzN(bot) + ')/2]');
    jzAddFill(g, '#FFFFFF');
    eb2_waves(S, E, sz, ph);
    eb2_fullMatte(m, S, cx, r.top + r.height / 2);
    eb2_matte(m, S);
} });

/* ---- windBlown \u2014 \u98A8\u306B\u4E57\u3063\u3066: glyphs are blown in from one side on a gust, fluttering like leaves before they settle
   (browser: every stroke piece separately -> AE: per glyph) */
jzReg('enter', 'windBlown', { selfHide: true, apply: function (m) {
    var L = m.L, sz = m.size, dir = eb2_dir(m, 191), ks = 1;
    try { ks = Math.abs(jzXf(L, 'ADBE Scale').value[0]) / 100 || 1; } catch (e0) {}
    var Dd = m.W * 0.3 / ks + sz * 2, PX = Dd * 1.2, PY = sz * 1.2;
    var q = m.HD + EB2_FNS + 'var n=textTotal,o=n>1?(textIndex-1)/(n-1):0;' + (dir > 0 ? 'o=1-o;' : '') +
        'var i=textIndex+SD,q=cl((P-0.55*(0.55*o+0.45*hh(i*3+1)))/0.45),k=1-oc(q),ph=hh(i*5+2)*Math.PI*2,on=q>0&&q<1;';
    jzAnimator(L, 'JZ In Gust', [['ADBE Text Position 3D', [PX, PY, 0]]], q + 'on?[' + (-dir) + '*' + jzN(Dd) + '*k*(0.8+0.4*hh(i*7+3))/' + jzN(PX) + '*100,(Math.sin(ph+k*6)*0.55-0.5*(hh(i*11+4)*2-1))*SZ*k/' + jzN(PY) + '*100,0]:[0,0,0]');
    jzAnimator(L, 'JZ In Flutter', [['ADBE Text Rotation', 170]], q + 'on?Math.sin(ph*2+k*7)*k*100:0');
    jzAnimator(L, 'JZ In Leaf', [['ADBE Text Scale 3D', [70, 70, 100]]], q + 'on?k*100:0');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + 'q<=0?100:(1-cl(q*4))*100');
} });

/* ---- strokeOrder \u2014 \u4E00\u753B\u305A\u3064: every glyph is written in from its top-left to its bottom-right, glyph after glyph
   (browser: stroke pieces in reading order -> AE: a soft circular front per glyph + a small settle) */
jzReg('enter', 'strokeOrder', { selfHide: true, apply: function (m) {
    var L = m.L, G = eb2_geo(m, true), sz = m.size, H = eb2_head(m), i, first = true;
    var soft = G.masked || G.arranged || eb2_crowded(m, G) || G.N > 40;
    var q = m.HD + EB2_FNS + eb2_q(0.6);
    jzAnimator(L, 'JZ In Write Settle', [['ADBE Text Scale 3D', [soft ? 115 : 108, soft ? 115 : 108, 100]]], q + 'q<=0||q>=1?0:(1-oc(q))*100');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + (soft ? 'q<=0?100:(1-ios(q))*100' : 'q<=0?100:(1-cl(q*4))*100'));
    if (soft) return;
    var fe = sz * 0.1, n = G.N;
    for (i = 0; i < n; i++) {
        var g = G.g[i]; if (g.sp) continue;
        var tlx = g.x0, tly = g.cy - g.h * 0.55, ccx = tlx - g.w * 0.25, ccy = tly - g.h * 0.3;
        var R0 = Math.sqrt((tlx - ccx) * (tlx - ccx) + (tly - ccy) * (tly - ccy)) * 0.75;
        var bx = g.x1 - ccx, by = g.cy + g.h * 0.55 - ccy, R1 = Math.sqrt(bx * bx + by * by) + fe;
        var QE = H + 'var q=cl((P-' + jzN(0.6 * (n > 1 ? i / (n - 1) : 0)) + ')/0.4);';
        var mk = eb2_circ(L, ccx, ccy, 1, 'JZ In Write ' + (i + 1));
        try { mk.property('ADBE Mask Feather').setValue([fe, fe]); } catch (e0) {}
        jzSetExpr(mk.property('ADBE Mask Opacity'), QE + 'q>0||time>=DL+IN?100:0');
        jzSetExpr(mk.property('ADBE Mask Offset'), QE + (first ? 'time>=DL+IN?1e4:' : '') + jzN(R0) + '+' + jzN(R1 - R0) + '*ios((q-0.06)/0.86)-1');
        first = false;
    }
} });

/* ---- clockWipe \u2014 \u6642\u8A08\u56DE\u308A: every glyph is uncovered by a clock hand sweeping once around its centre */
jzReg('enter', 'clockWipe', { apply: function (m) {
    var L = m.L, G = eb2_geo(m, true), sz = m.size, seed = m.c.seed | 0, H = eb2_head(m), r = G.rect, i, n = G.N;
    var cx = r.left + r.width / 2, cy = r.top + r.height / 2;
    if (!eb2_canMatte(m) || G.arranged || eb2_crowded(m, G) || n > 40) {   // one clock sweep over the whole line
        var rw = jzEffect(L, 'ADBE Radial Wipe', 'JZ In Clock');
        jzEP(rw, 3, [cx, cy]); jzEP(rw, 4, eb2_r(seed, 0, 211) < 0.5 ? 1 : 2); jzEX(rw, 1, H + '(1-ios(P))*100');
        return;
    }
    var T = eb2_main(m, G) ? eb2_shape(m, 'JZ In Clock Hands') : null, S = eb2_shape(m, 'JZ In Clock Matte', true);
    var lw = Math.max(1.5 * m.u, sz * 0.028), acc = eb2_pick(m.ctx.sc.accent, m.ctx.sc.fg);
    for (i = 0; i < n; i++) {
        var g = G.g[i]; if (g.sp) continue;
        var R = Math.sqrt(g.w * g.w + g.h * g.h) * 0.55 + sz * 0.05, cw = eb2_r(seed, i, 211) < 0.5;
        var QE = H + 'var q=cl((P-' + jzN(0.5 * (n > 1 ? i / (n - 1) : 0)) + ')/0.5),sw=ios(q);';
        // pie = a circle of radius R/2 stroked R wide, trimmed to the swept angle (AE ellipses start at 12 o'clock, clockwise)
        var pg = jzGrp(S, 'pie ' + (i + 1));
        jzAddEllipse(pg, R, R);
        if (cw) jzAddTrimPaths(pg, QE + 'q>=1?100:sw*100', null);
        else jzAddTrimPaths(pg, null, QE + 'q>=1?0:100-sw*100');
        jzAddStroke(pg, '#FFFFFF', R);
        jzGX(pg).property('ADBE Vector Position').setValue([g.cx, g.cy]);
        jzSetExpr(jzGX(pg).property('ADBE Vector Group Opacity'), QE + 'q>0?100:0');
        if (!T) continue;
        var hg = jzGrp(T, 'hand ' + (i + 1));
        jzAddPath(hg, [[0, 0], [0, -R * 0.9]], false);
        jzAddStroke(hg, acc, lw);
        jzAddEllipse(hg, lw * 2.6, lw * 2.6);
        jzAddFill(hg, acc);
        jzGX(hg).property('ADBE Vector Position').setValue([g.cx, g.cy]);
        jzSetExpr(jzGX(hg).property('ADBE Vector Rotation'), QE + (cw ? '' : '-') + 'sw*360');
        jzSetExpr(jzGX(hg).property('ADBE Vector Group Opacity'), QE + 'q>0&&q<1?(1-sm(0.8,1,q))*cl(q*8)*100:0');
    }
    eb2_fullMatte(m, S, cx, cy);
    eb2_matte(m, S);
} });

/* ---- shadowFirst \u2014 \u5F71\u843D\u3068\u3057: each glyph's shadow lands first; the glyph drops from above the lens straight onto it */
jzReg('enter', 'shadowFirst', { selfHide: true, apply: function (m) {
    var L = m.L, sz = m.size, sc = m.ctx.sc, bg = eb2_pick(sc.bg, '#000000');
    var sh = jzMixHex(bg, jzLum(bg) < 0.5 ? eb2_pick(sc.fg) : '#000000', 0.28);
    var D = eb2_crowded(m) ? null : eb2_twin(m, 'JZ In Shadow', true);
    var q = m.HD + EB2_FNS + eb2_q(0.45) + 'var tL=0.72,e=iq((q-0.12)/(tL-0.12)),u=cl((q-tL)/(1-tL));';
    jzAnimator(L, 'JZ In Drop Scale', [['ADBE Text Scale 3D', [175, 175, 100]]], q + 'q<=0.12||q>=1?0:(q<tL?(1-e)*100:-0.07*Math.sin(Math.PI*u)*(1-u)/0.75*100)');
    jzAnimator(L, 'JZ In Drop Move', [['ADBE Text Position 3D', [-sz * 0.22, -sz * 0.34, 0]]], q + 'q>0.12&&q<tL?(1-e)*100:0');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + 'q<=0.12?100:(1-cl((q-0.12)*4))*100');
    if (!D) return;
    var qs = q + 'var es=cl(q/tL);';
    jzAnimator(D, 'JZ Shadow Ink', [['ADBE Text Fill Color', jzHex(sh)], ['ADBE Text Stroke Color', jzHex(sh)]], '100');
    jzAnimator(D, 'JZ Shadow Grow', [['ADBE Text Scale 3D', [55, 55, 100]], ['ADBE Text Blur', [sz * 0.05, sz * 0.05]]], qs + 'q<=0?0:(1-iq(es))*100');
    jzAnimator(D, 'JZ Shadow Fade', [['ADBE Text Opacity', 0]], qs + 'q<=0||q>=tL+0.05?100:(1-cl(q*5)*0.85)*100');
} });

/* ---- bubbles \u2014 \u6CE1: glyphs float up inside soap bubbles, wobbling, and pop out full size when the bubbles burst */
jzReg('enter', 'bubbles', { selfHide: true, apply: function (m) {
    var L = m.L, G = eb2_geo(m, true), sz = m.size, seed = m.c.seed | 0, i, N = G.N, RD = [], PH = [];
    for (i = 0; i < N; i++) { RD.push(eb2_r(seed, i, 221)); PH.push(eb2_r(seed, i, 222) * Math.PI * 2); }
    var q = m.HD + 'var RD=' + eb2_arr(RD) + ',PH=' + eb2_arr(PH) + ',n=textTotal,o=n>1?(textIndex-1)/(n-1):0,' +
        'q=cl((P-0.45*(0.5*(RD[textIndex-1]||0)+0.5*o))/0.55),f=cl(q/0.68),ph=PH[textIndex-1]||0,tP=0.68;';
    jzAnimator(L, 'JZ In Bubble Float', [['ADBE Text Position 3D', [sz * 0.1, sz * 1.7, 0]]], q + 'q>0&&q<tP?[Math.sin(ph+f*9)*(1-f)*100,(1-oc(f))*100,0]:[0,0,0]');
    jzAnimator(L, 'JZ In Bubble Size', [['ADBE Text Scale 3D', [0, 0, 100]]], q + 'q<=0||q>=1?0:(q<tP?32:(1-(0.68+0.32*ob((q-tP)/(1-tP),2.6)))*100)');
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + 'q<=0?100:(1-cl(q*6))*100');
    if (!eb2_main(m, G) || G.arranged || N > 40) return;
    var S = eb2_shape(m, 'JZ In Bubbles'), lw = Math.max(1.2 * m.u, sz * 0.018), H = eb2_head(m), sc = m.ctx.sc;
    var acc = eb2_pick(sc.accent, sc.fg), hi = eb2_pick(sc.fg, '#FFFFFF');
    for (i = 0; i < N; i++) {
        var g = G.g[i]; if (g.sp) continue;
        var rr = Math.max(g.w, g.h) * 0.56, o = N > 1 ? i / (N - 1) : 0;
        var QE = H + 'var q=cl((P-0.45*(0.5*' + jzN(RD[i]) + '+0.5*' + jzN(o) + '))/0.55),f=cl(q/0.68),tP=0.68,u=(q-tP)/(1-tP),' +
            'bx=' + jzN(g.cx) + '+Math.sin(' + jzN(PH[i]) + '+f*9)*' + jzN(sz * 0.1) + '*(1-f),by=' + jzN(g.cy) + '+' + jzN(sz * 1.7) + '*(1-oc(f));';
        var bg = jzGrp(S, 'bubble ' + (i + 1)), el = jzAddEllipse(bg, rr * 2, rr * 2);
        jzSetExpr(el.property('ADBE Vector Ellipse Size'), QE + 'var k=q<tP?1:1+0.6*oc(u/0.5);[' + jzN(rr * 2) + '*k,' + jzN(rr * 2) + '*k]');   // before the stroke (keeps el valid)
        var st = jzAddStroke(bg, acc, lw);
        jzSetExpr(st.property('ADBE Vector Stroke Width'), QE + 'q<tP?' + jzN(lw) + ':' + jzN(lw) + '*Math.max(0,1-u)');
        jzSetExpr(jzGX(bg).property('ADBE Vector Position'), QE + 'q<tP?[bx,by]:' + eb2_pt(g.cx, g.cy));
        jzSetExpr(jzGX(bg).property('ADBE Vector Group Opacity'), QE + 'q<=0||q>=1?0:(q<tP?cl(q*6)*85:(u<0.5?(1-u/0.5)*100:0))');
        // the highlight arc (upper left, 200..245 deg -> 80.6..93.1 % of an ellipse that starts at 12 o'clock)
        var hg = jzGrp(S, 'shine ' + (i + 1));
        jzAddEllipse(hg, rr * 1.44, rr * 1.44);
        var tp = jzAddTrimPaths(hg, null, null);
        tp.property('ADBE Vector Trim Start').setValue(80.6); tp.property('ADBE Vector Trim End').setValue(93.1);
        jzAddStroke(hg, hi, lw * 1.2);
        jzSetExpr(jzGX(hg).property('ADBE Vector Position'), QE + '[bx,by]');
        jzSetExpr(jzGX(hg).property('ADBE Vector Group Opacity'), QE + 'q>0&&q<tP?cl(q*6)*60:0');
    }
} });

/* ---- tokoroten \u2014 \u3068\u3053\u308D\u3066\u3093: the line is pushed out through a slit, glyphs squeezed thin at the slit, relaxing as they emerge */
jzReg('enter', 'tokoroten', { selfHide: true, apply: function (m) {
    var L = m.L, G = eb2_geo(m, true), r = G.rect, sz = m.size, vert = G.vert, i, U = [], HU = [];
    var U0 = vert ? r.top : r.left, U1 = vert ? r.top + r.height : r.left + r.width, slit = U0 - sz * 0.12, Lt = U1 - U0 + sz * 0.3, KX = Lt + sz;
    for (i = 0; i < G.N; i++) { U.push(vert ? G.g[i].cy : G.g[i].cx); HU.push((vert ? G.fs : G.g[i].w) / 2); }
    // squeeze factor km at the slit -> 1 at `rel` past it; dd = compressed distance travelled past the slit
    var q = m.HD + EB2_FNS + 'var U=' + eb2_arr(U) + ',HU=' + eb2_arr(HU) + ',u=U[textIndex-1]||0,hu=HU[textIndex-1]||0,sl=' + jzN(slit) + ',rel=' + jzN(sz * 2) +
        ',km=0.18,c=1-sm(0.55,1,P),off=-(1-ios(cl(P/0.9)))*' + jzN(Lt) + ',e=u+off-sl,k0=1-c*(1-km),dd=0;' +
        'if(e<=0){dd=e*k0;}else{var rr=Math.min(e,rel);dd=k0*rr+(1-k0)*rr*rr/(2*rel)+Math.max(0,e-rel);}' +
        'var k=Math.max(0.05,1-c*(1-(km+(1-km)*cl(e/rel)))),du=sl+dd-u,st=1+0.3*(1-k),hid=e+hu<0,done=off>=-0.5&&c<=0;';
    jzAnimator(L, 'JZ In Push', [['ADBE Text Position 3D', vert ? [0, KX, 0] : [KX, 0, 0]]], q + 'hid||done?0:du/' + jzN(KX) + '*100');
    jzAnimator(L, 'JZ In Squeeze', [['ADBE Text Scale 3D', vert ? [200, 0, 100] : [0, 200, 100]]], q + 'hid||done?[0,0,0]:' + (vert ? '[(st-1)*100,(1-k)*100,0]' : '[(1-k)*100,(st-1)*100,0]'));
    jzAnimator(L, 'JZ In Hide', [['ADBE Text Opacity', 0]], q + 'hid&&!done?100:0');
    var H = eb2_head(m), mg = sz * 0.3;
    if (!G.masked) {     // nothing shows before the slit
        if (vert) eb2_mask(L, r.left - mg * 4, slit, r.left + r.width + mg * 4, U1 + sz * 20, null, H + 'time>=DL+IN?1e4:0', 'JZ In Slit');
        else eb2_mask(L, slit, r.top - mg * 4, U1 + sz * 20, r.top + r.height + mg * 4, null, H + 'time>=DL+IN?1e4:0', 'JZ In Slit');
    }
    if (!eb2_prim(m, G)) return;
    var S = eb2_shape(m, 'JZ In Slit'), gr = jzGrp(S, 'slit'), t = Math.max(3 * m.u, sz * 0.07), ext = sz * 0.25;
    if (vert) jzAddRect(gr, r.width + ext * 2, t, 0, r.left + r.width / 2, slit - t / 2);
    else jzAddRect(gr, t, r.height + ext * 2, 0, slit - t / 2, r.top + r.height / 2);
    jzAddFill(gr, eb2_pick(m.ctx.sc.accent, m.ctx.sc.fg));
    jzSetExpr(jzGX(gr).property('ADBE Vector Group Opacity'), H + 'cl(P*8)*(1-sm(0.85,1,P))*100');
} });

// ================================================================ pack exit part 1 (AE port of src/11p_exit.js, pack exitHold)
// sinkMask riseOut slideOutL slideOutR flipOutX flipOutY foldOut squash trackOutWide collapse zoomThrough zoomFar spinOut
// twist waveOut blurOutStagger undraw outlineOut irisClose diagWipeOut blindsClose checkerOut
// Exits run on the exit progress PO (0 at the cut's exit start OS, 1 at the cut's end; everything is gone at PO = 1).
// Per-glyph motion = text animators with Expression Selectors (browser win(p, ord, spread) -> q). Clips = layer masks in the
// text layer's own space that stay fully open (expansion 1e4) until the exit starts; the first exit mask INTERSECTs with masks
// the layout / entrance already put on the layer. Copies (the top halves of foldOut, the outline of outlineOut, the echo trails
// of the zooms) are duplicates of the lyric layer parented to it that only show during the exit. Helper graphics (iris ring,
// CRT dot, wipe bar) are shape layers parented to the text layer (placed above it, never between it and its track matte).

// ---------------------------------------------------------------- shared helpers (ex1_*)
// easings not in JZ_FNS: io3 inOutCubic, ios inOutSine, sm smoothstep(a, b, x)
var EX1_FNS = 'function io3(x){x=cl(x);return x<0.5?4*x*x*x:1-Math.pow(-2*x+2,3)/2;}function ios(x){x=cl(x);return -(Math.cos(Math.PI*x)-1)/2;}' +
    'function sm(a,b,x){x=cl((x-a)/(b-a));return x*x*(3-2*x);}\n';
var EX1_OWNSTROKE = 'var s0=0;try{s0=text.sourceText.style.strokeWidth;}catch(err){}';   // > 0.5: a treatment gave the text its own stroke
function ex1_H(m) { return m.HD + EX1_FNS; }
// compact header for the many small mask expressions (PO exactly as in the motion header)
function ex1_mini(m) { var t = ex1_T(m); return 'var OS=' + jzN(t.OS) + ',OD=' + jzN(t.OD) + ';function cl(x){return Math.max(0,Math.min(1,x));}var PO=OD>0.002?cl((time-OS)/OD):0;'; }
function ex1_T(m) { var c = m.c, od = c.outDur || 0; return { IN: Math.max(0.02, c.inDur || 0.3), DL: (m.o.mi || 0) * (c.stagger || 0.04), OS: c.dur - od, OD: Math.max(0.001, od) }; }
function ex1_arr(a) { var s = []; for (var i = 0; i < a.length; i++) s.push(jzN(a[i])); return '[' + s.join(',') + ']'; }
function ex1_isHex(c) { return typeof c === 'string' && c.charAt(0) === '#' && (c.length === 7 || c.length === 4); }
function ex1_hex(a, b, c) { if (ex1_isHex(a)) return a; if (ex1_isHex(b)) return b; if (ex1_isHex(c)) return c; return '#FFFFFF'; }
function ex1_td(L) { try { return L.property('ADBE Text Properties').property('ADBE Text Document').value; } catch (e) { return null; } }
function ex1_text(L) { var d = ex1_td(L); return d ? String(d.text) : ''; }
function ex1_isSp(ch) { return /^[\s\u3000]$/.test(ch); }
// the browser's J.h (hash of up to 5 ints) so seeded choices (cutBit, wipe angle) match the web version
function ex1_h(a, b, c, d, e) {
    var h = 0x9e3779b9 ^ (a | 0);
    h = jzImul(h ^ (h >>> 16), 0x85ebca6b);
    h = (h + jzImul((b | 0) + 0x632be5ab, 0xc2b2ae35)) | 0;
    h = jzImul(h ^ (h >>> 13), 0xc2b2ae35);
    h = (h + jzImul((c | 0) + 0x5bd1e995, 0x27d4eb2f)) | 0;
    h = jzImul(h ^ (h >>> 15), 0x165667b1);
    h = (h + jzImul((d | 0) + 0x1b873593, 0x85ebca6b)) | 0;
    h = jzImul(h ^ (h >>> 16), 0x27d4eb2f);
    h = (h + jzImul((e | 0) + 0x68e31da4, 0x9e3779b1)) | 0;
    h ^= h >>> 15; h = jzImul(h, 0x2c1b3c6d); h ^= h >>> 12; h = jzImul(h, 0x297a2d39); h ^= h >>> 15;
    return h >>> 0;
}
function ex1_bit(m, k) { return (ex1_h(m.c.seed | 0, k, 991) & 1) === 1; }   // browser cutBit: same choice for every item of a cut
// browser orderOf: glyph order 0..1; a one-glyph layer (mixed / scatter layouts) uses its mi within the cut
function ex1_om(m) { var N = jzCount(m.c.text || ''); return N > 1 ? jzClamp((m.o.mi || 0) / (N - 1), 0, 1) : 0; }
function ex1_single(m) { return jzCount(ex1_text(m.L)) <= 1 && jzCount(m.c.text || '') > 1; }
// per-glyph window q = win(PO, ord, spread) inside an Expression Selector (rev: the last glyph leaves first)
function ex1_q(m, spread, rev) {
    return 'var n=textTotal,o=n>1?(textIndex-1)/(n-1):' + jzN(ex1_om(m)) + ';' + (rev ? 'o=1-o;' : '') + 'var q=cl((PO-' + jzN(spread) + '*o)/' + jzN(1 - spread) + ');';
}
// random order (browser J.r(seed, i, salt)): a hash of the glyph index and the layer seed, so every animator agrees
function ex1_qr(spread, salt) { return 'var o=hh(textIndex*7.31+SD*0.173+' + salt + '),q=cl((PO-' + jzN(spread) + '*o)/' + jzN(1 - spread) + ');'; }
// the text at rest (after the entrance, before the exit)
function ex1_rest(m) { var t = ex1_T(m); return Math.max(0, Math.min(t.OS - 0.01, t.DL + t.IN)); }
function ex1_rect(m) { try { return m.L.sourceRectAtTime(ex1_rest(m), false); } catch (e) { return jzRect(m.L); } }
function ex1_scale(L) { var s = jzXf(L, 'ADBE Scale').value; return Math.max(0.05, (Math.abs(s[0]) + Math.abs(s[1])) / 200); }
// animators the layout added (glyphs not at their plain places): anything but the motion system's own / colour animators
function ex1_arranged(L) {
    var TP = L.property('ADBE Text Properties'), i;
    try { if (TP.property('ADBE Text Path Options').property('ADBE Text Path').value > 0) return true; } catch (e0) {}
    try {
        var A = TP.property('ADBE Text Animators');
        for (i = 1; i <= A.numProperties; i++) { var nm = String(A.property(i).name); if (!/^JZ (In|Hold|Out)\b/.test(nm) && !/Colou?r/i.test(nm)) return true; }
    } catch (e1) {}
    return false;
}
function ex1_matted(L) { try { var t = L.trackMatteType; return !!t && t !== TrackMatteType.NO_TRACK_MATTE; } catch (e) { return false; } }
// helper layers are skipped for very long lines, one-glyph-per-layer layouts of long lyrics and crowded comps
function ex1_crowded(m, G) {
    var n = G ? G.N : jzCount(ex1_text(m.L));
    if (n > 60 || (n <= 1 && jzCount(m.c.text || '') > 14)) return true;
    try { return m.ctx.comp.numLayers > 60; } catch (e) { return false; }
}
function ex1_meas(T, L, s, just) {
    var d = L.property('ADBE Text Properties').property('ADBE Text Document').value;
    d.text = s; d.justification = just;
    T.property('ADBE Text Properties').property('ADBE Text Document').setValue(d);
    var q = T.sourceRectAtTime(0, false);
    return { l: q.left, r: q.left + q.width };
}
// Geometry of the lyric in the text layer's own space (call BEFORE adding the exit's animators).
//   G.fs, G.lead, G.rect (at rest), G.cx / G.cy box centre, G.vert (one glyph per line), G.nL, G.lines[{cy, chars}],
//   G.g[] per AE character (textIndex order, spaces included): {li, sp, x0, x1, cx, cy, w}, G.W widths, G.N, G.K widest,
//   G.arranged (layout animators / text on a path). measure = exact glyph boxes via a temporary text layer.
function ex1_geo(m, measure) {
    var L = m.L, td = ex1_td(L), i, j;
    var fs = td.fontSize, lead = (!td.autoLeading && td.leading > 0) ? td.leading : fs * 1.2;
    var lines = String(td.text).split(/\r\n|\r|\n|\u0003/), nL = lines.length, r = ex1_rect(m);
    var G = { fs: fs, lead: lead, nL: nL, rect: r, g: [], W: [], lines: [], N: 0, vert: nL > 1, arranged: ex1_arranged(L), K: 0,
        cx: r.left + r.width / 2, cy: r.top + r.height / 2 };
    var inkH = r.height - (nL - 1) * lead; if (!(inkH > fs * 0.3 && inkH < fs * 2)) inkH = fs * 0.9;
    var cy0 = r.top + inkH / 2, trk = (td.tracking || 0) / 1000 * fs;
    for (i = 0; i < nL; i++) { var cs0 = jzChars(lines[i]); if (cs0.length > 1) G.vert = false; G.lines.push({ chars: cs0, cy: cy0 + i * lead }); }
    var T = null;
    if (measure && !G.vert && !G.arranged && jzCount(td.text) <= 60) { try { T = m.ctx.comp.layers.addText('x'); } catch (e3) { T = null; } }
    var LEFT = ParagraphJustification.LEFT_JUSTIFY;
    try {
        for (i = 0; i < nL; i++) {
            var ln = G.lines[i], cs = ln.chars, n = cs.length;
            if (!n) continue;
            var F = null, F0 = null, off = 0, prev = 0;
            if (T && !G.vert) {
                F = ex1_meas(T, L, lines[i], td.justification);
                if (n > 1) { F0 = ex1_meas(T, L, lines[i], LEFT); off = F.l - F0.l; }
                prev = F.l - trk;
            } else if (!G.vert) {
                var wEst = r.width / Math.max(1, n), lx = r.left + (r.width - wEst * n) / 2;
                prev = lx - trk;
            }
            for (j = 0; j < n; j++) {
                var sp = ex1_isSp(cs[j]), x0, x1;
                if (G.vert) { x0 = r.left; x1 = r.left + r.width; }
                else if (T) {
                    x0 = prev + trk;
                    x1 = (j === n - 1) ? F.r : off + ex1_meas(T, L, cs.slice(0, j + 1).join(''), LEFT).r;
                    if (x1 < x0) x1 = x0;
                    prev = x1;
                } else { x0 = prev + trk; x1 = x0 + (r.width + trk) / Math.max(1, n) - trk; prev = x1; }
                var gi = { li: i, sp: sp, x0: x0, x1: x1, cx: (x0 + x1) / 2, cy: ln.cy, w: Math.max(1, x1 - x0) };
                if (G.vert) { gi.w = Math.min(r.width, fs); gi.cx = r.left + r.width / 2; }
                G.g.push(gi); G.W.push(gi.w); G.N++;
                if (gi.w > G.K) G.K = gi.w;
            }
        }
    } catch (e4) { jzWarn('ex1 geometry: ' + e4.toString()); }
    if (T) { try { T.remove(); } catch (e5) {} }
    if (!G.K) G.K = fs;
    return G;
}
// what the layer's existing masks leave visible once the entrance is over:
//   'none' (no masks), 'open' (an entrance mask opened to the whole layer), 'clip' (a real clip from the layout)
function ex1_mstate(L) {
    var MP = L.property('ADBE Mask Parade'), n = MP.numProperties, ok = true, i;
    if (!n) return 'none';
    for (i = n; i >= 1; i--) {
        var mk = MP.property(i), mode = mk.maskMode, ex = '';
        if (mode === MaskMode.NONE) continue;
        try { ex = String(mk.property('ADBE Mask Offset').expression || ''); } catch (e) {}
        if (mode === MaskMode.ADD && !mk.inverted && ex.indexOf('1e4') >= 0) return ok ? 'open' : 'clip';
        if (mode !== MaskMode.ADD) ok = false;
    }
    return 'clip';
}
// finish a new mask: first mask of a group INTERSECTs with the layer's existing masks; optional opacity / expansion expressions
function ex1_fin(Lx, mk, had, first, opEx, exEx, name) {
    if (first && had) { try { mk.maskMode = MaskMode.INTERSECT; } catch (e0) {} }
    try { if (name) mk.name = name; } catch (e1) {}
    if (opEx) jzSetExpr(mk.property('ADBE Mask Opacity'), opEx);
    if (exEx) jzSetExpr(mk.property('ADBE Mask Offset'), exEx);
    return mk;
}
function ex1_mask(Lx, x0, y0, x1, y1, first, opEx, exEx, name) {
    var had = Lx.property('ADBE Mask Parade').numProperties > 0;
    return ex1_fin(Lx, jzMaskRect(Lx, x0, y0, x1, y1), had, first, opEx, exEx, name);
}
function ex1_poly(Lx, pts, first, opEx, exEx, name) {
    var MP = Lx.property('ADBE Mask Parade'), had = MP.numProperties > 0, mk = MP.addProperty('ADBE Mask Atom'), sh = new Shape();
    sh.vertices = pts; sh.closed = true;
    mk.property('ADBE Mask Shape').setValue(sh);
    return ex1_fin(Lx, mk, had, first, opEx, exEx, name);
}
function ex1_circ(Lx, cx, cy, r0, first, opEx, exEx, name) {
    var MP = Lx.property('ADBE Mask Parade'), had = MP.numProperties > 0, mk = MP.addProperty('ADBE Mask Atom'), sh = new Shape(), k = r0 * 0.5523;
    sh.vertices = [[cx, cy - r0], [cx + r0, cy], [cx, cy + r0], [cx - r0, cy]];
    sh.inTangents = [[-k, 0], [0, -k], [k, 0], [0, k]]; sh.outTangents = [[k, 0], [0, k], [-k, 0], [0, -k]]; sh.closed = true;
    mk.property('ADBE Mask Shape').setValue(sh);
    return ex1_fin(Lx, mk, had, first, opEx, exEx, name);
}
// a copy of the lyric layer shown only during the exit: parented to L (follows every move of it), same space as L.
// style: take L's final text style by expression (the treatment is applied later). Made before L gets its exit animators.
function ex1_twin(m, tag, style) {
    var L = m.L, D = L.duplicate(), i, mns = ['ADBE Position', 'ADBE Scale', 'ADBE Rotate Z', 'ADBE Opacity'];
    try { D.name = String(L.name).substr(0, 20) + ' ' + tag; } catch (e0) {}
    var tr = D.property('ADBE Transform Group');
    for (i = 0; i < mns.length; i++) { try { if (tr.property(mns[i]).expression) tr.property(mns[i]).expression = ''; } catch (e1) {} }
    D.parent = L;
    tr.property('ADBE Scale').setValue([100, 100]); tr.property('ADBE Rotate Z').setValue(0);
    jzSetExpr(tr.property('ADBE Position'), 'parent.transform.anchorPoint');
    jzSetExpr(tr.property('ADBE Opacity'), ex1_H(m) + 'time<OS?0:value');
    if (style) { try { D.property('ADBE Text Properties').property('ADBE Text Document').expression = 'parent.text.sourceText.style'; } catch (e2) {} }
    return D;
}
// helper shape layer in the text layer's own space (parented to it), placed above it (and above its track matte)
function ex1_shape(m, name) {
    var L = m.L, S = jzShapeLayer(m.ctx, name, 0, 0), above = L;
    if (ex1_matted(L)) { try { if (L.index > 1) above = m.ctx.comp.layer(L.index - 1); } catch (e0) {} }
    try { S.moveBefore(above); } catch (e1) {}
    S.parent = L;
    var tr = S.property('ADBE Transform Group');
    tr.property('ADBE Anchor Point').setValue([0, 0]); tr.property('ADBE Position').setValue([0, 0]);
    tr.property('ADBE Scale').setValue([100, 100]); tr.property('ADBE Rotate Z').setValue(0);
    return S;
}
// layer space <-> comp space of an unparented layer (position / anchor / scale / rotation at rest)
function ex1_toComp(L, x, y) {
    var tr = L.property('ADBE Transform Group'), p = tr.property('ADBE Position').value, a = tr.property('ADBE Anchor Point').value;
    var s = tr.property('ADBE Scale').value, r = tr.property('ADBE Rotate Z').value * Math.PI / 180, c = Math.cos(r), sn = Math.sin(r);
    var X = (x - a[0]) * s[0] / 100, Y = (y - a[1]) * s[1] / 100;
    return [p[0] + X * c - Y * sn, p[1] + X * sn + Y * c];
}
function ex1_toLayer(L, x, y) {
    var tr = L.property('ADBE Transform Group'), p = tr.property('ADBE Position').value, a = tr.property('ADBE Anchor Point').value;
    var s = tr.property('ADBE Scale').value, r = -tr.property('ADBE Rotate Z').value * Math.PI / 180, c = Math.cos(r), sn = Math.sin(r);
    var X = x - p[0], Y = y - p[1], sx = Math.abs(s[0]) > 0.5 ? s[0] / 100 : 1, sy = Math.abs(s[1]) > 0.5 ? s[1] / 100 : 1;
    return [a[0] + (X * c - Y * sn) / sx, a[1] + (X * sn + Y * c) / sy];
}
// keep the text box centre fixed while the layer is scaled by fS (expression string): the browser scales about the box centre
function ex1_pivot(m, fS) {
    var L = m.L, tr = L.property('ADBE Transform Group'), a = tr.property('ADBE Anchor Point').value, s = tr.property('ADBE Scale').value;
    var rot = tr.property('ADBE Rotate Z').value * Math.PI / 180, r = ex1_rect(m);
    var ux = (r.left + r.width / 2 - a[0]) * s[0] / 100, uy = (r.top + r.height / 2 - a[1]) * s[1] / 100;
    var vx = Math.cos(rot) * ux - Math.sin(rot) * uy, vy = Math.sin(rot) * ux + Math.cos(rot) * uy;
    if (Math.abs(vx) + Math.abs(vy) < 0.5) return;
    m.parts.pos.push('var x1pv=1-(' + fS + ');d=[d[0]+' + jzN(vx) + '*x1pv,d[1]+' + jzN(vy) + '*x1pv];');
}
// add a stroke to the text document (width 0, animated by a Stroke Width animator) when it has none; returns true if added
function ex1_addStroke(L) {
    var td = ex1_td(L);
    if (!td || (td.applyStroke && td.strokeWidth > 0)) return false;
    jzTextDoc(L, function (d) {
        d.applyStroke = true; d.strokeWidth = 0;
        try { d.strokeColor = d.applyFill ? d.fillColor : d.strokeColor; } catch (e) {}
        try { d.strokeOverFill = false; } catch (e2) {}
    });
    return true;
}
// smoothstep for layer transform parts (the parts only get JZ_FNS): 1 - smooth(a, b, PO) as an expression
function ex1_fade(a, b, v) { return 'var ' + v + '=cl((PO-' + jzN(a) + ')/' + jzN(b - a) + ');f*=1-' + v + '*' + v + '*(3-2*' + v + ');'; }

// ================================================================ masks
/* ---- sinkMask \u2014 \u6C88\u3080: each glyph lifts a hair, then sinks below its own baseline (clipped to its line), left to right */
// glyphs leaving their line vertically are clipped to their own line band (the browser's per-glyph window). With several
// lines, alternate lines go to a linked copy D (made here, before the exit's animators) so a glyph leaving its line never
// shows over the next one. Returns null (no clip: glyphs fade instead), { D: null } (one line) or { D: copy }.
function ex1_clipPlan(m, G) {
    var L = m.L;
    if (G.arranged) return null;
    if (G.nL === 1) return { D: null };
    if (ex1_matted(L) || ex1_crowded(m, G) || ex1_mstate(L) === 'clip') return null;
    var D = ex1_twin(m, 'JZ Out Lines B', true), S = [], i;
    for (i = 0; i < G.N; i++) S.push(G.g[i].li % 2);
    var arr = ex1_arr(S);
    jzAnimator(L, 'JZ Out Set A', [['ADBE Text Opacity', 0]], ex1_H(m) + 'var S=' + arr + ';S[textIndex-1]==1&&time>=OS?100:0');
    jzAnimator(D, 'JZ Out Set B', [['ADBE Text Opacity', 0]], 'var S=' + arr + ';S[textIndex-1]==1?0:100');
    return { D: D };
}
function ex1_bands(m, G, D, name) {
    var L = m.L, fs = G.fs, r = G.rect, hb = fs * 0.66, H = ex1_H(m), x0 = r.left - fs * 2, x1 = r.left + r.width + fs * 2, i, nA = 0, nB = 0;
    for (i = 0; i < G.nL; i++) {
        if (!G.lines[i].chars.length) continue;
        var cy = G.lines[i].cy, onB = !!D && i % 2 === 1, first = onB ? !nB : !nA;
        ex1_mask(onB ? D : L, x0, cy - hb, x1, cy + hb, first, null, (!onB && first) ? H + 'time<OS?1e4:0' : null, name + ' ' + (i + 1));
        if (onB) nB++; else nA++;
    }
}

/* ---- sinkMask \u2014 \u6C88\u3080: each glyph lifts a hair, then sinks below its own baseline (clipped to its line), left to right */
jzReg('exit', 'sinkMask', { apply: function (m) {
    var L = m.L, G = ex1_geo(m, false), fs = G.fs, H = ex1_H(m), cp = ex1_clipPlan(m, G), k;
    var q = H + ex1_q(m, 0.5) + 'var d=io3(q)-0.06*Math.sin(Math.PI*cl(q/0.3));';
    var Ls = cp && cp.D ? [L, cp.D] : [L];
    for (k = 0; k < Ls.length; k++) {
        jzAnimator(Ls[k], 'JZ Out Sink', [['ADBE Text Position 3D', [0, fs * 1.4, 0]]], q + 'q<=0?0:d*100');
        jzAnimator(Ls[k], 'JZ Out Hide', [['ADBE Text Opacity', 0]], q + (cp ? 'q>=1?100:0' : 'q>=1?100:sm(0.3,0.75,d)*100'));
    }
    if (cp) ex1_bands(m, G, cp.D, 'JZ Out Sink');
} });

/* ---- riseOut \u2014 \u4E0A\u3078\u629C\u3051\u308B: glyphs shoot up out of their line in random order, stretching with speed (clipped at the line top) */
jzReg('exit', 'riseOut', { apply: function (m) {
    var L = m.L, G = ex1_geo(m, false), fs = G.fs, H = ex1_H(m), cp = ex1_clipPlan(m, G), k;
    var q = H + ex1_qr(0.5, 201) + 'var e=Math.pow(q,2.2),st=Math.sin(Math.PI*q)*q;';
    var Ls = cp && cp.D ? [L, cp.D] : [L];
    for (k = 0; k < Ls.length; k++) {
        jzAnimator(Ls[k], 'JZ Out Rise', [['ADBE Text Position 3D', [0, -fs * 1.5, 0]]], q + 'q<=0?0:e*100');
        jzAnimator(Ls[k], 'JZ Out Stretch', [['ADBE Text Scale 3D', [90, 150, 100]]], q + 'q<=0?[0,0,0]:[st*50,st*100,0]');
        jzAnimator(Ls[k], 'JZ Out Hide', [['ADBE Text Opacity', 0]], q + (cp ? 'q>=1?100:0' : 'q>=1?100:sm(0.2,0.55,e)*100'));
    }
    if (cp) ex1_bands(m, G, cp.D, 'JZ Out Rise');
} });

// ================================================================ slides
/* ---- slideOutL / slideOutR \u2014 \u5DE6\u3078 / \u53F3\u3078\u6D41\u308C\u308B: a small wind-up, then glyphs accelerate off-screen, stretching and leaning,
   the leading edge first */
function ex1_slide(m, dir) {
    var L = m.L, r = ex1_rect(m), fs = m.size, ks = Math.max(0.05, Math.abs(jzXf(L, 'ADBE Scale').value[0]) / 100);
    var dist = m.W / ks + r.width + fs * 1.5;
    var q = ex1_H(m) + ex1_q(m, 0.5, dir > 0) + 'var wind=q<0.24?Math.sin(q/0.24*Math.PI)*' + jzN(fs * 0.07) + ':0,u=cl((q-0.1)/0.9),v=3*u*u;';
    jzAnimator(L, 'JZ Out Slide', [['ADBE Text Position 3D', [dir * dist, 0, 0]]], q + 'q<=0?0:(' + jzN(dist) + '*u*u*u-wind)/' + jzN(dist) + '*100');
    jzAnimator(L, 'JZ Out Stretch', [['ADBE Text Scale 3D', [230, 72, 100]]], q + 'q<=0?[0,0,0]:[Math.min(1.3,v*0.45)/1.3*100,Math.min(0.28,v*0.09)/0.28*100,0]');
    jzAnimator(L, 'JZ Out Lean', [['ADBE Text Skew', -dir * 22]], q + 'q<=0?0:Math.min(22,v*8)/22*100');
    jzAnimator(L, 'JZ Out Fade', [['ADBE Text Opacity', 0]], q + 'q>=1?100:sm(0.82,1,q)*100');
}
jzReg('exit', 'slideOutL', { apply: function (m) { ex1_slide(m, -1); } });
jzReg('exit', 'slideOutR', { apply: function (m) { ex1_slide(m, 1); } });

// ================================================================ flips / folds
/* ---- flipOutX \u2014 \u6249\u304C\u9589\u307E\u308B: each glyph swings shut like a door on its hinge side, darkening towards the background */
jzReg('exit', 'flipOutX', { apply: function (m) {
    var L = m.L, G = ex1_geo(m, true), K = G.K, dir = ex1_bit(m, 1) ? 1 : -1, bg = ex1_hex(m.ctx.sc.bg, '#000000');
    var q = ex1_H(m) + ex1_q(m, 0.5) + 'var W=' + ex1_arr(G.W) + ',w=W[textIndex-1]||SZ,th=iq(q)*Math.PI/2,c=Math.cos(th),s=Math.sin(th);';
    jzAnimator(L, 'JZ Out Door', [['ADBE Text Scale 3D', [0, 114, 100]]], q + 'q<=0?[0,0,0]:[(1-c)*100,s*100,0]');
    jzAnimator(L, 'JZ Out Hinge', [['ADBE Text Position 3D', [dir * K, 0, 0]]], q + 'q<=0?0:(1-c)*w/2/' + jzN(K) + '*100');
    jzAnimator(L, 'JZ Out Shade', [['ADBE Text Fill Color', jzHex(bg)]], q + 'q<=0?0:s*55');
    jzAnimator(L, 'JZ Out Fade', [['ADBE Text Opacity', 0]], q + 'q>=1?100:sm(0.8,1,q)*100');
} });

/* ---- flipOutY \u2014 \u30D1\u30BF\u30F3\u5012\u308C: glyphs fall flat backwards onto their baseline in random order */
jzReg('exit', 'flipOutY', { apply: function (m) {
    var L = m.L, fs = m.size, bg = ex1_hex(m.ctx.sc.bg, '#000000');
    var q = ex1_H(m) + ex1_qr(0.5, 211) + 'var th=iq(q)*Math.PI/2,c=Math.cos(th),s=Math.sin(th);';
    jzAnimator(L, 'JZ Out Fall', [['ADBE Text Scale 3D', [88, 0, 100]]], q + 'q<=0?[0,0,0]:[s*100,(1-c)*100,0]');
    jzAnimator(L, 'JZ Out Hinge', [['ADBE Text Position 3D', [0, fs * 0.5, 0]]], q + 'q<=0?0:(1-c)*100');
    jzAnimator(L, 'JZ Out Shade', [['ADBE Text Fill Color', jzHex(bg)]], q + 'q<=0?0:s*60');
    jzAnimator(L, 'JZ Out Hide', [['ADBE Text Opacity', 0]], q + 'q>=1?100:0');
} });

/* ---- foldOut \u2014 \u6298\u308A\u7573\u307F: the top half of every glyph folds down over the bottom half (showing an accent back), then the
   folded glyph flattens away. L keeps the bottom halves; two linked copies show the top halves upright / folded over. */
jzReg('exit', 'foldOut', { apply: function (m) {
    var L = m.L, G = ex1_geo(m, false), fs = G.fs, H = ex1_H(m), i, sc = m.ctx.sc;
    var back = jzMixHex(jzTextColor(L), ex1_hex(sc.accent, sc.fg), 0.75);
    // t = top-half scale (1 -> -1 folded down, then -> 0 with the bottom half), k = bottom-half scale
    var ev = H + ex1_q(m, 0.3) + 'var b=cl((q-0.5)/0.5),k=Math.cos(iq(b)*Math.PI/2),t=Math.cos(ios(q/0.5)*Math.PI)*k,fa=q>=1?100:sm(0.85,1,q)*100;';
    var st = ex1_mstate(L);
    var twins = !G.arranged && !ex1_matted(L) && !ex1_crowded(m, G) && (G.nL === 1 || st !== 'clip');
    if (!twins) {        // one layer: the glyph folds down onto its lower half (no split), then flattens
        jzAnimator(L, 'JZ Out Fold', [['ADBE Text Scale 3D', [100, 0, 100]], ['ADBE Text Position 3D', [0, fs * 0.5, 0]]],
            ev + 'var sy=q<0.5?1-0.5*ios(q/0.5):0.5*k;q<=0?[0,0,0]:[0,(1-sy)*100,0]');
        jzAnimator(L, 'JZ Out Back', [['ADBE Text Fill Color', jzHex(back)]], ev + 'q>0.25&&q<1?100:0');
        jzAnimator(L, 'JZ Out Fade', [['ADBE Text Opacity', 0]], ev + 'fa');
        return;
    }
    var D1 = ex1_twin(m, 'JZ Out Fold Top', true), D2 = ex1_twin(m, 'JZ Out Fold Back', true);
    jzAnimator(L, 'JZ Out Fold', [['ADBE Text Scale 3D', [100, 0, 100]]], ev + 'q<=0?[0,0,0]:[0,(1-k)*100,0]');
    jzAnimator(L, 'JZ Out Fade', [['ADBE Text Opacity', 0]], ev + 'fa');
    jzAnimator(D1, 'JZ Out Fold', [['ADBE Text Scale 3D', [100, -100, 100]]], ev + 'q<=0?[0,0,0]:[0,(1-t)/2*100,0]');
    jzAnimator(D1, 'JZ Out Fade', [['ADBE Text Opacity', 0]], ev + 'q>0&&t<0?100:fa');
    jzAnimator(D2, 'JZ Out Fold', [['ADBE Text Scale 3D', [100, -100, 100]]], ev + 'q<=0?[0,0,0]:[0,(1-t)/2*100,0]');
    jzAnimator(D2, 'JZ Out Back', [['ADBE Text Fill Color', jzHex(back)]], '100');
    jzAnimator(D2, 'JZ Out Fade', [['ADBE Text Opacity', 0]], ev + 'q<=0||t>=0?100:fa');
    // halves: one band per line (vertical text: per glyph); L's first band stays open until the exit starts
    var r = G.rect, x0 = r.left - fs, x1 = r.left + r.width + fs, hb = Math.max(fs * 0.45, Math.min(fs * 0.66, G.lead - fs * 0.5)), sn = fs * 0.004, n = 0;
    for (i = 0; i < G.nL; i++) {
        if (!G.lines[i].chars.length) continue;
        var cy = G.lines[i].cy;
        ex1_mask(L, x0, cy, x1, cy + hb, !n, null, !n ? H + 'time<OS?1e4:0' : null, 'JZ Out Bottom ' + (i + 1));
        ex1_mask(D1, x0, cy - hb, x1, cy + sn, !n, null, null, 'JZ Out Top ' + (i + 1));
        ex1_mask(D2, x0, cy - sn, x1, cy + hb, !n, null, null, 'JZ Out Flap ' + (i + 1));
        n++;
    }
} });

/* ---- squash \u2014 \u6F70\u308C\u308B: CRT switch-off \u2014 the line flattens to a hairline, then pinches to a bright dot that fades */
jzReg('exit', 'squash', { apply: function (m) {
    var L = m.L, r = ex1_rect(m), fs = m.size, H = ex1_H(m), N = jzCount(m.c.text || ''), single = ex1_single(m);
    var cx = r.left + r.width / 2, cy = r.top + r.height / 2;
    var ev = H + 'var a1=cl(PO/0.42),a2=cl((PO-0.4)/0.45),ky=1-0.97*ic(a1),kx=' + (single ? '1+0.08*oc(a1)' : '(1+0.08*oc(a1))*(1-io3(a2))') + ';';
    // scaled about the text box centre by a Transform effect (the dot layer parented to L is not squashed with it)
    var tf = jzEffect(L, 'ADBE Geometry2', 'JZ Out Squash');
    jzEP(tf, 1, [cx, cy]); jzEP(tf, 2, [cx, cy]); jzEP(tf, 3, 0);
    jzEX(tf, 4, ev + 'ky*100'); jzEX(tf, 5, ev + 'kx*100');
    if (single) {        // one glyph per layer: the whole line collapses to the screen centre
        m.parts.pos.push('var x1a=cl((PO-0.4)/0.45),x1b=x1a<0.5?4*x1a*x1a*x1a:1-Math.pow(-2*x1a+2,3)/2;d=[d[0]+(thisComp.width/2-value[0])*x1b,d[1]];');
        m.parts.op.push(ex1_fade(0.7, 0.9, 'x1f'));
    } else m.parts.op.push('var x1a=cl((PO-0.4)/0.45),x1b=x1a<0.5?4*x1a*x1a*x1a:1-Math.pow(-2*x1a+2,3)/2;f*=(1+0.08*oc(cl(PO/0.42)))*(1-x1b)>0.01?1:0;');
    // the bright dot + hairline left behind (once per cut for one-glyph layers: by the middle one)
    if (single && Math.round(m.o.mi || 0) !== Math.floor((N - 1) / 2)) return;
    if (ex1_crowded(m)) return;
    var col = jzTextColor(L), S, rr, px = cx, py = cy;
    if (single) {
        var c0 = ex1_toComp(L, cx, cy), ks = ex1_scale(L);
        S = jzShapeLayer(m.ctx, 'JZ Out Squash Dot', m.W / 2, c0[1]);
        jzNoGhost(S);
        try { S.moveBefore(ex1_matted(L) && L.index > 1 ? m.ctx.comp.layer(L.index - 1) : L); } catch (e0) {}
        rr = fs * ks * 0.08; px = 0; py = 0;
    } else { S = jzNoGhost(ex1_shape(m, 'JZ Out Squash Dot')); rr = fs * 0.08; }
    var dA = ev + 'var dA=sm(0.6,0.8,PO)*(1-sm(0.88,1,PO));';
    // (each shape item is fully set up before the next sibling is added: adding one invalidates references to the others in AE)
    var gl = jzGrp(S, 'line'), rc = jzAddRect(gl, rr * 10, rr * 0.24, 0);
    jzSetExpr(rc.property('ADBE Vector Rect Size'), dA + '[' + jzN(rr * 10) + '*(1-a2*0.5),' + jzN(rr * 0.24) + ']');
    jzAddFill(gl, col);
    jzGX(gl).property('ADBE Vector Position').setValue([px, py]);
    jzSetExpr(jzGX(gl).property('ADBE Vector Group Opacity'), dA + 'dA*80');
    var gd = jzGrp(S, 'dot');
    jzAddEllipse(gd, rr * 2, rr * 2);
    jzAddFill(gd, col);
    jzGX(gd).property('ADBE Vector Position').setValue([px, py]);
    jzSetExpr(jzGX(gd).property('ADBE Vector Group Opacity'), dA + 'dA*100');
} });

/* ---- trackOutWide \u2014 \u5B57\u9593\u304C\u958B\u304F: the letter spacing opens up while glyphs thin, blur and fade */
jzReg('exit', 'trackOutWide', { apply: function (m) {
    var L = m.L, G = ex1_geo(m, true), fs = G.fs, vert = G.vert, i, A = [], K = 1;
    for (i = 0; i < G.N; i++) { var v = vert ? G.g[i].cy - G.cy : G.g[i].cx - G.cx; A.push(v); K = Math.max(K, Math.abs(v) * 1.95 + 1); }
    var ev = ex1_H(m) + 'var k=iq(PO)*1.6+PO*0.3;';
    if (G.N > 1) jzAnimator(L, 'JZ Out Track', [['ADBE Text Position 3D', vert ? [0, K, 0] : [K, 0, 0]]], ev + 'var A=' + ex1_arr(A) + ',v=(A[textIndex-1]||0)*k/' + jzN(K) + '*100;' + (vert ? '[0,v,0]' : '[v,0,0]'));
    jzAnimator(L, 'JZ Out Thin', [['ADBE Text Scale 3D', vert ? [100, 40, 100] : [40, 100, 100]]], ev + 'iq(PO)*100');
    var B = fs * 0.035, cap = jzU(m.ctx) * 0.012 / ex1_scale(L);
    jzAnimator(L, 'JZ Out Blur', [['ADBE Text Blur', [B, B]]], ev + 'Math.min(iq(PO),' + jzN(cap / B) + ')*100');
    m.parts.op.push(ex1_fade(0.2, 1, 'x1t'));
    if (ex1_single(m)) m.parts.pos.push('var x1k=PO*PO*1.6+PO*0.3;d=[d[0]+(value[0]-thisComp.width/2)*x1k*0.7,d[1]+(value[1]-thisComp.height/2)*x1k*0.4];');
} });

/* ---- collapse \u2014 \u5438\u3044\u8FBC\u307F: the line is sucked into its centre along a spiral (inner glyphs turn faster) */
jzReg('exit', 'collapse', { apply: function (m) {
    var L = m.L, dir = ex1_bit(m, 2) ? 1 : -1;
    m.parts.op.push(ex1_fade(0.8, 1, 'x1c'));
    if (ex1_single(m)) {         // spiral the whole one-glyph layer into the screen centre
        var p0 = jzXf(L, 'ADBE Position').value, px = p0[0] - m.W / 2, py = p0[1] - m.H / 2, R = Math.max(m.W, m.H) * 0.5;
        var ph = dir * (140 + 220 * (1 - Math.min(1, Math.sqrt(px * px + py * py) / R)));
        var PH = 'var x1e=iq(PO),x1p=' + jzN(ph) + '*x1e*Math.PI/180,x1r=1-x1e;';
        var X0 = '(' + jzN(px) + ')', Y0 = '(' + jzN(py) + ')';
        m.parts.pos.push(PH + 'd=[d[0]+(' + X0 + '*Math.cos(x1p)-' + Y0 + '*Math.sin(x1p))*x1r-' + X0 + ',d[1]+(' + X0 + '*Math.sin(x1p)+' + Y0 + '*Math.cos(x1p))*x1r-' + Y0 + '];');
        m.parts.rot.push(PH + 'r+=x1p*180/Math.PI;');
        m.parts.sc.push('var x1s=Math.max(0.02,1-iq(PO)*0.94);f=[f[0]*x1s,f[1]*x1s];');
        return;
    }
    var G = ex1_geo(m, true), r = G.rect, X = [], Y = [], i;
    var Rr = Math.max(1, Math.sqrt(r.width * r.width + r.height * r.height) * 0.5), K = Rr * 2 + 1;
    for (i = 0; i < G.N; i++) { X.push(G.g[i].cx - G.cx); Y.push(G.g[i].cy - G.cy); }
    var ev = ex1_H(m) + 'var X=' + ex1_arr(X) + ',Y=' + ex1_arr(Y) + ',px=X[textIndex-1]||0,py=Y[textIndex-1]||0,e=iq(PO),' +
        'ph=' + dir + '*e*(140+220*(1-cl(Math.sqrt(px*px+py*py)/' + jzN(Rr) + ')))*Math.PI/180,c=Math.cos(ph),s=Math.sin(ph),r=1-e;';
    jzAnimator(L, 'JZ Out Spiral', [['ADBE Text Position 3D', [K, K, 0]]], ev + '[((px*c-py*s)*r-px)/' + jzN(K) + '*100,((px*s+py*c)*r-py)/' + jzN(K) + '*100,0]');
    jzAnimator(L, 'JZ Out Turn', [['ADBE Text Rotation', 360]], ev + 'ph*180/Math.PI/360*100');
    jzAnimator(L, 'JZ Out Shrink', [['ADBE Text Scale 3D', [0, 0, 100]]], ev + '(1-Math.max(0.02,1-e*0.94))*100');
} });

// ================================================================ zooms (echo trails = linked copies behind the text)
// n copies of L behind it, parented to it, scaled k^i about the box centre; opEx: opacity factor of copy i (uses I)
function ex1_echo(m, n, k, opEx, outline, dyEx) {
    var L = m.L, r = ex1_rect(m), bx = r.left + r.width / 2, by = r.top + r.height / 2, H = ex1_H(m), prev = L, i, j;
    var col = jzTextColor(L), mns = ['ADBE Position', 'ADBE Scale', 'ADBE Rotate Z', 'ADBE Opacity', 'ADBE Anchor Point'];
    for (i = 1; i <= n; i++) {
        var D = L.duplicate(), tr = D.property('ADBE Transform Group');
        try { D.name = String(L.name).substr(0, 20) + ' JZ Out Echo ' + i; } catch (e0) {}
        for (j = 0; j < mns.length; j++) { try { if (tr.property(mns[j]).expression) tr.property(mns[j]).expression = ''; } catch (e1) {} }
        D.parent = L;
        tr.property('ADBE Anchor Point').setValue([bx, by]); tr.property('ADBE Position').setValue([bx, by]);
        var s = Math.pow(k, i) * 100;
        tr.property('ADBE Scale').setValue([s, s]); tr.property('ADBE Rotate Z').setValue(0);
        if (dyEx) jzSetExpr(tr.property('ADBE Position'), H + 'var I=' + i + ';[' + jzN(bx) + ',' + jzN(by) + '+(' + dyEx + ')]');
        jzSetExpr(tr.property('ADBE Opacity'), H + 'var I=' + i + ';time<OS?0:value*(' + opEx + ')');
        if (outline) {
            jzTextDoc(D, function (d) {
                d.applyFill = false; d.applyStroke = true; d.strokeColor = jzHex(col); d.strokeWidth = Math.max(1, m.size * 0.012);
            });
        } else { try { D.property('ADBE Text Properties').property('ADBE Text Document').expression = 'parent.text.sourceText.style'; } catch (e2) {} }
        try { D.moveAfter(prev); } catch (e3) {}
        prev = D;
    }
}

/* ---- zoomThrough \u2014 \u624B\u524D\u3078\u629C\u3051\u308B: the line rushes towards the camera (grows past the frame) leaving smaller echoes, and fades */
jzReg('exit', 'zoomThrough', { apply: function (m) {
    var L = m.L, single = ex1_single(m), szc = m.size * ex1_scale(L);
    var maxK = Math.max(1.3, Math.max(m.W, m.H) * (single ? 0.8 : 1.6) / Math.max(1, szc));
    var kE = 'Math.min(' + jzN(maxK) + ',1+4.5*ic(PO)+0.25*PO)';
    var echo = !ex1_matted(L) && !ex1_crowded(m);
    if (echo) ex1_echo(m, 3, 0.84, '0.45*sm(0,0.25,PO)*(1-sm(0.3,0.88,PO))*Math.pow(0.62,I-1)', false, null);
    m.parts.sc.push('var x1z=' + kE + ';f=[f[0]*x1z,f[1]*x1z];');
    ex1_pivot(m, kE);
    if (single) m.parts.pos.push('var x1w=4.5*ic(PO)+0.25*PO;d=[d[0]+(value[0]-thisComp.width/2)*x1w,d[1]+(value[1]-thisComp.height/2)*x1w];');
    m.parts.op.push(ex1_fade(0.3, 0.88, 'x1o'));
} });

/* ---- zoomFar \u2014 \u5965\u3078\u9060\u3056\u304B\u308B: the line recedes fast towards a vanishing point above, trailing larger outline echoes */
jzReg('exit', 'zoomFar', { apply: function (m) {
    var L = m.L, single = ex1_single(m), szc = m.size * ex1_scale(L), E = '(1-Math.pow(1-PO,2.6))';
    if (!ex1_matted(L) && !ex1_crowded(m)) ex1_echo(m, 3, 1.2, '0.6*sm(0,0.2,PO)*(1-sm(0.5,1,PO))*Math.pow(0.62,I-1)', true, jzN(m.size * 0.06) + '*' + E + '*I');
    m.parts.sc.push('var x1q=1-0.9*' + E + ';f=[f[0]*x1q,f[1]*x1q];');
    ex1_pivot(m, '1-0.9*' + E);
    m.parts.pos.push('d=[d[0],d[1]-' + jzN(szc * 0.35) + '*' + E + '];');
    if (single) m.parts.pos.push('var x1g=' + E + '*0.85;d=[d[0]+(thisComp.width/2-value[0])*x1g,d[1]+(thisComp.height/2-value[1])*x1g];');
    m.parts.op.push(ex1_fade(0.5, 1, 'x1o'));
} });

// ================================================================ per-glyph motion
/* ---- spinOut \u2014 \u56DE\u3063\u3066\u6D88\u3048\u308B: glyphs spin away, shrinking, with a little hop, left to right */
jzReg('exit', 'spinOut', { apply: function (m) {
    var L = m.L, fs = m.size, dir = ex1_bit(m, 3) ? 1 : -1;
    var q = ex1_H(m) + ex1_q(m, 0.45);
    jzAnimator(L, 'JZ Out Spin', [['ADBE Text Rotation', dir * 250]], q + 'q<=0?0:((textIndex-1)%2?1:0.8)*iq(q)*100');
    jzAnimator(L, 'JZ Out Shrink', [['ADBE Text Scale 3D', [0, 0, 100]]], q + 'q<=0?0:(1-Math.max(0.01,1-ic(q)))*100');
    jzAnimator(L, 'JZ Out Hop', [['ADBE Text Position 3D', [0, -fs * 0.18, 0]]], q + 'q<=0?0:Math.sin(Math.PI*q)*100');
    jzAnimator(L, 'JZ Out Fade', [['ADBE Text Opacity', 0]], q + 'q>=1?100:sm(0.8,1,q)*100');
} });

/* ---- twist \u2014 \u306D\u3058\u308C: the line twists like a ribbon (a phase gradient along it), showing its darker back, then goes edge-on */
jzReg('exit', 'twist', { apply: function (m) {
    var L = m.L, G = ex1_geo(m, false), vert = G.vert, sc = m.ctx.sc;
    var back = jzMixHex(jzTextColor(L), ex1_hex(sc.bg, '#000000'), 0.5);
    var ev = ex1_H(m) + 'var n=textTotal,o=n>1?(textIndex-1)/(n-1):' + jzN(ex1_om(m)) + ',fl=1-iq((PO-0.55)/0.45),th=Math.pow(PO,1.3)*(0.5+2*o)*Math.PI,c=Math.cos(th)*fl;';
    jzAnimator(L, 'JZ Out Twist', [['ADBE Text Scale 3D', vert ? [-100, 100, 100] : [100, -100, 100]]], ev + (vert ? '[(1-c)/2*100,0,0]' : '[0,(1-c)/2*100,0]'));
    if (!vert) jzAnimator(L, 'JZ Out Lean', [['ADBE Text Skew', 12]], ev + 'Math.sin(th)*fl*100');
    jzAnimator(L, 'JZ Out Back', [['ADBE Text Fill Color', jzHex(back)]], ev + 'c<0?100:0');
    m.parts.op.push(ex1_fade(0.9, 1, 'x1t'));
} });

/* ---- waveOut \u2014 \u6CE2\u3067\u5D29\u308C\u308B: a wave rolls through the line; each glyph bobs up, tumbles forward and drops away */
jzReg('exit', 'waveOut', { apply: function (m) {
    var L = m.L, A = m.size * 0.42, K = A * 2.7;
    var q = ex1_H(m) + ex1_q(m, 0.55);
    jzAnimator(L, 'JZ Out Wave', [['ADBE Text Position 3D', [K, K, 0]]], q + 'q<=0?[0,0,0]:[q*0.6*' + jzN(A / K) + '*100,(q*q*2.6-Math.sin(q*Math.PI))*' + jzN(A / K) + '*100,0]');
    jzAnimator(L, 'JZ Out Tumble', [['ADBE Text Rotation', 60]], q + 'q<=0?0:(Math.sin(q*Math.PI*1.5)*28+q*30)/60*100');
    jzAnimator(L, 'JZ Out Fade', [['ADBE Text Opacity', 0]], q + 'q>=1?100:sm(0.45,1,q)*100');
} });

/* ---- blurOutStagger \u2014 \u5B57\u3054\u3068\u30DC\u30B1: glyph by glyph, each drifts up a little, swells, blurs and fades */
jzReg('exit', 'blurOutStagger', { apply: function (m) {
    var L = m.L, fs = m.size, B = Math.max(0.5, Math.min(fs * 0.09, jzU(m.ctx) * 0.02 / ex1_scale(L)));
    var q = ex1_H(m) + ex1_q(m, 0.55) + 'var e=ios(q);';
    jzAnimator(L, 'JZ Out Blur', [['ADBE Text Blur', [B, B]], ['ADBE Text Scale 3D', [122, 122, 100]], ['ADBE Text Position 3D', [0, -fs * 0.12, 0]]], q + 'q<=0?0:e*100');
    jzAnimator(L, 'JZ Out Fade', [['ADBE Text Opacity', 0]], q + 'q*100');
} });

// ================================================================ line work
/* ---- undraw \u2014 \u7DDA\u306B\u623B\u308B: the fill empties out leaving the outline, then the outline is erased stroke by stroke (fine
   diagonal blinds closing = the browser's shrinking dashes) */
jzReg('exit', 'undraw', { apply: function (m) {
    var L = m.L, fs = m.size, ev = ex1_H(m) + 'var a=cl(PO/0.3),b=cl((PO-0.24)/0.76);';
    if (ex1_addStroke(L)) jzAnimator(L, 'JZ Out Line', [['ADBE Text Stroke Width', Math.max(0.6, fs * 0.022)]], ev + EX1_OWNSTROKE + 's0>0.5?0:cl(PO/0.06)*100');
    jzAnimator(L, 'JZ Out Fill', [['ADBE Text Fill Opacity', 0]], ev + 'ios(a)*100');
    var vb = jzEffect(L, 'ADBE Venetian Blinds', 'JZ Out Undraw');
    jzEP(vb, 2, 35); jzEP(vb, 3, Math.max(3, fs * 0.2)); jzEP(vb, 4, 0);
    jzEX(vb, 1, ev + 'ios(b)*100');
    m.parts.op.push(ex1_fade(0.9, 1, 'x1u'));
} });

/* ---- outlineOut \u2014 \u5857\u308A\u304C\u629C\u3051\u308B: the fill drains away (level falling from the top) while an accent outline copy of each glyph
   appears, then swells and fades, left to right */
jzReg('exit', 'outlineOut', { apply: function (m) {
    var L = m.L, G = ex1_geo(m, false), fs = G.fs, H = ex1_H(m), sc = m.ctx.sc, acc = ex1_hex(sc.accent, sc.fg);
    var D = (!ex1_matted(L) && !ex1_crowded(m, G)) ? ex1_twin(m, 'JZ Out Outline', false) : null;
    var ev = H + ex1_q(m, 0.35) + 'var k=ios(cl(q/0.7)),u=cl((q-0.68)/0.32);';
    // the fill drains line by line: one level per line (timed like the line's middle glyph) falling from above the glyphs to
    // below them; with several lines, alternate lines go to a linked copy so a line's drain box never shows the next line
    var cp = (ex1_td(L).applyFill !== false) ? ex1_clipPlan(m, G) : null, Ls = cp && cp.D ? [L, cp.D] : [L], i, j;
    for (i = 0; i < Ls.length; i++) jzAnimator(Ls[i], 'JZ Out Hide', [['ADBE Text Opacity', 0]], ev + 'k>=0.999?100:0');
    if (cp) {
        var r = G.rect, hh = fs * 0.72, nA = 0, nB = 0;
        for (i = 0; i < G.nL; i++) {
            var a = -1, b = -1;
            for (j = 0; j < G.N; j++) if (G.g[j].li === i) { if (a < 0) a = j; b = j; }
            if (a < 0) continue;
            var o = G.N > 1 ? (a + b) / 2 / (G.N - 1) : ex1_om(m), cy = G.lines[i].cy, onB = !!cp.D && i % 2 === 1, first = onB ? !nB : !nA;
            var lv = H + 'var qm=cl((PO-' + jzN(0.35 * o) + ')/0.65),km=ios(cl(qm/0.7));';
            ex1_mask(onB ? cp.D : L, r.left - fs * 1.6, cy - hh, r.left + r.width + fs * 1.6, cy + hh * 3, first, lv + 'km>=0.999?0:100',
                lv + (!onB && first ? 'time<OS?1e4:' : '') + '-' + jzN(hh * 2) + '*km', 'JZ Out Drain ' + (i + 1));
            if (onB) nB++; else nA++;
        }
    } else jzAnimator(L, 'JZ Out Fill', [['ADBE Text Fill Opacity', 0]], ev + 'k*100');
    if (!D) return;
    var lw = Math.max(1, fs * 0.024);
    jzTextDoc(D, function (d) {
        d.applyFill = false; d.applyStroke = true; d.strokeColor = jzHex(acc); d.strokeWidth = lw;
        try { d.strokeOverFill = true; } catch (e) {}
    });
    jzAnimator(D, 'JZ Out Swell', [['ADBE Text Scale 3D', [130, 130, 100]]], ev + 'q<=0||q>=1?0:oc(u)*100');
    jzAnimator(D, 'JZ Out Line', [['ADBE Text Opacity', 0]], ev + 'q<=0||q>=1?100:(1-Math.min(1,q*8)*(1-u))*100');
} });

// ================================================================ clip shapes
/* ---- irisClose \u2014 \u30A2\u30A4\u30EA\u30B9: a circular iris closes on the text box centre, an accent ring riding its edge */
jzReg('exit', 'irisClose', { apply: function (m) {
    var L = m.L, r = ex1_rect(m), fs = m.size, H = ex1_H(m), cx = r.left + r.width / 2, cy = r.top + r.height / 2;
    var R0 = Math.sqrt(r.width * r.width + r.height * r.height) * 0.5 + fs * 0.12;
    var ev = H + 'var rad=' + jzN(R0) + '*(1-io3(PO));';
    // a tiny circle grown by mask expansion (an exact circle in AE)
    ex1_circ(L, cx, cy, 1, true, ev + 'rad<0.5?0:100', ev + 'time<OS?1e4:Math.max(0,rad-1)', 'JZ Out Iris');
    if (ex1_crowded(m)) return;
    var lw = Math.max(1.5 * m.u, fs * 0.035), S = jzNoGhost(ex1_shape(m, 'JZ Out Iris Ring')), g = jzGrp(S, 'ring'), el = jzAddEllipse(g, 10, 10);
    jzSetExpr(el.property('ADBE Vector Ellipse Size'), ev + 'var d=(rad+' + jzN(lw / 2) + ')*2;[d,d]');     // before the stroke is added (el goes invalid then)
    jzAddStroke(g, ex1_hex(m.ctx.sc.accent, m.ctx.sc.fg), lw);
    jzGX(g).property('ADBE Vector Position').setValue([cx, cy]);
    jzSetExpr(jzGX(g).property('ADBE Vector Group Opacity'), ev + 'rad<0.5?0:Math.min(1,PO*6)*(1-sm(0.9,1,PO))*100');
} });

/* ---- diagWipeOut \u2014 \u659C\u3081\u30EF\u30A4\u30D7: a slanted edge sweeps across the text (angle from the cut seed), an accent bar riding it */
jzReg('exit', 'diagWipeOut', { apply: function (m) {
    var L = m.L, r = ex1_rect(m), fs = m.size, H = ex1_H(m), W = m.W, Hh = m.H, i;
    var v = (ex1_h(m.c.seed | 0, 77) >>> 3) & 3, ang = [22, -22, 158, 202][v];
    var rho = jzXf(L, 'ADBE Rotate Z').value, ks = ex1_scale(L), a = (ang - rho) * Math.PI / 180;
    var dx = Math.cos(a), dy = Math.sin(a), tx = -dy, ty = dx, pad = fs * 0.15;
    // u = along the wipe direction, w = along the edge (layer space)
    var cs = [[r.left - pad, r.top - pad], [r.left + r.width + pad, r.top - pad], [r.left - pad, r.top + r.height + pad], [r.left + r.width + pad, r.top + r.height + pad]];
    var u0 = 1e9, u1 = -1e9, w0 = 1e9, w1 = -1e9;
    for (i = 0; i < 4; i++) { var uu = cs[i][0] * dx + cs[i][1] * dy; if (uu < u0) u0 = uu; if (uu > u1) u1 = uu; }
    var sc = [[0, 0], [W, 0], [0, Hh], [W, Hh]];
    for (i = 0; i < 4; i++) { var p = ex1_toLayer(L, sc[i][0], sc[i][1]), ww = p[0] * tx + p[1] * ty; if (ww < w0) w0 = ww; if (ww > w1) w1 = ww; }
    var th = Math.max(2, Math.min(W, Hh) * 0.011) / ks, uA = u0 - th, span = u1 + th - uA, M = (W + Hh) * 4 / ks, wc = (w0 + w1) / 2;
    var P = function (u, w) { return [dx * u + tx * w, dy * u + ty * w]; };
    var ev = H + 'var sp=' + jzN(span) + '*io3(PO);';
    // the visible half-plane u >= edge: a big rect whose near edge starts before the text, pushed along by negative expansion
    ex1_poly(L, [P(uA, wc - M), P(uA, wc + M), P(uA + 2 * M, wc + M), P(uA + 2 * M, wc - M)], true, ev + 'PO>=0.999?0:100', ev + 'time<OS?1e4:-sp', 'JZ Out Diag');
    if (Math.abs(+m.o.mi || 0) > 1e-6 || ex1_crowded(m)) return;      // the bar is drawn once (by the first item)
    var len = (w1 - w0) + Math.max(W, Hh) * 0.2 / ks, S = jzNoGhost(ex1_shape(m, 'JZ Out Wipe Bar')), g = jzGrp(S, 'bar');
    jzAddRect(g, th, len, 0);
    jzAddFill(g, ex1_hex(m.ctx.sc.accent, m.ctx.sc.fg));
    jzGX(g).property('ADBE Vector Rotation').setValue(ang - rho);
    jzSetExpr(jzGX(g).property('ADBE Vector Position'), ev + 'var u=' + jzN(uA - th / 2) + '+sp;[' + jzN(dx) + '*u+' + jzN(tx * wc) + ',' + jzN(dy) + '*u+' + jzN(ty * wc) + ']');
    jzSetExpr(jzGX(g).property('ADBE Vector Group Opacity'), ev + 'PO>0&&PO<0.999?100:0');
} });

/* ---- blindsClose \u2014 \u30D6\u30E9\u30A4\u30F3\u30C9: the text is cut into thin blinds that close one after another (top to bottom; vertical text
   left to right) */
jzReg('exit', 'blindsClose', { apply: function (m) {
    var L = m.L, G = ex1_geo(m, false), r = G.rect, fs = G.fs, vert = G.vert, H = ex1_mini(m), j;
    var pad = fs * 0.2, x0 = r.left - pad, y0 = r.top - pad, x1 = r.left + r.width + pad, y1 = r.top + r.height + pad;
    var span = vert ? x1 - x0 : y1 - y0, cnt = jzClamp(Math.ceil(span / Math.max(4, fs * 0.26)), 2, 90), pitch = span / cnt, e = pitch / 2 + 1;
    if (ex1_mstate(L) === 'clip') {        // the layout clips this layer: Venetian Blinds instead of a union of masks
        var vb = jzEffect(L, 'ADBE Venetian Blinds', 'JZ Out Blinds');
        jzEP(vb, 2, vert ? 0 : 90); jzEP(vb, 3, pitch); jzEP(vb, 4, 0);
        jzEX(vb, 1, H + 'Math.pow(PO,0.85)*100');
        return;
    }
    for (j = 0; j < cnt; j++) {
        var o = cnt > 1 ? j / (cnt - 1) : 0, ev = H + 'var q=cl((PO-' + jzN(0.4 * o) + ')/0.6),h=' + jzN(pitch) + '*(1-Math.pow(q,0.85));';
        var ex = ev + (j ? '' : 'time<OS?1e4:') + '-(' + jzN(pitch) + '-h)/2+0.4';
        if (vert) ex1_mask(L, x0 + j * pitch, y0 - e, x0 + (j + 1) * pitch, y1 + e, !j, ev + 'h>0.05?100:0', ex, 'JZ Out Blind ' + (j + 1));
        else ex1_mask(L, x0 - e, y0 + j * pitch, x1 + e, y0 + (j + 1) * pitch, !j, ev + 'h>0.05?100:0', ex, 'JZ Out Blind ' + (j + 1));
    }
} });

/* ---- checkerOut \u2014 \u5E02\u677E: the text breaks into a checkerboard of squares that shrink away along a diagonal, one colour first */
jzReg('exit', 'checkerOut', { apply: function (m) {
    var L = m.L, G = ex1_geo(m, false), r = G.rect, fs = G.fs, H = ex1_mini(m), x, y;
    var pad = fs * 0.15, x0 = r.left - pad, y0 = r.top - pad, w = r.width + pad * 2, h = r.height + pad * 2;
    var cell = Math.max(4, fs * 0.26);
    while ((w / cell) * (h / cell) > 160) cell *= 1.25;          // (browser: 420 cells; masks are heavier)
    var nx = Math.ceil(w / cell), ny = Math.ceil(h / cell), dg = Math.max(1, nx + ny - 2), c1 = cell + 0.8;
    if (ex1_mstate(L) === 'clip') {        // the layout clips this layer: blocks dissolve instead of a union of masks
        var bd = jzEffect(L, 'ADBE Block Dissolve', 'JZ Out Checker');
        jzEP(bd, 2, cell); jzEP(bd, 3, cell); jzEP(bd, 4, 0); jzEP(bd, 5, 0);
        jzEX(bd, 1, H + 'cl(PO/0.9)*100');
        return;
    }
    for (y = 0; y < ny; y++) for (x = 0; x < nx; x++) {
        var st = ((x + y) & 1) * 0.42 + (x + y) / dg * 0.3, cx = x0 + (x + 0.5) * cell, cy = y0 + (y + 0.5) * cell;
        var ev = H + 'var t=cl((PO-' + jzN(st) + ')/0.28),k=1-t*t;';
        var ex = ev + (x || y ? '' : 'time<OS?1e4:') + '-(' + jzN(c1) + '-(' + jzN(cell) + '*k+(k>0.98?0.8:0)))/2';
        ex1_mask(L, cx - c1 / 2, cy - c1 / 2, cx + c1 / 2, cy + c1 / 2, !(x || y), ev + 'k>0.02?100:0', ex, 'JZ Out Cell ' + (y * nx + x + 1));
    }
} });

// ================================================================ pack exit 2 (AE port of src/11p_exit.js, pack exitHold)
// exits: splitApart vSliceDrop melt dissolve backspace scrambleOut glitchDissolve echoOut whipOut gravity popOut burn sweepCover shatterLite
// holds: float sway pulse shimmer colorRun rotateSlow trackBreathe skewWobble beatHop hWave heartbeat orbitSmall jelly scanBand
//        noiseDrift tilt zoomSlow stretchPulse glitchJump echoTrail
// Exits run on PO (exit progress 0..1); holds scale by AMT x motionK (browser: amt x clamp(fx.motion / 0.7, 0, 1.6)).
// Per-glyph motion = text animators with Expression Selectors. Splits = linked copies of the lyric layer (parented to it, made
// when the exit is applied, alive only during the exit) clipped by layer masks and moved by a Transform effect, so each clip
// travels with its piece (masks come before effects). Helper graphics the browser draws with ghost off (crack line, cursor,
// blocks, bursts, embers, bars, scan line) are shape layers in the text layer's own space (parented to it) marked jzNoGhost.
// Masks that must stay inactive outside the exit / hold use mask expansion 1e4 (= everything) like riseMask.

// ---------------------------------------------------------------- shared helpers (ex2_*)
// expression helpers (prefixed so they never collide with other packs' snippets in the merged transform expressions)
var EX2_FNS = 'function x2sm(a,b,x){x=cl((x-a)/(b-a));return x*x*(3-2*x);}' +
    'function x2io(x){x=cl(x);return x<0.5?4*x*x*x:1-Math.pow(-2*x+2,3)/2;}' +
    'function x2ios(x){x=cl(x);return -(Math.cos(Math.PI*x)-1)/2;}' +
    'function x2o2(x){x=cl(x);return 1-(1-x)*(1-x);}' +
    'function x2vn(x,s){var i=Math.floor(x),f=x-i,u=f*f*(3-2*f),a=hh(i*1.618+s*0.7311)*2-1,b=hh((i+1)*1.618+s*0.7311)*2-1;return a+(b-a)*u;}' +
    'function x2w(p,o,s){return cl((p-o*s)/(1-s));}\n';
// hold amount: AMT (0 during the entrance, 1 while holding, fades with the exit) x motionK
var EX2_K = 'var x2k=AMT*Math.max(0,Math.min(1.6,M/0.7));';
var EX2_POOL = '\u203B\u25C6\u25C7\u25A0\u25A1\u25B2\u25B3\u25BC\u25CF\u25CB\u25CE\uFF03\uFF0A\uFF0B\u00D7\u00F7\uFF1D\u2260\u221E\u00A7\u202001\uFF0F\uFF3C\uFF1C\uFF1E';

function ex2_hd(m) { return m.HD + EX2_FNS; }
// the browser's J.h / J.r so seeded choices match the web version
function ex2_h(a, b, c, d, e) {
    var h = 0x9e3779b9 ^ (a | 0);
    h = jzImul(h ^ (h >>> 16), 0x85ebca6b);
    h = (h + jzImul((b | 0) + 0x632be5ab, 0xc2b2ae35)) | 0;
    h = jzImul(h ^ (h >>> 13), 0xc2b2ae35);
    h = (h + jzImul((c | 0) + 0x5bd1e995, 0x27d4eb2f)) | 0;
    h = jzImul(h ^ (h >>> 15), 0x165667b1);
    h = (h + jzImul((d | 0) + 0x1b873593, 0x85ebca6b)) | 0;
    h = jzImul(h ^ (h >>> 16), 0x27d4eb2f);
    h = (h + jzImul((e | 0) + 0x68e31da4, 0x9e3779b1)) | 0;
    h ^= h >>> 15; h = jzImul(h, 0x2c1b3c6d); h ^= h >>> 12; h = jzImul(h, 0x297a2d39); h ^= h >>> 15;
    return h >>> 0;
}
function ex2_r(a, b, c, d, e) { return ex2_h(a, b, c, d, e) / 4294967296; }
function ex2_rs(a, b, c, d, e) { return ex2_r(a, b, c, d, e) * 2 - 1; }
function ex2_bit(m, k) { return (ex2_h(m.c.seed | 0, k, 991) & 1) === 1; }          // browser cutBit(env, k)
function ex2_itSeed(m) { return ex2_h(m.c.seed | 0, (m.o.mi | 0) + 1, 7) | 0; }     // browser it.seed
function ex2_arr(a) { var s = [], i; for (i = 0; i < a.length; i++) s.push(jzN(a[i])); return '[' + s.join(',') + ']'; }
function ex2_pts(a) { var s = [], i; for (i = 0; i < a.length; i++) s.push('[' + jzN(a[i][0]) + ',' + jzN(a[i][1]) + ']'); return '[' + s.join(',') + ']'; }
function ex2_col(hex) { var c = jzHex(hex); return '[' + jzN(c[0]) + ',' + jzN(c[1]) + ',' + jzN(c[2]) + ',1]'; }
function ex2_isHex(c) { return typeof c === 'string' && c.charAt(0) === '#' && (c.length === 7 || c.length === 4); }
function ex2_pick(a, b, c) { if (ex2_isHex(a)) return a; if (ex2_isHex(b)) return b; if (ex2_isHex(c)) return c; return '#FFFFFF'; }
function ex2_mix(a, b, t) {
    var A = jzHex(a), B = jzHex(b), i, o = [];
    for (i = 0; i < 3; i++) o.push(Math.round((A[i] + (B[i] - A[i]) * t) * 255));
    return jzToHex(o[0], o[1], o[2]);
}
// string literal with every non-ASCII character escaped (expression text stays ASCII)
function ex2_esc(s) {
    var o = '', i, c, h;
    for (i = 0; i < s.length; i++) {
        c = s.charCodeAt(i);
        if (c < 128 && c !== 34 && c !== 92) o += s.charAt(i);
        else { h = c.toString(16).toUpperCase(); while (h.length < 4) h = '0' + h; o += '\\u' + h; }
    }
    return '"' + o + '"';
}
function ex2_isSp(ch) { return /^[\s\u3000]$/.test(ch); }
function ex2_td(L) { try { return L.property('ADBE Text Properties').property('ADBE Text Document').value; } catch (e) { return null; } }
function ex2_sz(m) { var td = ex2_td(m.L); return (td && td.fontSize > 1) ? td.fontSize : (m.size || 100); }
function ex2_hasOut(m) { return (m.c.outDur || 0) > 0.002; }
function ex2_T(m) { var c = m.c; return { IN: Math.max(0.02, c.inDur || 0.3), DL: (m.o.mi || 0) * (c.stagger || 0.04), OS: c.dur - (c.outDur || 0) }; }
function ex2_matted(L) { try { return L.trackMatteType !== TrackMatteType.NO_TRACK_MATTE; } catch (e) { return false; } }
function ex2_nMask(L) { try { return L.property('ADBE Mask Parade').numProperties; } catch (e) { return 0; } }
// the layer new helper layers go above (above the lyric's own track matte when it has one)
function ex2_top(m) { var L = m.L; if (ex2_matted(L)) { try { return m.ctx.comp.layer(L.index - 1); } catch (e) {} } return L; }
// glyph count of this layer / of the whole cut
function ex2_nGl(m) { var td = ex2_td(m.L); return td ? jzCount(String(td.text)) : 1; }
function ex2_cutN(m) { return Math.max(1, jzCount(m.c.text || '')); }
function ex2_single(m) { return ex2_nGl(m) <= 1 && ex2_cutN(m) > 1; }
// one glyph per line (the panel's vertical text)
function ex2_isVert(L) {
    var td = ex2_td(L); if (!td) return false;
    var ls = String(td.text).split(/\r\n|\r|\n|\u0003/), i;
    if (ls.length < 2) return false;
    for (i = 0; i < ls.length; i++) if (jzChars(ls[i]).length > 1) return false;
    return true;
}
// helper layers are skipped for very long lines, for one-glyph-per-layer layouts of long lyrics (decided per cut, so every
// glyph of the cut is built the same way) and when the comp is already crowded
function ex2_crowded(m, maxCut) {
    if (ex2_nGl(m) > 60) return true;
    if (ex2_nGl(m) <= 1 && ex2_cutN(m) > (maxCut || 14)) return true;
    try { return m.ctx.comp.numLayers > 60; } catch (e) { return false; }
}
// the text box at rest (after the entrance; animators are included by sourceRectAtTime in AE)
function ex2_rect(m) {
    var t = ex2_T(m), tt = Math.min(Math.max(0, t.OS - 0.01), t.DL + t.IN + 0.01);
    try { return m.L.sourceRectAtTime(tt, false); } catch (e) { return jzRect(m.L); }
}
// layout animators that move glyphs (not this panel's own motion) / text on a path
function ex2_arranged(L) {
    var TP = L.property('ADBE Text Properties'), i, j;
    try { if (TP.property('ADBE Text Path Options').property('ADBE Text Path').value > 0) return true; } catch (e0) {}
    try {
        var A = TP.property('ADBE Text Animators');
        for (i = 1; i <= A.numProperties; i++) {
            var an = A.property(i), nm = String(an.name || '');
            if (/^JZ (In|Hold|Out)/.test(nm)) continue;
            var pr = an.property('ADBE Text Animator Properties');
            for (j = 1; j <= pr.numProperties; j++) { var mn = pr.property(j).matchName; if (mn === 'ADBE Text Position 3D' || mn === 'ADBE Text Rotation' || mn === 'ADBE Text Scale 3D') return true; }
        }
    } catch (e1) {}
    return false;
}
function ex2_meas(T, L, s, just) {
    var d = L.property('ADBE Text Properties').property('ADBE Text Document').value;
    d.text = s; d.justification = just;
    T.property('ADBE Text Properties').property('ADBE Text Document').setValue(d);
    var q = T.sourceRectAtTime(0, false);
    return { l: q.left, r: q.left + q.width };
}
// Geometry of the lyric in the text layer's own space.
//   G.fs, G.lead, G.rect, G.vert (one glyph per line), G.nL, G.lines[{cy, x0, x1, chars}],
//   G.g[] per AE character (textIndex order, spaces included): {i, li, sp, k (rank among non-spaces), x0, x1, cx, cy, w}, G.N = textTotal,
//   G.nv non-space count, G.arranged (layout animators / text on a path). measure: exact glyph boxes with a temporary text layer.
function ex2_geo(m, measure) {
    var L = m.L, TP = L.property('ADBE Text Properties'), td = TP.property('ADBE Text Document').value, i, j;
    var fs = td.fontSize, lead = (!td.autoLeading && td.leading > 0) ? td.leading : fs * 1.2;
    var lines = String(td.text).split(/\r\n|\r|\n|\u0003/), nL = lines.length, r = ex2_rect(m);
    var G = { fs: fs, lead: lead, nL: nL, rect: r, g: [], lines: [], N: 0, nv: 0, vert: nL > 1, arranged: ex2_arranged(L) };
    var inkH = r.height - (nL - 1) * lead; if (!(inkH > fs * 0.3 && inkH < fs * 2)) inkH = fs * 0.9;
    var cy0 = r.top + inkH / 2, trk = (td.tracking || 0) / 1000 * fs;
    for (i = 0; i < nL; i++) { var cs0 = jzChars(lines[i]); if (cs0.length > 1) G.vert = false; G.lines.push({ chars: cs0, cy: cy0 + i * lead, x0: r.left, x1: r.left + r.width }); }
    var T = null;
    if (measure && !G.arranged && jzCount(td.text) <= 60) { try { T = m.ctx.comp.layers.addText('x'); } catch (e3) { T = null; } }
    var LEFT = ParagraphJustification.LEFT_JUSTIFY;
    try {
        for (i = 0; i < nL; i++) {
            var ln = G.lines[i], cs = ln.chars, n = cs.length;
            if (!n) continue;
            var F = null, F0 = null, off = 0, prev = 0;
            if (T && !G.vert) {
                F = ex2_meas(T, L, lines[i], td.justification); ln.x0 = F.l; ln.x1 = F.r;
                if (n > 1) { F0 = ex2_meas(T, L, lines[i], LEFT); off = F.l - F0.l; }
                prev = F.l - trk;
            } else if (!G.vert) {
                var wEst = r.width / Math.max(1, n), lx = r.left + (r.width - wEst * n) / 2;
                ln.x0 = lx; ln.x1 = lx + wEst * n; prev = lx - trk;
            }
            for (j = 0; j < n; j++) {
                var sp = ex2_isSp(cs[j]), x0, x1;
                if (G.vert) { x0 = r.left; x1 = r.left + r.width; }
                else if (T) {
                    x0 = prev + trk;
                    x1 = (j === n - 1) ? F.r : off + ex2_meas(T, L, cs.slice(0, j + 1).join(''), LEFT).r;
                    if (x1 < x0) x1 = x0;
                    prev = x1;
                } else { x0 = prev + trk; x1 = x0 + (r.width + trk) / Math.max(1, n) - trk; prev = x1; }
                var gi = { i: G.N, li: i, sp: sp, k: sp ? -1 : G.nv, x0: x0, x1: x1, cx: (x0 + x1) / 2, cy: ln.cy, w: Math.max(1, x1 - x0) };
                if (G.vert) { gi.w = Math.min(r.width, fs); gi.x0 = gi.cx - gi.w / 2; gi.x1 = gi.cx + gi.w / 2; }
                if (!sp) G.nv++;
                G.g.push(gi); G.N++;
            }
        }
    } catch (e4) { jzWarn('ex2 geometry: ' + e4.toString()); }
    if (T) { try { T.remove(); } catch (e5) {} }
    return G;
}
// reading lines with glyphs: [{li, x0, x1, cy}] (vertical text = one column)
function ex2_lines(G) {
    var r = G.rect, out = [], i, j;
    if (G.vert) return [{ li: 0, x0: r.left, x1: r.left + r.width, y0: r.top, y1: r.top + r.height, cy: r.top + r.height / 2, cx: r.left + r.width / 2 }];
    for (i = 0; i < G.nL; i++) {
        var u0 = 1e9, u1 = -1e9;
        for (j = 0; j < G.N; j++) { var g = G.g[j]; if (g.li !== i || g.sp) continue; if (g.x0 < u0) u0 = g.x0; if (g.x1 > u1) u1 = g.x1; }
        if (u1 > u0) out.push({ li: i, x0: u0, x1: u1, y0: G.lines[i].cy - G.fs * 0.5, y1: G.lines[i].cy + G.fs * 0.5, cy: G.lines[i].cy, cx: (u0 + u1) / 2 });
    }
    return out;
}
// polygon mask in the layer's space; intersect = combine with masks the layer already has (enter / layout masks)
function ex2_mask(Lx, pts, intersect, name, expEx, mode) {
    var mk = Lx.property('ADBE Mask Parade').addProperty('ADBE Mask Atom'), sh = new Shape();
    sh.vertices = pts; sh.closed = true;
    mk.property('ADBE Mask Shape').setValue(sh);
    try { if (name) mk.name = name; } catch (e0) {}
    try { if (mode) mk.maskMode = mode; else if (intersect) mk.maskMode = MaskMode.INTERSECT; } catch (e1) {}
    if (expEx) jzSetExpr(mk.property('ADBE Mask Offset'), expEx);
    return mk;
}
function ex2_box(x0, y0, x1, y1) { return [[x0, y0], [x1, y0], [x1, y1], [x0, y1]]; }
// trim a helper layer to the exit (t0 = exit start)
function ex2_life(Lx, t0) { try { if (t0 > Lx.inPoint + 0.01 && t0 < Lx.outPoint - 0.02) Lx.inPoint = t0; } catch (e) {} }
// helper shape layer in the text layer's own space (parented to it); above = layer to go right above (default: lyric / its matte)
function ex2_shape(m, name, above, below, exitOnly) {
    var L = m.L, S = m.ctx.comp.layers.addShape();
    try { S.name = name; } catch (e0) {}
    try { if (below) S.moveAfter(L); else S.moveBefore(above || ex2_top(m)); } catch (e1) {}
    S.parent = L;
    var tr = S.property('ADBE Transform Group');
    tr.property('ADBE Anchor Point').setValue([0, 0]); tr.property('ADBE Position').setValue([0, 0]);
    tr.property('ADBE Scale').setValue([100, 100]); tr.property('ADBE Rotate Z').setValue(0);
    if (exitOnly) ex2_life(S, ex2_T(m).OS);
    return S;
}
function ex2_sub(g, name) { var s = jzVecs(g).addProperty('ADBE Vector Group'); if (name) s.name = name; return s; }
// linked copy of the lyric layer: parented to L (follows every move of it), same opacity as L; link = takes L's final text style
// by expression (the treatment is applied later); exitOnly = exists only during the exit
function ex2_copy(m, tag, link, exitOnly) {
    var L = m.L, D = L.duplicate(), i, mns = ['ADBE Position', 'ADBE Scale', 'ADBE Rotate Z', 'ADBE Opacity', 'ADBE Anchor Point'];
    try { D.name = String(L.name).substr(0, 20) + ' ' + tag; } catch (e0) {}
    var tr = D.property('ADBE Transform Group');
    for (i = 0; i < mns.length; i++) { try { if (tr.property(mns[i]).expression) tr.property(mns[i]).expression = ''; } catch (e1) {} }
    D.parent = L;
    tr.property('ADBE Scale').setValue([100, 100]); tr.property('ADBE Rotate Z').setValue(0);
    jzSetExpr(tr.property('ADBE Position'), 'parent.transform.anchorPoint');
    jzSetExpr(tr.property('ADBE Opacity'), 'parent.transform.opacity');
    if (link) { try { D.property('ADBE Text Properties').property('ADBE Text Document').expression = 'parent.text.sourceText.style'; } catch (e2) {} }
    if (exitOnly) ex2_life(D, ex2_T(m).OS);
    return D;
}
// Transform effect that only translates (layer space): anchor = position = 0, position expression gives the offset
function ex2_move(Lx, name, posEx) {
    var e = jzEffect(Lx, 'ADBE Geometry2', name);
    jzEP(e, 1, [0, 0]); jzEP(e, 2, [0, 0]); jzEX(e, 2, posEx);
    return e;
}
// keep the point (px, py) of L's own space fixed while the layer is scaled by [fx, fy] and rotated by rot degrees on top of its
// own transform (expression strings; pre defines what they need) -> snippet for m.parts.pos ('' when the point is the anchor)
function ex2_pivot(m, fx, fy, rot, px, py, pre) {
    var tr = m.L.property('ADBE Transform Group'), a = tr.property('ADBE Anchor Point').value, s = tr.property('ADBE Scale').value;
    var r0 = tr.property('ADBE Rotate Z').value, rr = r0 * Math.PI / 180;
    var ux = (px - a[0]) * s[0] / 100, uy = (py - a[1]) * s[1] / 100;
    if (Math.abs(ux) + Math.abs(uy) < 0.5) return '';
    var vx = Math.cos(rr) * ux - Math.sin(rr) * uy, vy = Math.sin(rr) * ux + Math.cos(rr) * uy;
    return (pre || '') + 'var x2pa=(' + jzN(r0) + '+(' + (rot || '0') + '))*Math.PI/180,x2pc=Math.cos(x2pa),x2ps=Math.sin(x2pa),x2px=(' + (fx || '1') + ')*' + jzN(ux) + ',x2py=(' + (fy || '1') + ')*' + jzN(uy) + ';' +
        'd=[d[0]+' + jzN(vx) + '-(x2pc*x2px-x2ps*x2py),d[1]+' + jzN(vy) + '-(x2ps*x2px+x2pc*x2py)];';
}
// scale about the text box centre: parts.sc + pivot (fx, fy: expressions, pre defines their variables)
function ex2_scaleC(m, fx, fy, pre) {
    var r = ex2_rect(m);
    m.parts.sc.push((pre || '') + 'f=[f[0]*(' + fx + '),f[1]*(' + fy + ')];');
    var pv = ex2_pivot(m, fx, fy, null, r.left + r.width / 2, r.top + r.height / 2, pre);
    if (pv) m.parts.pos.push(pv);
}
// rotate about a point of L's own space: parts.rot + pivot
function ex2_rotAt(m, ang, px, py, pre) {
    m.parts.rot.push((pre || '') + 'r+=' + ang + ';');
    var pv = ex2_pivot(m, '1', '1', ang, px, py, pre);
    if (pv) m.parts.pos.push(pv);
}
// comp position of a point of L's own space (L's static transform)
function ex2_toComp(L, x, y) {
    var tr = L.property('ADBE Transform Group'), a = tr.property('ADBE Anchor Point').value, p = tr.property('ADBE Position').value;
    var s = tr.property('ADBE Scale').value, r = tr.property('ADBE Rotate Z').value * Math.PI / 180;
    var ux = (x - a[0]) * s[0] / 100, uy = (y - a[1]) * s[1] / 100;
    return [p[0] + Math.cos(r) * ux - Math.sin(r) * uy, p[1] + Math.sin(r) * ux + Math.cos(r) * uy];
}
function ex2_scaleK(L) { var s = jzXf(L, 'ADBE Scale').value; return [Math.max(0.05, Math.abs(s[0]) / 100), Math.max(0.05, Math.abs(s[1]) / 100)]; }
// per-glyph order 0..1 (browser orderOf): glyph index in the layer, or the item's place in the cut for one-glyph layers
function ex2_ordEx(m) {
    var N = ex2_cutN(m), mi = m.o.mi || 0;
    return 'textTotal>1?(textIndex-1)/(textTotal-1):' + jzN(N > 1 ? jzClamp(mi / (N - 1), 0, 1) : 0);
}

// ================================================================ EXITS
/* ---- splitApart \u2014 \u4E0A\u4E0B\u306B\u5272\u308C\u308B: a hairline accent crack runs along each line, then the top and bottom halves spring apart
   (vertical text: left / right). The bottom half is a linked copy; each half is clipped by a comb mask (one tooth per line)
   and moved by a Transform effect. */
function ex2_halfPts(G, first) {
    var r = G.rect, fs = G.fs, X0 = r.left - fs * 2, X1 = r.left + r.width + fs * 2, eps = fs * 0.006, big = fs * 3, i;
    if (G.vert) {
        var cx = r.left + r.width / 2, Y0 = r.top - big, Y1 = r.top + r.height + big;
        return first ? ex2_box(X0, Y0, cx + eps, Y1) : ex2_box(cx - eps, Y0, X1, Y1);
    }
    var cy = [];
    for (i = 0; i < G.nL; i++) if (G.lines[i].chars.length) cy.push(G.lines[i].cy);
    if (!cy.length) cy.push(r.top + r.height / 2);
    var n = cy.length, a = [], b = [], hl = G.lead / 2;
    for (i = 0; i < n; i++) {
        if (first) { a.push(i ? cy[i] - hl : r.top - big); b.push(cy[i] + eps); }
        else { a.push(cy[i] - eps); b.push(i < n - 1 ? cy[i] + hl : r.top + r.height + big); }
    }
    var sp = X0 - fs, pts = [[sp, a[0]]];
    for (i = 0; i < n; i++) {
        pts.push([X1, a[i]]); pts.push([X1, b[i]]);
        if (i < n - 1) { pts.push([X0, b[i]]); pts.push([X0, a[i + 1]]); }
    }
    pts.push([sp, b[n - 1]]);
    return pts;
}
jzReg('exit', 'splitApart', { apply: function (m) {
    if (!ex2_hasOut(m)) return;
    var L = m.L, G = ex2_geo(m, true), sz = ex2_sz(m), H = ex2_hd(m), vert = G.vert, i;
    var canCopy = !ex2_matted(L) && !G.arranged && !ex2_crowded(m, 14);
    var E = 'var x2u=cl((PO-0.16)/0.84),x2e=oc(x2u),x2o=cl(PO/0.2)*' + jzN(sz * 0.03) + '+' + jzN(sz * 0.75) + '*x2e;';
    var sq = canCopy ? 65 : 12;          // no copy possible: the line closes up along the crack instead of splitting
    jzAnimator(L, 'JZ Out Squash', [['ADBE Text Scale 3D', vert ? [sq, 100, 100] : [100, sq, 100]]], H + E + 'x2e*100');
    m.parts.op.push('var x2q=cl((cl((PO-0.16)/0.84)-0.35)/0.65);f*=1-x2q*x2q*(3-2*x2q);');
    var above = null;
    if (canCopy) {
        var nm = ex2_nMask(L), D = ex2_copy(m, 'JZ Out Half', true, true);
        ex2_mask(L, ex2_halfPts(G, true), nm > 0, 'JZ Out Half A', H + 'PO<=0?1e4:0');
        ex2_mask(D, ex2_halfPts(G, false), nm > 0, 'JZ Out Half B');
        ex2_move(L, 'JZ Out Split', H + E + (vert ? '[-x2o,-x2o*0.3]' : '[-x2o*0.3,-x2o]'));
        ex2_move(D, 'JZ Out Split', H + E + (vert ? '[x2o,x2o*0.3]' : '[x2o*0.3,x2o]'));
        above = D;
    }
    // the crack: one accent hairline per line, overshooting the line ends while the halves part
    var S = ex2_shape(m, 'JZ Out Crack', above, false, true), lns = ex2_lines(G), acc = ex2_pick(m.ctx.sc.accent, m.ctx.sc.fg);
    jzNoGhost(S);
    var CE = H + 'var x2e=oc(cl((PO-0.16)/0.84)),x2p=' + jzN(sz) + '*(0.15+0.5*x2e),x2l=Math.max(1.5,' + jzN(sz) + '*(0.02+0.05*x2e));';
    for (i = lns.length - 1; i >= 0; i--) {
        var ln = lns[i], g = jzGrp(S, 'crack ' + (i + 1)), rc = jzAddRect(g, 10, 2, 0);
        rc.property('ADBE Vector Rect Position').setValue([ln.cx, ln.cy]);
        jzSetExpr(rc.property('ADBE Vector Rect Size'), CE + (vert ? '[x2l,' + jzN(ln.y1 - ln.y0) + '+2*x2p]' : '[' + jzN(ln.x1 - ln.x0) + '+2*x2p,x2l]'));
        jzAddFill(g, acc);        // after the rect is set up (adding the fill invalidates rc in AE)
    }
    jzSetExpr(jzXf(S, 'ADBE Opacity'), H + 'cl(PO/0.2)*(1-x2sm(0.55,1,PO))*100');
} });

/* ---- vSliceDrop \u2014 \u7E26\u30B9\u30E9\u30A4\u30B9\u843D\u4E0B: the line is cut into vertical strips that drop off the bottom of the frame one after another.
   Two square Wave Warps (vertical displacement, incommensurate widths) give four strip groups; their amplitudes plus a Transform
   drop are the least-squares fit of four gravity curves with staggered starts. */
jzReg('exit', 'vSliceDrop', { apply: function (m) {
    if (!ex2_hasOut(m)) return;
    var L = m.L, r = ex2_rect(m), sz = ex2_sz(m), H = ex2_hd(m), k = ex2_scaleK(L), sd = ex2_itSeed(m), i;
    var w = r.width * k[0] + sz * 0.7, n = jzClamp(Math.round(w / (sz * 0.24)), 4, 14), bw = w / n / k[0];
    var top = Math.min(ex2_toComp(L, r.left, r.top)[1], ex2_toComp(L, r.left + r.width, r.top)[1]) - sz * 0.35 * k[1];
    var D = Math.max(sz * 2, (m.H - top) / k[1] + sz);
    var E = 'var x2D=' + jzN(D) + ',x2d=[0.02,0.3,0.14,0.43],x2y=[],x2i,x2q;for(x2i=0;x2i<4;x2i++){x2q=cl((PO-x2d[x2i])/0.55);x2y.push((x2q*x2q*1.05-Math.sin(Math.PI*cl(x2q/0.25))*0.012)*x2D);}' +
        'var x2A=(x2y[0]+x2y[1]+x2y[2]+x2y[3])/4,x2B1=(x2y[1]+x2y[3]-x2y[0]-x2y[2])/4,x2B2=(x2y[2]+x2y[3]-x2y[0]-x2y[1])/4;';
    var ww = [[2 * bw, 'x2B1', ex2_r(sd, 301) * 360], [2 * bw * 1.53, 'x2B2', ex2_r(sd, 302) * 360]];
    for (i = 0; i < 2; i++) {
        var e = jzEffect(L, 'ADBE Wave Warp', 'JZ Out Strips ' + (i + 1));
        jzEP(e, 1, 2); jzEP(e, 3, ww[i][0]); jzEP(e, 4, 90); jzEP(e, 5, 0); jzEP(e, 6, 1); jzEP(e, 7, ww[i][2]);
        jzEX(e, 2, H + E + ww[i][1]);
    }
    ex2_move(L, 'JZ Out Drop', H + E + '[0,x2A]');
    m.parts.op.push('var x2f=cl((PO-0.95)/0.05);f*=1-x2f*x2f*(3-2*x2f);');
} });

/* ---- melt \u2014 \u6EB6\u3051\u308B: the line stretches down from its top edge while ragged strips drip lower, warms toward the accent and fades.
   Drips = two square + one sine Wave Warp (vertical) over a Transform drop. */
jzReg('exit', 'melt', { apply: function (m) {
    if (!ex2_hasOut(m)) return;
    var L = m.L, r = ex2_rect(m), sz = ex2_sz(m), H = ex2_hd(m), k = ex2_scaleK(L), sd = ex2_itSeed(m), i;
    var one = ex2_nGl(m) <= 1, st = one ? 0.3 : 0.15;
    var pre = 'var x2mx=1-0.05*PO,x2my=1+0.8*iq(PO);';
    m.parts.sc.push(pre + 'f=[f[0]*x2mx,f[1]*x2my];');
    var pv = ex2_pivot(m, 'x2mx', 'x2my', null, r.left + r.width / 2, r.top, pre);
    if (pv) m.parts.pos.push(pv);
    var WW = [[2, 2 * st, 0.28], [2, 2 * st * 1.57, 0.22], [1, 7.3 * st, 0.32]];
    for (i = 0; i < WW.length; i++) {
        var e = jzEffect(L, 'ADBE Wave Warp', 'JZ Out Drip ' + (i + 1));
        jzEP(e, 1, WW[i][0]); jzEP(e, 3, sz * WW[i][1] / k[0]); jzEP(e, 4, 90); jzEP(e, 5, 0); jzEP(e, 6, 1); jzEP(e, 7, ex2_r(sd, i, 311) * 360);
        jzEX(e, 2, H + 'iq(PO)*' + jzN(sz * WW[i][2] / k[1]) + '/(1+0.8*iq(PO))');
    }
    ex2_move(L, 'JZ Out Sag', H + '[0,iq(PO)*' + jzN(sz * 0.92 / k[1]) + '/(1+0.8*iq(PO))]');
    var acc = ex2_pick(m.ctx.sc.accent, m.ctx.sc.fg);
    jzAnimator(L, 'JZ Out Melt Colour', [['ADBE Text Fill Color', jzHex(acc)]], H + 'x2sm(0.3,0.9,PO)*45');
    m.parts.op.push('var x2f=cl((PO-0.55)/0.45);f*=1-x2f*x2f*(3-2*x2f);');
} });

/* ---- dissolve \u2014 \u307B\u308D\u307B\u308D: the line crumbles into small flakes that lift and fade, with a soft left-to-right sweep.
   Block Dissolve (random flakes) x a per-glyph fade / lift staggered left to right. */
jzReg('exit', 'dissolve', { apply: function (m) {
    if (!ex2_hasOut(m)) return;
    var L = m.L, sz = ex2_sz(m), H = ex2_hd(m), bw = Math.max(2, sz * 0.065);
    var bd = jzEffect(L, 'ADBE Block Dissolve', 'JZ Out Flakes');
    jzEP(bd, 2, bw); jzEP(bd, 3, bw); jzEP(bd, 4, 0); jzEP(bd, 5, 1);
    jzEX(bd, 1, H + '100*cl(PO*1.05)');
    jzAnimator(L, 'JZ Out Crumble', [['ADBE Text Opacity', 0], ['ADBE Text Position 3D', [0, -sz * 0.06, 0]], ['ADBE Text Scale 3D', [112, 112, 100]]],
        H + 'var o=' + ex2_ordEx(m) + ',x=x2sm(0.2+0.3*o,0.72+0.28*o,PO);x*100');
} });

/* ---- backspace \u2014 \u30D0\u30C3\u30AF\u30B9\u30DA\u30FC\u30B9: an accent cursor deletes the line from the end, accelerating like key repeat; or (per cut)
   selects everything from the end (accent highlight, glyphs in the background colour) and deletes it in one stroke.
   One-glyph layers delete in reverse reading order with the cursor hopping back. */
function ex2_curAfter(G, g, sz, cw) { return G.vert ? [g.cx, g.cy + G.fs * 0.5 + sz * 0.05 + cw / 2, sz * 0.96, cw] : [g.x1 + sz * 0.05 + cw / 2, g.cy, cw, sz]; }
function ex2_curBefore(G, g, sz, cw) { return G.vert ? [g.cx, g.cy - G.fs * 0.5 - sz * 0.05 - cw / 2, sz * 0.96, cw] : [g.x0 - sz * 0.05 - cw / 2, g.cy, cw, sz]; }
jzReg('exit', 'backspace', { apply: function (m) {
    if (!ex2_hasOut(m)) return;
    var L = m.L, G = ex2_geo(m, true), sz = ex2_sz(m), H = ex2_hd(m), sc = m.ctx.sc, i;
    var vis = [];
    for (i = 0; i < G.N; i++) if (!G.g[i].sp) vis.push(G.g[i]);
    if (!vis.length) return;
    var nv = vis.length, N = ex2_cutN(m), single = nv === 1 && N > 1;
    var sel = !single && (ex2_h(m.c.seed | 0, 601) & 1) === 1;
    var acc = ex2_pick(sc.accent, sc.fg), bg = ex2_pick(sc.bg, '#000000'), cw = Math.max(2, sz * 0.07);
    var helper = !G.arranged && !ex2_crowded(m, 30);
    var DEL = 'var x2del=Math.pow(cl((PO-0.1)/0.8),1.3),x2bl=Math.floor(time*5)%2==0;';
    var R = [], rk = -1;
    for (i = 0; i < G.N; i++) { if (!G.g[i].sp) rk = G.g[i].k; R.push(rk); }
    var RA = 'var R=' + ex2_arr(R) + ',x2r=R[textIndex-1];';
    var C = [], posEx = null, opEx = null, cs = null;
    if (single) {
        var j = jzClamp(Math.round(m.o.mi || 0), 0, N - 1), tj = (N - 1 - j) / N;
        jzAnimator(L, 'JZ Out Delete', [['ADBE Text Opacity', 0]], H + DEL + 'x2del>' + jzN(tj) + '?100:0');
        var ca = ex2_curAfter(G, vis[0], sz, cw), cb = ex2_curBefore(G, vis[0], sz, cw);
        var md = 'var x2m=0;if(PO>0&&PO<0.94){if(' + (j === N - 1 ? 'x2del<=0' : 'false') + ')x2m=x2bl?1:0;else if(x2del>' + jzN(tj) + '&&(' + (j === 0 ? 'true' : 'x2del<=' + jzN(tj + 1 / N)) + '))x2m=(' + (j === 0 ? 'x2del>=1&&!x2bl' : 'false') + ')?0:2;}';
        posEx = H + DEL + md + 'x2m==1?' + ex2_pts([ca]).slice(1, -1) + ':' + ex2_pts([cb]).slice(1, -1);
        opEx = H + DEL + md + 'x2m>0?100:0';
        cs = [ca[2], ca[3]];
    } else if (sel) {
        var SEL = DEL + 'var x2s=Math.ceil(' + nv + '*x2o2(PO/0.42)),x2from=' + nv + '-x2s;';
        jzAnimator(L, 'JZ Out Delete', [['ADBE Text Opacity', 0]], H + 'PO>=0.55?100:0');
        jzAnimator(L, 'JZ Out Selected', [['ADBE Text Fill Color', jzHex(bg)]], H + SEL + RA + 'PO>0&&PO<0.55&&x2r>=x2from&&x2r>=0?100:0');
        for (i = 0; i < nv; i++) { var q0 = ex2_curBefore(G, vis[i], sz, cw); C.push([q0[0], q0[1]]); cs = [q0[2], q0[3]]; }
        var q1 = ex2_curAfter(G, vis[nv - 1], sz, cw); C.push([q1[0], q1[1]]);
        posEx = H + SEL + 'var C=' + ex2_pts(C) + ';PO>=0.55?C[0]:C[Math.max(0,Math.min(' + nv + ',x2from))]';
        opEx = H + SEL + 'PO<=0||PO>=0.94?0:((PO>=0.55||x2s==0)?(x2bl?100:0):100)';
        if (helper) {        // the selection highlight behind the glyphs (the browser draws it in the ghost passes too)
            var S2 = ex2_shape(m, 'JZ Out Selection', null, true, true), trk = ((ex2_td(L) || {}).tracking || 0) / 1000 * G.fs;
            for (i = nv - 1; i >= 0; i--) {
                var g = vis[i], grp = jzGrp(S2, 'sel ' + (i + 1));
                if (G.vert) jzAddRect(grp, sz * 1.2, G.lead + sz * 0.04, 0, g.cx, g.cy);
                else jzAddRect(grp, g.x1 - g.x0 + Math.max(0, trk) + sz * 0.04, Math.max(sz * 1.2, G.lead + sz * 0.02), 0, (g.x0 + g.x1 + Math.max(0, trk)) / 2, g.cy);
                jzAddFill(grp, acc);
                jzSetExpr(jzGX(grp).property('ADBE Vector Group Opacity'), H + SEL + 'PO>0&&PO<0.55&&' + g.k + '>=x2from?100:0');
            }
        }
    } else {
        var KX = DEL + 'var x2n=' + nv + ',x2k=x2n-Math.min(x2n,Math.floor(x2del*(x2n+0.999)));';
        jzAnimator(L, 'JZ Out Delete', [['ADBE Text Opacity', 0]], H + KX + RA + 'x2r>=x2k&&x2r>=0?100:0');
        var b0 = ex2_curBefore(G, vis[0], sz, cw); C.push([b0[0], b0[1]]); cs = [b0[2], b0[3]];
        for (i = 0; i < nv; i++) { var a0 = ex2_curAfter(G, vis[i], sz, cw); C.push([a0[0], a0[1]]); }
        posEx = H + KX + 'var C=' + ex2_pts(C) + ';C[Math.max(0,Math.min(' + nv + ',x2k))]';
        opEx = H + KX + 'PO<=0||PO>=0.94?0:((PO<0.1||x2k==0)?(x2bl?100:0):100)';
    }
    if (!helper || !posEx) return;
    var S = ex2_shape(m, 'JZ Out Cursor', null, false, true), cg = jzGrp(S, 'cursor'), rc = jzAddRect(cg, cs[0], cs[1], 0);
    jzSetExpr(rc.property('ADBE Vector Rect Position'), posEx);
    jzAddFill(cg, acc);       // after the rect is set up (adding the fill invalidates rc in AE)
    jzNoGhost(S);
    jzSetExpr(jzXf(S, 'ADBE Opacity'), opEx);
} });

/* ---- scrambleOut \u2014 \u8A18\u53F7\u5316: glyph by glyph (reading order + a little randomness) the characters turn into flickering symbols
   (some in the accent), shrink and blink away. Symbols come from a Source Text expression (the lyric stays editable: the
   expression only rewrites it during the exit); a layout that already drives the source text gets Character Offset instead. */
jzReg('exit', 'scrambleOut', { apply: function (m) {
    if (!ex2_hasOut(m)) return;
    var L = m.L, H = ex2_hd(m), acc = ex2_pick(m.ctx.sc.accent, m.ctx.sc.fg), N = ex2_cutN(m), mi = m.o.mi || 0;
    var o1 = jzN(N > 1 ? jzClamp(mi / (N - 1), 0, 1) : 0);
    var Q = H + 'var o=' + ex2_ordEx(m) + ',q=x2w(PO,0.65*o+0.35*hh(textIndex*7.31+SD*0.13+701),0.55),x2st=Math.floor(time*24);';
    jzAnimator(L, 'JZ Out Shrink', [['ADBE Text Scale 3D', [65, 65, 100]]], Q + 'q>0&&q<1?x2sm(0.4,1,q)*100:0');
    jzAnimator(L, 'JZ Out Code Colour', [['ADBE Text Fill Color', jzHex(acc)]], Q + 'q>0&&q<1&&hh(textIndex*3.71+x2st*1.31+SD*0.29+704)<0.3?100:0');
    jzAnimator(L, 'JZ Out Hide', [['ADBE Text Opacity', 0]], Q + 'q>=1||(q>0.55&&hh(textIndex*5.33+x2st*2.17+SD*0.41+703)<(q-0.55)*2.4)?100:0');
    var src = L.property('ADBE Text Properties').property('ADBE Text Document'), busy = false;
    try { busy = !!src.expression; } catch (e0) { busy = true; }
    if (busy) {
        jzAnimator(L, 'JZ Out Scramble', [['ADBE Text Character Offset', 60]], Q + 'q>0&&q<1?20+hh(textIndex*9.13+x2st*3.3+SD*0.53+702)*80:0');
        return;
    }
    try {
        src.expression = H + 'var x2out=value;if(PO>0){var s=String(value),PL=' + ex2_esc(EX2_POOL) + ',o2="",n=0,i=0,j,c,o,q,x2st=Math.floor(time*24);' +
            'for(j=0;j<s.length;j++){c=s.charAt(j);if(c!="\\r"&&c!="\\n"&&c!="\\u0003")n++;}' +
            'for(j=0;j<s.length;j++){c=s.charAt(j);if(c=="\\r"||c=="\\n"||c=="\\u0003"){o2+=c;continue;}i++;' +
            'o=n>1?(i-1)/(n-1):' + o1 + ';q=x2w(PO,0.65*o+0.35*hh(i*7.31+SD*0.13+701),0.55);' +
            'if(q>0&&q<1&&c!=" "&&c!="\\u3000")c=PL.charAt(Math.floor(hh(i*9.13+x2st*3.3+SD*0.53+702)*PL.length)%PL.length);o2+=c;}x2out=o2;}x2out';
    } catch (e1) { jzWarn('scrambleOut source: ' + e1.toString()); }
} });

/* ---- glitchDissolve \u2014 \u30D6\u30ED\u30C3\u30AF\u5316: each glyph (random start) jitters sideways cut to a sliver in glitch colours and breaks into
   a cloud of flickering colour blocks that thins out. Blocks = a shape layer in the text's space (a few rects per glyph). */
jzReg('exit', 'glitchDissolve', { apply: function (m) {
    if (!ex2_hasOut(m)) return;
    var L = m.L, G = ex2_geo(m, true), sz = ex2_sz(m), H = ex2_hd(m), sc = m.ctx.sc, i, j;
    var c0 = ex2_pick(jzTextColor(L), sc.fg), acc = ex2_pick(sc.accent, sc.fg), cols = [c0, acc, ex2_pick(sc.ghostA, acc), ex2_pick(sc.ghostB, c0)];
    var Q = H + 'var x2T=hh(textIndex*4.13+SD*0.17+801)*0.55,q=(PO-x2T)/0.45,x2st=Math.floor(time*24),x2a=q>0&&q<0.3;';
    jzAnimator(L, 'JZ Out Jitter', [['ADBE Text Position 3D', [sz * 0.12, 0, 0]]], Q + 'x2a?(hh(textIndex*6.1+x2st*1.7+SD*0.23+803)*2-1)*100:0');
    // the browser clips each glyph to a random top / bottom sliver: squash + shift toward that side
    jzAnimator(L, 'JZ Out Sliver', [['ADBE Text Scale 3D', [100, 50, 100]]], Q + 'x2a?hh(textIndex*8.3+x2st*2.9+SD*0.37+804)*100:0');
    jzAnimator(L, 'JZ Out Sliver Side', [['ADBE Text Position 3D', [0, sz * 0.25, 0]]],
        Q + 'x2a?hh(textIndex*8.3+x2st*2.9+SD*0.37+804)*(hh(textIndex*2.3+x2st*5.1+SD*0.43+805)<0.5?-100:100):0');
    jzAnimator(L, 'JZ Out Glitch Colour', [['ADBE Text Fill Color', jzHex(acc)]], Q + 'x2a&&hh(textIndex*2.3+x2st*5.1+SD*0.43+802)<0.25?100:0');
    jzAnimator(L, 'JZ Out Hide', [['ADBE Text Opacity', 0]], Q + 'q>=0.3?100:0');
    if (G.arranged || ex2_crowded(m, 24) || !G.nv) return;
    var per = jzClamp(Math.floor(48 / G.nv), 2, 4), S = ex2_shape(m, 'JZ Out Blocks', null, false, true);
    jzNoGhost(S);
    var CL = '[' + ex2_col(cols[0]) + ',' + ex2_col(cols[1]) + ',' + ex2_col(cols[2]) + ',' + ex2_col(cols[3]) + ']';
    for (i = G.N - 1; i >= 0; i--) {
        var g = G.g[i]; if (g.sp) continue;
        var gg = jzGrp(S, 'blocks ' + (i + 1));
        jzGX(gg).property('ADBE Vector Position').setValue([g.cx, g.cy]);
        for (j = 0; j < per; j++) {
            var BE = H + 'var x2T=hh(' + (i + 1) + '*4.13+SD*0.17+801)*0.55,q=(PO-x2T)/0.45,x2st=Math.floor(time*24);seedRandom(SD*7+' + ((i + 1) * 131 + j * 17) + '+x2st*1009,true);' +
                'var r1=random(),r2=random(),r3=random(),r4=random(),r5=random(),r6=random(),x2on=q>0&&q<1&&' + j + '<Math.ceil(5*(1-q*0.8))&&r6>=q*0.55;';
            // the rect is fully set up before the fill is added (adding a sibling invalidates rc in AE)
            var sb = ex2_sub(gg, 'b' + (j + 1)), rc = jzAddRect(sb, 10, 10, 0);
            jzSetExpr(rc.property('ADBE Vector Rect Size'), BE + 'x2on?[' + jzN(sz) + '*(0.12+0.48*r1)*(1-q*0.5),' + jzN(sz) + '*(0.06+0.24*r2)]:[0,0]');
            jzSetExpr(rc.property('ADBE Vector Rect Position'), BE + '[(r3*2-1)*' + jzN(sz * 0.34) + ',(r4*2-1)*' + jzN(sz * 0.34) + ']');
            var fl = jzAddFill(sb, cols[j % 4]);
            jzSetExpr(fl.property('ADBE Vector Fill Color'), BE + 'var CL=' + CL + ';CL[Math.floor(r5*4)%4]');
        }
    }
} });

/* ---- echoOut \u2014 \u6B8B\u97FF: the line fades while three outline echoes (text colour, accent, text colour) swell out from its centre
   and dissolve. Echoes = linked copies with fill off / stroke on, scaled about the box centre. */
jzReg('exit', 'echoOut', { apply: function (m) {
    if (!ex2_hasOut(m)) return;
    var L = m.L, r = ex2_rect(m), sz = ex2_sz(m), H = ex2_hd(m), sc = m.ctx.sc, k;
    var cx = r.left + r.width / 2, cy = r.top + r.height / 2, lw = Math.max(1, sz * 0.016);
    var col = ex2_pick(jzTextColor(L), sc.fg), acc = ex2_pick(sc.accent, sc.fg);
    m.parts.op.push('var x2a=cl(PO/0.45);f*=1-(0.5-0.5*Math.cos(Math.PI*x2a));');
    if (ex2_matted(L) || ex2_crowded(m, 8)) {      // no copies: the line itself swells a little while it fades
        ex2_scaleC(m, 'x2es', 'x2es', 'var x2es=1+0.25*oc(PO);');
        return;
    }
    for (k = 0; k < 3; k++) {
        var D = ex2_copy(m, 'JZ Out Echo ' + (k + 1), false, true), tr = D.property('ADBE Transform Group'), c = k === 1 ? acc : col;
        jzTextDoc(D, function (d) {
            d.applyFill = false; d.applyStroke = true; d.strokeWidth = lw;
            try { d.strokeColor = jzHex(c); } catch (e0) {}
            try { d.strokeOverFill = true; } catch (e1) {}
        });
        tr.property('ADBE Anchor Point').setValue([cx, cy]);
        jzSetExpr(tr.property('ADBE Position'), '');
        tr.property('ADBE Position').setValue([cx, cy]);
        var QE = H + 'var q=cl((PO-' + jzN(k * 0.15) + ')/0.55);';
        jzSetExpr(tr.property('ADBE Scale'), QE + 'var s=1+0.5*oc(q)*' + jzN(1 + k * 0.2) + ';[value[0]*s,value[1]*s]');
        jzSetExpr(tr.property('ADBE Opacity'), QE + 'q>0&&q<1?80*(1-q):0');
    }
} });

/* ---- whipOut \u2014 \u30DB\u30A4\u30C3\u30D7: a small wind-up the other way, then the line whips off-screen along its reading direction, stretching,
   leaning into the motion and smearing (Directional Blur for the browser's streak copies). */
jzReg('exit', 'whipOut', { apply: function (m) {
    if (!ex2_hasOut(m)) return;
    var L = m.L, r = ex2_rect(m), sz = ex2_sz(m), H = ex2_hd(m), k = ex2_scaleK(L), vert = ex2_isVert(L);
    var dir = ex2_bit(m, 4) ? 1 : -1, rot = jzXf(L, 'ADBE Rotate Z').value * Math.PI / 180, szc = sz * k[1];
    var dist = (vert ? m.H + r.height * k[1] : m.W + r.width * k[0]) + szc * 2;
    var T = 'var x2pb=' + jzN(szc * 0.14) + '*oc(PO/0.28),x2u=cl((PO-0.26)/0.74),x2v=Math.min(1.6,2*x2u),x2off=' + dir + '*(' + jzN(dist) + '*x2u*x2u-x2pb*(1-x2u));';
    var ux = vert ? -Math.sin(rot) : Math.cos(rot), uy = vert ? Math.cos(rot) : Math.sin(rot);
    m.parts.pos.push(T + 'd=[d[0]+x2off*' + jzN(ux) + ',d[1]+x2off*' + jzN(uy) + '];');
    if (vert) ex2_scaleC(m, '1', '(1+x2v*0.45)', T);
    else {
        ex2_scaleC(m, '(1+x2v*0.55)', '(1-x2v*0.08)', T);
        jzAnimator(L, 'JZ Out Lean', [['ADBE Text Skew', -dir * 28]], H + T + 'Math.min(28,x2v*16)/28*100');
    }
    var db = jzEffect(L, 'ADBE Motion Blur', 'JZ Out Streak');
    jzEP(db, 1, vert ? 0 : 90);
    jzEX(db, 2, H + T + jzN(sz * 0.6) + '*Math.min(1,x2v)');
    m.parts.op.push('var x2f=cl((PO-0.9)/0.1);f*=1-x2f*x2f*(3-2*x2f);');
} });

/* ---- gravity \u2014 \u91CD\u529B\u843D\u4E0B: glyphs (random starts) hop up a little, then fall off the bottom of the frame under gravity,
   drifting sideways and tumbling. */
jzReg('exit', 'gravity', { apply: function (m) {
    if (!ex2_hasOut(m)) return;
    var L = m.L, r = ex2_rect(m), sz = ex2_sz(m), H = ex2_hd(m), k = ex2_scaleK(L);
    var rot = jzXf(L, 'ADBE Rotate Z').value * Math.PI / 180, dsx = Math.sin(rot), dsy = Math.cos(rot);
    var top = Math.min(ex2_toComp(L, r.left, r.top)[1], ex2_toComp(L, r.left + r.width, r.top)[1]);
    var D = Math.max(sz * 2, (m.H - top) / k[1] + sz * 1.2), K = D + sz;
    var Q = H + 'var d0=hh(textIndex*3.1+SD*0.11+901)*0.4,q=cl((PO-d0)/0.6),h=' + jzN(sz) + '*(0.12+0.22*hh(textIndex*5.7+SD*0.19+902)),D=' + jzN(D) +
        ',v0=2*h+Math.sqrt(4*h*h+4*h*D),g2=D+v0,dn=q<=0?0:-v0*q+g2*q*q,sd=(hh(textIndex*7.3+SD*0.23+903)*2-1)*' + jzN(sz * 0.9) + '*q;';
    jzAnimator(L, 'JZ Out Fall', [['ADBE Text Position 3D', [K, K, 0]]],
        Q + '[(dn*' + jzN(dsx) + '+sd*' + jzN(dsy) + ')/' + jzN(K) + '*100,(dn*' + jzN(dsy) + '-sd*' + jzN(dsx) + ')/' + jzN(K) + '*100,0]');
    jzAnimator(L, 'JZ Out Tumble', [['ADBE Text Rotation', 240]], Q + '(hh(textIndex*9.1+SD*0.29+904)*2-1)*q*q*100');
    jzAnimator(L, 'JZ Out Hide', [['ADBE Text Opacity', 0]], Q + 'q>=1?100:x2sm(0.92,1,q)*100');
} });

/* ---- popOut \u2014 \u5F3E\u3051\u308B: glyphs (reading order + randomness) swell and wobble while heating to the accent, then pop: an accent
   ring and eight sparks burst from where each glyph was. */
jzReg('exit', 'popOut', { apply: function (m) {
    if (!ex2_hasOut(m)) return;
    var L = m.L, G = ex2_geo(m, true), sz = ex2_sz(m), H = ex2_hd(m), sd = ex2_itSeed(m), i;
    var acc = ex2_pick(m.ctx.sc.accent, m.ctx.sc.fg), N = ex2_cutN(m), mi = m.o.mi || 0, O = [];
    for (i = 0; i < G.N; i++) O.push(0.6 * (G.N > 1 ? i / (G.N - 1) : (N > 1 ? jzClamp(mi / (N - 1), 0, 1) : 0)) + 0.4 * ex2_r(sd, i, 1001));
    var Q = H + 'var O=' + ex2_arr(O) + ',q=x2w(PO,O[textIndex-1]||0,0.5),u=cl(q/0.55);';
    jzAnimator(L, 'JZ Out Swell', [['ADBE Text Scale 3D', [150, 150, 100]]], Q + 'q>0&&q<0.55?u*u*100:0');
    jzAnimator(L, 'JZ Out Wobble', [['ADBE Text Scale 3D', [107, 93, 100]]], Q + 'q>0&&q<0.55?Math.sin(u*26)*u*100:0');
    jzAnimator(L, 'JZ Out Heat', [['ADBE Text Fill Color', jzHex(acc)]], Q + 'q>0?u*70:0');
    jzAnimator(L, 'JZ Out Hide', [['ADBE Text Opacity', 0]], Q + 'q>=0.55?100:0');
    if (G.arranged || ex2_crowded(m, 20) || !G.nv) return;
    var S = ex2_shape(m, 'JZ Out Bursts', null, false, true);
    jzNoGhost(S);
    for (i = G.N - 1; i >= 0; i--) {
        var g = G.g[i]; if (g.sp) continue;
        var grp = jzGrp(S, 'burst ' + (i + 1)), BE = H + 'var q=x2w(PO,' + jzN(O[i]) + ',0.5),u=cl((q-0.55)/0.45),e=oc(u);';
        jzGX(grp).property('ADBE Vector Position').setValue([g.cx, g.cy]);
        jzSetExpr(jzGX(grp).property('ADBE Vector Group Opacity'), BE + 'q>=0.55&&q<1?(1-u)*100:0');
        var ring = ex2_sub(grp, 'ring'), el = jzVecs(ring).addProperty('ADBE Vector Shape - Ellipse');
        jzSetExpr(el.property('ADBE Vector Ellipse Size'), BE + 'var R=' + jzN(sz) + '*(0.35+0.45*e)*2;[R,R]');
        var st = jzAddStroke(ring, acc, 2);
        jzSetExpr(st.property('ADBE Vector Stroke Width'), BE + 'Math.max(1,' + jzN(sz * 0.08) + '*(1-u))');
        var tk = ex2_sub(grp, 'sparks'), rc = jzAddRect(tk, 10, 2, 0);
        jzSetExpr(rc.property('ADBE Vector Rect Size'), BE + '[Math.max(0.01,' + jzN(sz * 0.16) + '*(1-u)),Math.max(1,' + jzN(sz * 0.08) + '*(1-u))]');
        jzSetExpr(rc.property('ADBE Vector Rect Position'), BE + '[' + jzN(sz) + '*(0.5+0.55*e)+' + jzN(sz * 0.08) + '*(1-u),0]');
        jzAddFill(tk, acc);
        var rp = jzVecs(tk).addProperty('ADBE Vector Filter - Repeater');
        rp.property('ADBE Vector Repeater Copies').setValue(8);
        var rt = rp.property('ADBE Vector Repeater Transform');
        rt.property('ADBE Vector Repeater Position').setValue([0, 0]);
        rt.property('ADBE Vector Repeater Rotation').setValue(45);
        jzGX(tk).property('ADBE Vector Rotation').setValue(ex2_r(sd, i, 1002) * 360);
    }
} });

/* ---- burn \u2014 \u713C\u5931: the line burns away from the bottom up, the burn front sweeping along the reading direction (either way,
   per cut): glyphs heat to the accent, char dark, a glowing rough edge eats them and embers rise from the front.
   Front = one slanted mask shrunk by mask expansion (the browser clips each glyph from its own bottom, staggered). */
jzReg('exit', 'burn', { apply: function (m) {
    if (!ex2_hasOut(m)) return;
    var L = m.L, G = ex2_geo(m, true), sz = ex2_sz(m), H = ex2_hd(m), sc = m.ctx.sc, sd = ex2_itSeed(m), i, j;
    var rev = ex2_bit(m, 5), acc = ex2_pick(sc.accent, sc.fg), bg = ex2_pick(sc.bg, '#000000'), vert = G.vert;
    var r = G.rect, x0 = r.left, x1 = r.left + r.width, yT = r.top, yB = r.top + r.height, Wt = Math.max(1, r.width), hb = Math.max(1, r.height), fs = G.fs;
    // per glyph: horizontal -> start u (0..1 along the burn), q = cl((PO-0.45u)/0.55); vertical -> the front rises through the column
    var U = [], VB = [], sv = fs / hb, N = ex2_cutN(m), one = !vert && G.nv <= 1;
    var u1 = N > 1 ? jzClamp((m.o.mi || 0) / (N - 1), 0, 1) : 0;     // one-glyph layer: its place in the cut
    if (rev) u1 = 1 - u1;
    for (i = 0; i < G.N; i++) {
        var g = G.g[i], u = one ? u1 : G.arranged ? (G.N > 1 ? i / (G.N - 1) : 0) : (g.cx - x0) / Wt;
        if (rev && !one) u = 1 - u;
        U.push(jzClamp(u, 0, 1));
        VB.push(jzClamp((yB - (g.cy + fs * 0.5)) / hb, -0.5, 1.5));
    }
    var QG = vert ? 'var VB=' + ex2_arr(VB) + ',x2kf=cl((PO-0.03)/0.9),q=cl(0.12+0.88*(x2kf-(VB[textIndex-1]||0))/' + jzN(sv) + ');'
        : 'var U=' + ex2_arr(U) + ',q=cl((PO-0.45*(U[textIndex-1]||0))/0.55);';
    jzAnimator(L, 'JZ Out Heat', [['ADBE Text Fill Color', jzHex(acc)]], H + QG + 'x2sm(0,0.2,q)*100');
    jzAnimator(L, 'JZ Out Char', [['ADBE Text Fill Color', jzHex(ex2_mix(acc, bg, 0.75))]], H + QG + 'x2sm(0.35,0.95,q)*100');
    jzAnimator(L, 'JZ Out Flicker', [['ADBE Text Position 3D', [sz * 0.012, 0, 0]]], H + QG + '(hh(textIndex*3.9+Math.floor(time*24)*1.3+SD*0.2+1102)*2-1)*x2sm(0,0.2,q)*(q<1?100:0)');
    // the front: a rotated rectangle whose lower edge is the burn line; negative expansion moves it up (1e4 before the exit)
    var sl = vert || one ? 0 : hb * 0.45 / (0.484 * Wt) * (rev ? -1 : 1), nn = Math.sqrt(1 + sl * sl);
    var tx = 1 / nn, ty = sl / nn, nx = sl / nn, ny = -1 / nn;
    var xm = (x0 + x1) / 2, yF = vert ? yB + hb * 0.034 : one ? yB + fs * 0.03 : yB + hb * 0.291 / 0.484;
    var HW = Wt / 2 + hb * 4 + fs * 2, HT = hb * 6 + fs * 4;
    var c1 = [xm - HW * tx, yF - HW * ty], c2 = [xm + HW * tx, yF + HW * ty];
    var pts = [c1, c2, [c2[0] + HT * nx, c2[1] + HT * ny], [c1[0] + HT * nx, c1[1] + HT * ny]];
    var mk = ex2_mask(L, pts, ex2_nMask(L) > 0, 'JZ Out Burn', H + (one ? 'PO<=0?1e4:-' + jzN(hb + fs * 0.03) + '*cl((cl((PO-' + jzN(0.45 * u1) + ')/0.55)-0.12)/0.88)' : 'PO<=0?1e4:-' + jzN(hb / nn) + '*PO/' + (vert ? '0.9' : '0.484')));
    try { mk.property('ADBE Mask Feather').setValue([sz * 0.03, sz * 0.03]); } catch (e0) {}
    var re = jzEffect(L, 'ADBE Roughen Edges', 'JZ Out Char Edge');
    jzEP(re, 1, 2); jzEP(re, 2, jzHex(acc)); jzEP(re, 6, 60);
    jzEX(re, 3, H + jzN(sz * 0.06) + '*x2sm(0.02,0.4,PO)');
    jzEX(re, 10, 'time*240');
    if (G.arranged || ex2_crowded(m, 20) || !G.nv) return;
    // embers rising from the front
    var per = jzClamp(Math.floor(30 / G.nv), 1, 3), S = ex2_shape(m, 'JZ Out Embers', null, false, true);
    jzNoGhost(S);
    for (i = G.N - 1; i >= 0; i--) {
        var gl = G.g[i]; if (gl.sp) continue;
        for (j = 0; j < per; j++) {
            var b0 = 0.15 + ex2_r(sd, i, j, 1103) * 0.6, K0 = Math.pow(jzClamp((b0 - 0.12) / 0.88, 0, 1), 1.15);
            var ey0 = vert ? gl.cy + fs * (0.5 - K0) : yB - hb * jzClamp((0.55 * b0 - 0.066) / 0.484, 0, 1);
            var ex0 = gl.cx + ex2_rs(sd, i, j, 1104) * gl.w * 0.4;
            var qg = vert ? 'var x2kf=cl((PO-0.03)/0.9),q=cl(0.12+0.88*(x2kf-(' + jzN(VB[i]) + '))/' + jzN(sv) + ');' : 'var q=cl((PO-' + jzN(0.45 * U[i]) + ')/0.55);';
            var EE = H + qg + 'var a=(q-' + jzN(b0) + ')/0.35;';
            var eg = jzGrp(S, 'ember ' + (i + 1) + '.' + (j + 1)), el = jzVecs(eg).addProperty('ADBE Vector Shape - Ellipse');
            jzSetExpr(el.property('ADBE Vector Ellipse Size'), EE + 'var R=Math.max(1,' + jzN(sz * 0.04) + '*(1-cl(a)))*2;[R,R]');
            jzAddFill(eg, acc);       // after the ellipse is set up (adding the fill invalidates el in AE)
            jzSetExpr(jzGX(eg).property('ADBE Vector Position'), EE + 'a=cl(a);[' + jzN(ex0) + '+Math.sin(a*6+' + j + ')*' + jzN(sz * 0.05) + ',' + jzN(ey0) + '-a*' + jzN(sz * 0.55) + ']');
            jzSetExpr(jzGX(eg).property('ADBE Vector Group Opacity'), EE + 'a>0&&a<1?(1-a)*100:0');
        }
    }
} });

/* ---- sweepCover \u2014 \u30D0\u30FC\u3067\u96A0\u3059: an accent bar sweeps over each line (lines staggered), the line is gone underneath, and the bar
   retracts in the same direction. */
jzReg('exit', 'sweepCover', { apply: function (m) {
    if (!ex2_hasOut(m)) return;
    var L = m.L, G = ex2_geo(m, true), sz = ex2_sz(m), H = ex2_hd(m), acc = ex2_pick(m.ctx.sc.accent, m.ctx.sc.fg), i;
    var lns = G.arranged ? [{ li: 0, x0: G.rect.left, x1: G.rect.left + G.rect.width, y0: G.rect.top, y1: G.rect.top + G.rect.height, cy: G.rect.top + G.rect.height / 2, cx: G.rect.left + G.rect.width / 2 }] : ex2_lines(G);
    if (G.vert && !G.arranged) {       // vertical text: every glyph cell is its own "line" (the browser's bars step down the column)
        lns = [];
        for (i = 0; i < G.nL; i++) {
            if (!G.lines[i].chars.length || ex2_isSp(G.lines[i].chars[0])) continue;
            var cyv = G.lines[i].cy;
            lns.push({ li: i, x0: G.rect.left, x1: G.rect.left + G.rect.width, y0: cyv - G.fs * 0.5, y1: cyv + G.fs * 0.5, cy: cyv, cx: G.rect.left + G.rect.width / 2 });
        }
    }
    var nL = lns.length; if (!nL) return;
    var vert = G.vert && !G.arranged, st = nL > 1 ? Math.min(0.12, 0.3 / (nL - 1)) : 0, span = 1 - (nL - 1) * st;
    var single = ex2_single(m), N = ex2_cutN(m), ord = N > 1 ? jzClamp((m.o.mi || 0) / (N - 1), 0, 1) : 0;
    var LI = [];
    for (i = 0; i < G.N; i++) {
        var li = 0, j;
        for (j = 0; j < nL; j++) if (lns[j].li === G.g[i].li) li = j;
        LI.push(G.arranged ? 0 : li);
    }
    var uOf = single ? 'x2w(PO,' + jzN(ord) + ',0.45)' : 'cl((PO-x2l*' + jzN(st) + ')/' + jzN(span) + ')';
    var helper = !ex2_crowded(m, 30);
    jzAnimator(L, 'JZ Out Covered', [['ADBE Text Opacity', 0]], H + 'var LI=' + ex2_arr(LI) + ',x2l=LI[textIndex-1]||0,u=' + uOf + ';' + (helper ? 'u>=0.5?100:0' : 'x2sm(0.3,0.6,u)*100'));
    if (!helper) return;
    var S = ex2_shape(m, 'JZ Out Bars', null, false, true), pa = sz * 0.14, pc = sz * 0.1;
    jzNoGhost(S);
    for (i = nL - 1; i >= 0; i--) {
        var ln = lns[i], grp = jzGrp(S, 'bar ' + (i + 1)), rc = jzAddRect(grp, 10, 10, 0);
        var a0 = vert ? ln.y0 - pa : ln.x0 - pa, a1 = vert ? ln.y1 + pa : ln.x1 + pa;
        var BE = H + 'var x2l=' + i + ',u=' + uOf + ',c1=x2io(u/0.5),c2=x2io((u-0.5)/0.5),p0=' + jzN(a0) + '+' + jzN(a1 - a0) + '*c2,p1=' + jzN(a0) + '+' + jzN(a1 - a0) + '*c1;';
        if (vert) {
            jzSetExpr(rc.property('ADBE Vector Rect Size'), BE + '[' + jzN(ln.x1 - ln.x0 + pc * 2) + ',Math.max(0,p1-p0)]');
            jzSetExpr(rc.property('ADBE Vector Rect Position'), BE + '[' + jzN(ln.cx) + ',(p0+p1)/2]');
        } else {
            jzSetExpr(rc.property('ADBE Vector Rect Size'), BE + '[Math.max(0,p1-p0),' + jzN(ln.y1 - ln.y0 + pc * 2) + ']');
            jzSetExpr(rc.property('ADBE Vector Rect Position'), BE + '[(p0+p1)/2,' + jzN(ln.cy) + ']');
        }
        jzAddFill(grp, acc);      // after the rect is set up (adding the fill invalidates rc in AE)
        jzSetExpr(jzGX(grp).property('ADBE Vector Group Opacity'), BE + 'u>0&&u<1&&p1-p0>0.3?100:0');
    }
} });

/* ---- shatterLite \u2014 \u56DB\u5206\u5272\u98DB\u6563: every glyph cracks into quarters that fly apart outward (with gravity), shrinking and fading.
   The quarters are the lyric layer + three linked copies, each clipped to one quarter of every glyph (one mask per glyph) and
   thrown by its own Transform effect; glyphs shrink / fade on their own staggered clocks. */
jzReg('exit', 'shatterLite', { apply: function (m) {
    if (!ex2_hasOut(m)) return;
    var L = m.L, G = ex2_geo(m, true), sz = ex2_sz(m), H = ex2_hd(m), sd = ex2_itSeed(m), i, q;
    var Q = H + 'var x2d=hh(textIndex*3.3+SD*0.13+1201)*0.35,q=cl((PO-x2d)/0.65),e=oc(q);';
    if (ex2_matted(L) || G.arranged || ex2_crowded(m, 8) || !G.nv) {     // no copies: whole glyphs fly off toward random corners
        jzAnimator(L, 'JZ Out Scatter', [['ADBE Text Position 3D', [sz * 2.6, sz * 2.6, 0]]],
            Q + 'var dx=(hh(textIndex*5.1+SD*0.3+1202)<0.5?-1:1)+(hh(textIndex*6.7+SD*0.5+1206)*2-1)*0.5,dy=(hh(textIndex*2.9+SD*0.7+1207)<0.5?-1:1)+(hh(textIndex*8.3+SD*0.9+1205)*2-1)*0.5,dd=' + jzN(sz * 1.7) + '*(0.7+0.6*hh(textIndex*4.4+SD*0.21+1203));' +
            '[dx*dd*e/' + jzN(sz * 2.6) + '*100,(dy*dd*e+' + jzN(sz * 0.9) + '*q*q)/' + jzN(sz * 2.6) + '*100,0]');
        jzAnimator(L, 'JZ Out Spin', [['ADBE Text Rotation', 110]], Q + '(hh(textIndex*7.7+SD*0.33+1204)*2-1)*e*100');
        jzAnimator(L, 'JZ Out Shrink', [['ADBE Text Scale 3D', [55, 55, 100]]], Q + 'q*100');
        jzAnimator(L, 'JZ Out Fade', [['ADBE Text Opacity', 0]], Q + 'q>=1?100:x2sm(0.45,1,q)*100');
        return;
    }
    jzAnimator(L, 'JZ Out Shrink', [['ADBE Text Scale 3D', [45, 45, 100]]], Q + 'q*100');
    jzAnimator(L, 'JZ Out Fade', [['ADBE Text Opacity', 0]], Q + 'q>=1?100:x2sm(0.45,1,q)*100');
    var nm = ex2_nMask(L), perGlyph = nm === 0 && G.nv <= 40, fs = G.fs, eps = fs * 0.006;
    var hl = G.vert || G.nL < 2 ? fs * 0.85 : G.lead * 0.5, trk = ((ex2_td(L) || {}).tracking || 0) / 1000 * fs, gap = Math.max(0, trk) / 2 + fs * 0.02;
    var QD = [[-1, -1], [1, -1], [-1, 1], [1, 1]], lays = [L];
    for (q = 1; q < 4; q++) lays.push(ex2_copy(m, 'JZ Out Quarter ' + (q + 1), true, true));
    var r = G.rect, rcx = r.left + r.width / 2, rcy = r.top + r.height / 2, big = fs * 3;
    for (q = 0; q < 4; q++) {
        var Lx = lays[q], qx = QD[q][0], qy = QD[q][1], first = true;
        var gate = q === 0 ? H + 'PO<=0?1e4:0' : null;
        if (perGlyph) {
            for (i = 0; i < G.N; i++) {
                var g = G.g[i]; if (g.sp) continue;
                var gx0 = G.vert ? g.cx - fs * 0.7 : g.x0 - gap, gx1 = G.vert ? g.cx + fs * 0.7 : g.x1 + gap;
                var bx0 = qx < 0 ? gx0 : g.cx - eps, bx1 = qx < 0 ? g.cx + eps : gx1;
                var by0 = qy < 0 ? g.cy - hl : g.cy - eps, by1 = qy < 0 ? g.cy + eps : g.cy + hl;
                ex2_mask(Lx, ex2_box(bx0, by0, bx1, by1), false, 'JZ Out Quarter ' + (i + 1), first ? gate : null);
                first = false;
            }
        } else {
            ex2_mask(Lx, ex2_box(qx < 0 ? r.left - big : rcx - eps, qy < 0 ? r.top - big : rcy - eps, qx < 0 ? rcx + eps : r.left + r.width + big, qy < 0 ? rcy + eps : r.top + r.height + big),
                nm > 0, 'JZ Out Quarter', gate);
        }
        var dd = sz * 1.7 * (0.7 + 0.6 * ex2_r(sd, q, 1203)), jx = ex2_rs(sd, q, 1202) * 0.5, jy = ex2_rs(sd, q, 1205) * 0.5, d0 = 0.05 + ex2_r(sd, q, 1201) * 0.15;
        // thrown about the block centre: translation + a little spin and radial spread of the whole quarter set
        var TH = H + 'var q=cl((PO-' + jzN(d0) + ')/0.7),e=1-(1-q)*(1-q);', tf = jzEffect(Lx, 'ADBE Geometry2', 'JZ Out Throw');
        jzEP(tf, 1, [rcx, rcy]); jzEP(tf, 2, [rcx, rcy]);
        jzEX(tf, 2, TH + '[' + jzN(rcx) + '+' + jzN((qx + jx) * dd) + '*e,' + jzN(rcy) + '+' + jzN((qy + jy) * dd) + '*e+' + jzN(sz * 0.9) + '*q*q]');
        jzEX(tf, 4, TH + '100*(1+0.3*e)');
        jzEX(tf, 8, TH + jzN(ex2_rs(sd, q, 1204) * 24) + '*e');
    }
} });

// ================================================================ HOLDS
// Every hold scales by x2k = AMT x motionK, so it eases in after the entrance and fades out with any exit. Animator values are
// the largest amplitude (motionK = 1.6); the selector amount carries the live value.
var EX2_TAU = Math.PI * 2;
function ex2_idx(m) { return ex2_nGl(m) > 1 ? '(textIndex-1)' : jzN(m.o.mi || 0); }     // browser: n > 1 ? i : mi

/* ---- float \u2014 \u3075\u308F\u3075\u308F: every glyph bobs on its own soft sine + noise, drifting a touch sideways */
jzReg('hold', 'float', { apply: function (m) {
    var sz = ex2_sz(m), s = ex2_itSeed(m), A = sz * 0.075 * 1.6, ph = (s % 97) * 0.13;
    jzAnimator(m.L, 'JZ Hold Float', [['ADBE Text Position 3D', [A * 0.35, A * 1.15, 0]]], ex2_hd(m) + EX2_K +
        'var i=textIndex-1,x2f=x2k/1.6*100;[x2vn(time*0.6+i*0.31,SD+7)*x2f,(0.55*Math.sin(time*2.1+i*1.3+' + jzN(ph) + ')+0.6*x2vn(time*0.9+i*0.43,SD))/1.15*x2f,0]');
} });

/* ---- sway \u2014 \u3086\u3089\u304E: the line swings like a pendulum hanging from a point just above it */
jzReg('hold', 'sway', { apply: function (m) {
    var r = ex2_rect(m), s = ex2_itSeed(m), sz = ex2_sz(m);
    ex2_rotAt(m, 'x2sw', r.left + r.width / 2, r.top - sz * 0.6, EX2_K + 'var x2sw=Math.sin(time*' + jzN(EX2_TAU / 3.2) + '+' + (s % 7) + ')*2.6*x2k;');
} });

/* ---- pulse \u2014 \u8108\u52D5: a quick swell on every beat (0.5 s without a beat grid) */
jzReg('hold', 'pulse', { apply: function (m) {
    ex2_scaleC(m, 'x2pf', 'x2pf', EX2_K + 'var x2pf=1+0.055*Math.exp(-(time%0.5)*9)*x2k;');
} });

/* ---- shimmer \u2014 \u304D\u3089\u3081\u304D: glyphs twinkle in brightness on smooth noise; now and then one sparkles in the accent */
jzReg('hold', 'shimmer', { apply: function (m) {
    var acc = ex2_pick(m.ctx.sc.accent, m.ctx.sc.fg);
    var Q = ex2_hd(m) + EX2_K + 'var i=textIndex-1,x2tw=0.5+0.5*x2vn(time*3.2+i*1.7,SD),x2sp=hh(Math.floor(time*24)*1.37+i*7.7+SD*0.29+401)<0.03*x2k;';
    jzAnimator(m.L, 'JZ Hold Twinkle', [['ADBE Text Opacity', 0]], Q + '42*Math.min(1,x2k)*x2tw*x2tw');
    jzAnimator(m.L, 'JZ Hold Sparkle', [['ADBE Text Fill Color', jzHex(acc)], ['ADBE Text Scale 3D', [105, 105, 100]]], Q + 'x2sp?85:0');
} });

/* ---- colorRun \u2014 \u8272\u304C\u8D70\u308B: a soft accent highlight runs through the line again and again, lifting the glyphs it passes */
jzReg('hold', 'colorRun', { apply: function (m) {
    var acc = ex2_pick(m.ctx.sc.accent, m.ctx.sc.fg), sz = ex2_sz(m), per = ex2_cutN(m) + 5;
    var Q = ex2_hd(m) + 'var x2k2=AMT*Math.max(0,Math.min(1,M/0.7)),x2pos=((time*7)%' + per + '+' + per + ')%' + per + '-2.5,x2d=' + ex2_idx(m) + '-x2pos,x2wt=Math.exp(-x2d*x2d/1.6);x2wt=x2wt<0.02?0:x2wt*x2k2;';
    jzAnimator(m.L, 'JZ Hold Run Colour', [['ADBE Text Fill Color', jzHex(acc)]], Q + 'x2wt*90');
    jzAnimator(m.L, 'JZ Hold Run Lift', [['ADBE Text Position 3D', [0, -sz * 0.035, 0]]], Q + 'x2wt*100');
} });

/* ---- rotateSlow \u2014 \u3086\u3063\u304F\u308A\u56DE\u8EE2: the line slowly turns about its centre (up to 10 degrees) */
jzReg('hold', 'rotateSlow', { apply: function (m) {
    var r = ex2_rect(m), dir = ((ex2_itSeed(m) >>> 1) & 1) ? 1 : -1;
    ex2_rotAt(m, 'x2rs', r.left + r.width / 2, r.top + r.height / 2, EX2_K + 'var x2rs=' + dir + '*Math.min(10,time*2.6)*x2k;');
} });

/* ---- trackBreathe \u2014 \u5B57\u9593\u306E\u547C\u5438: letter spacing slowly opens and closes (Tracking; one glyph per line: glyph offsets) */
jzReg('hold', 'trackBreathe', { apply: function (m) {
    var L = m.L, W = 'var x2f=Math.sin(time*' + jzN(EX2_TAU / 2.8) + ')*x2k/1.6*100;';
    if (ex2_isVert(L)) {
        var td = ex2_td(L), fs = td ? td.fontSize : ex2_sz(m), lead = td && !td.autoLeading && td.leading > 0 ? td.leading : fs * 1.2, K = lead * 0.12 * 8;
        jzAnimator(L, 'JZ Hold Breathe', [['ADBE Text Position 3D', [0, K, 0]]], ex2_hd(m) + EX2_K + W + '[0,(textIndex-1-(textTotal-1)/2)*' + jzN(lead * 0.12) + '*x2f/' + jzN(K) + ',0]');
    } else jzAnimator(L, 'JZ Hold Breathe', [['ADBE Text Tracking Amount', 120]], ex2_hd(m) + EX2_K + W + 'x2f');
} });

/* ---- skewWobble \u2014 \u659C\u3081\u63FA\u308C: the line leans left and right */
jzReg('hold', 'skewWobble', { apply: function (m) {
    jzAnimator(m.L, 'JZ Hold Lean', [['ADBE Text Skew', 19.2]], ex2_hd(m) + EX2_K + 'Math.sin(time*' + jzN(EX2_TAU / 1.9) + ')*x2k/1.6*100');
} });

/* ---- beatHop \u2014 \u62CD\u3067\u8DF3\u306D\u308B: one glyph per beat (0.45 s) hops with a squash, its neighbours ripple */
jzReg('hold', 'beatHop', { apply: function (m) {
    var sz = ex2_sz(m), N = ex2_cutN(m);
    var Q = ex2_hd(m) + EX2_K + 'var x2u=(time%0.45)/(0.45*0.85),x2b=Math.floor(time/0.45),x2c=((x2b%' + N + ')+' + N + ')%' + N +
        ',x2h=x2u<1?Math.pow(Math.sin(Math.PI*x2u),0.7):0,x2dd=Math.abs(' + ex2_idx(m) + '-x2c),x2a=x2h*(x2dd==0?1:(x2dd==1?0.3:0))*x2k/1.6*100;';
    jzAnimator(m.L, 'JZ Hold Hop', [['ADBE Text Position 3D', [0, -sz * 0.2 * 1.6, 0]], ['ADBE Text Scale 3D', [92, 112.8, 100]]], Q + 'x2a');
} });

/* ---- hWave \u2014 \u6A2A\u6CE2: a sideways wave runs along the line, glyphs leaning with it */
jzReg('hold', 'hWave', { apply: function (m) {
    var sz = ex2_sz(m), Q = ex2_hd(m) + EX2_K + 'var x2a=time*' + jzN(EX2_TAU * 0.55) + '-(textIndex-1)*0.9,x2f=x2k/1.6*100;';
    jzAnimator(m.L, 'JZ Hold Wave X', [['ADBE Text Position 3D', [sz * 0.085 * 1.6, 0, 0]]], Q + 'Math.sin(x2a)*x2f');
    jzAnimator(m.L, 'JZ Hold Wave Lean', [['ADBE Text Skew', -9 * 1.6]], Q + 'Math.cos(x2a)*x2f');
} });

/* ---- heartbeat \u2014 \u9F13\u52D5: a double thump (lub-dub) every 1.05 s, flushing toward the accent */
jzReg('hold', 'heartbeat', { apply: function (m) {
    var acc = ex2_pick(m.ctx.sc.accent, m.ctx.sc.fg), V = 'var x2u=time%1.05,x2v=Math.exp(-x2u*9)+(x2u>0.2?0.75*Math.exp(-(x2u-0.2)*9):0);';
    ex2_scaleC(m, 'x2hf', 'x2hf', EX2_K + V + 'var x2hf=1+0.055*x2v*x2k;');
    jzAnimator(m.L, 'JZ Hold Flush', [['ADBE Text Fill Color', jzHex(acc)]], ex2_hd(m) + EX2_K + V + 'Math.min(85,60*x2v*Math.min(1,x2k))');
} });

/* ---- orbitSmall \u2014 \u5C0F\u3055\u306A\u5186\u904B\u52D5: every glyph circles a little, each a step behind the previous one */
jzReg('hold', 'orbitSmall', { apply: function (m) {
    var sz = ex2_sz(m), R = sz * 0.05 * 1.6, dir = ((ex2_itSeed(m) >>> 2) & 1) ? 1 : -1;
    jzAnimator(m.L, 'JZ Hold Orbit', [['ADBE Text Position 3D', [R, R, 0]]], ex2_hd(m) + EX2_K +
        'var x2a=time*' + jzN(EX2_TAU / 1.7 * dir) + '+(textIndex-1)*0.9,x2f=x2k/1.6*100;[Math.cos(x2a)*x2f,Math.sin(x2a)*x2f,0]');
} });

/* ---- jelly \u2014 \u30BC\u30EA\u30FC: glyphs squash and stretch on their baseline in a travelling wave */
jzReg('hold', 'jelly', { apply: function (m) {
    var td = ex2_td(m.L), fs = td ? td.fontSize : ex2_sz(m);
    jzAnimator(m.L, 'JZ Hold Jelly', [['ADBE Text Scale 3D', [116, 84, 100]], ['ADBE Text Position 3D', [0, fs * 0.05 * 1.6, 0]]], ex2_hd(m) + EX2_K +
        'Math.sin(time*' + jzN(EX2_TAU * 1.1) + '-(textIndex-1)*0.55)*x2k/1.6*100');
} });

/* ---- scanBand \u2014 \u8D70\u67FB\u5E2F: every 2.2 s a band scans down the line; inside it the text is shifted sideways, an accent hairline
   marks its lower edge. The band = a linked copy shown through two masks (moved by mask expansion), cut out of the lyric by
   the same two masks; only while holding (not during the entrance / exit). */
jzReg('hold', 'scanBand', { apply: function (m) {
    var L = m.L, r = ex2_rect(m), sz = ex2_sz(m), H = ex2_hd(m), acc = ex2_pick(m.ctx.sc.accent, m.ctx.sc.fg);
    var pad = sz * 0.1, y0 = r.top - pad, h = r.height + pad * 2, bh = Math.max(sz * 0.24, h * 0.12), X0 = r.left - pad, X1 = r.left + r.width + pad;
    var BX = (h + bh) * 2 + sz, BU = (h + bh) * 4 + sz, lw = Math.max(1, sz * 0.012);
    var V = H + EX2_K + 'var x2c=Math.floor(time/2.2),x2v=(time-x2c*2.2)/1.54,x2on=x2v<1&&x2k>=0.01&&PO<=0;if(!x2on)x2v=-1.5;var x2e=' + jzN(h + bh) + '*x2v;';
    var above = null;
    if (ex2_nMask(L) === 0 && !ex2_matted(L) && !ex2_crowded(m, 12)) {
        var D = ex2_copy(m, 'JZ Hold Band', true, false);
        jzSetExpr(jzXf(D, 'ADBE Position'), V + 'var a=parent.transform.anchorPoint;[a[0]+' + jzN(sz * 0.075) + '*x2k*(hh(x2c*1.7+SD*0.3+451)<0.5?1:-1),a[1]]');
        var lo = ex2_box(X0 - BX, y0 - BU, X1 + BX, y0), hi = ex2_box(X0 - BX, y0 - BU - bh, X1 + BX, y0 - bh);
        ex2_mask(D, lo, false, 'JZ Band Bottom', V + 'x2e', MaskMode.ADD);
        ex2_mask(D, hi, false, 'JZ Band Top', V + 'x2e', MaskMode.SUBTRACT);
        ex2_mask(L, lo, false, 'JZ Band Cut', V + 'x2e', MaskMode.SUBTRACT);
        ex2_mask(L, hi, false, 'JZ Band Keep', V + 'x2e', MaskMode.ADD);
        above = D;
    }
    var S = ex2_shape(m, 'JZ Hold Scan', above, false, false), g = jzGrp(S, 'scan'), rc = jzAddRect(g, X1 - X0, lw, 0);
    jzSetExpr(rc.property('ADBE Vector Rect Position'), V + '[' + jzN((X0 + X1) / 2) + ',' + jzN(y0 - lw / 2) + '+x2e]');
    jzAddFill(g, acc);        // after the rect is set up (adding the fill invalidates rc in AE)
    jzNoGhost(S);
    jzSetExpr(jzXf(S, 'ADBE Opacity'), V + 'x2on?55*Math.min(1,x2k):0');
} });

/* ---- noiseDrift \u2014 \u30CE\u30A4\u30BA\u6F02\u6D41: glyphs wander and turn slowly on smooth noise */
jzReg('hold', 'noiseDrift', { apply: function (m) {
    var sz = ex2_sz(m), A = sz * 0.075 * 1.6, Q = ex2_hd(m) + EX2_K + 'var x2t=time*0.35,i=textIndex-1,x2f=x2k/1.6*100;';
    jzAnimator(m.L, 'JZ Hold Drift', [['ADBE Text Position 3D', [A, A, 0]]], Q + '[x2vn(x2t+i*1.7,SD+11)*x2f,x2vn(x2t+i*2.3,SD+23)*x2f,0]');
    jzAnimator(m.L, 'JZ Hold Drift Turn', [['ADBE Text Rotation', 7 * 1.6]], Q + 'x2vn(x2t*1.3+i*0.9,SD+37)*x2f');
} });

/* ---- tilt \u2014 \u30B7\u30FC\u30BD\u30FC: the baseline seesaws about the centre while the glyphs stay upright (layer rotation + per-glyph counter-rotation) */
jzReg('hold', 'tilt', { apply: function (m) {
    var r = ex2_rect(m), pre = EX2_K + 'var x2t=Math.sin(time*' + jzN(EX2_TAU / 4.2) + '+' + (ex2_itSeed(m) % 5) + ')*4*x2k;';
    ex2_rotAt(m, 'x2t', r.left + r.width / 2, r.top + r.height / 2, pre);
    jzAnimator(m.L, 'JZ Hold Upright', [['ADBE Text Rotation', -10]], m.HD + pre + 'x2t*10');
} });

/* ---- zoomSlow \u2014 \u3058\u308F\u5BC4\u308A: a slow push-in over the whole cut (up to +7 %) */
jzReg('hold', 'zoomSlow', { apply: function (m) {
    ex2_scaleC(m, 'x2z', 'x2z', EX2_K + 'var x2zu=cl(time/DUR),x2z=1+0.07*(1-(1-x2zu)*(1-x2zu))*x2k;');
} });

/* ---- stretchPulse \u2014 \u6A2A\u4F38\u3073\u62CD: on every beat (0.5 s) the line kicks wider and a little flatter */
jzReg('hold', 'stretchPulse', { apply: function (m) {
    ex2_scaleC(m, '(1+0.12*x2kk)', '(1-0.045*x2kk)', EX2_K + 'var x2kk=Math.exp(-(time%0.5)*8)*x2k;');
} });

/* ---- glitchJump \u2014 \u6642\u3005\u305A\u308C\u308B: every third of a second there may be a short glitch: the line jumps sideways for a few frames,
   on the first frame it is sliced into shifted bands and may flash the ghost colour */
jzReg('hold', 'glitchJump', { apply: function (m) {
    var L = m.L, sz = ex2_sz(m), H = ex2_hd(m), k = ex2_scaleK(L), gh = ex2_pick(m.ctx.sc.ghostA, m.ctx.sc.accent, m.ctx.sc.fg);
    var S = EX2_K + 'var x2s=Math.floor(time*24),x2l=Math.floor(x2s/8),x2p=x2s-x2l*8,x2on=x2k>=0.01&&x2p<=2&&hh(x2l*3.17+SD*0.37+501)<=0.4+0.3*G;';
    m.parts.pos.push(S + 'if(x2on)d=[d[0]+(hh(x2l*5.31+SD*0.53+502)*2-1)*' + jzN(sz * 0.2 * k[0]) + '*x2k,d[1]+(hh(x2l*6.7+SD*0.61+503)*2-1)*' + jzN(sz * 0.04 * k[1]) + '*x2k];');
    var ww = jzEffect(L, 'ADBE Wave Warp', 'JZ Hold Glitch Bands');
    jzEP(ww, 1, 2); jzEP(ww, 4, 0); jzEP(ww, 5, 0); jzEP(ww, 6, 1);
    jzEX(ww, 2, H + S + 'x2on&&x2p==0?(hh(x2l*7.3+SD*0.7+505)*2-1)*' + jzN(sz * 0.12) + '*x2k:0');
    jzEX(ww, 3, H + S + jzN(sz * 0.3) + '+hh(x2l*8.9+SD*0.8+504)*' + jzN(sz * 0.6));
    jzEX(ww, 7, H + S + 'hh(x2l*9.7+SD*0.9+507)*360');
    jzAnimator(L, 'JZ Hold Glitch Colour', [['ADBE Text Fill Color', jzHex(gh)]], H + S + 'x2on&&x2p==0&&hh(x2l*4.1+SD*0.2+506)<0.5?100:0');
} });

/* ---- echoTrail \u2014 \u6B8B\u50CF\u3092\u5F15\u304F: the line drifts on a slow Lissajous path trailing three fading echoes in the sub colour
   (a linked copy below the lyric with the Echo effect; the drift is a text animator so the echoes see it) */
jzReg('hold', 'echoTrail', { apply: function (m) {
    var L = m.L, sz = ex2_sz(m), H = ex2_hd(m), s = ex2_itSeed(m), w = EX2_TAU / 3.4, ph = (s % 100) * 0.1, Ax = sz * 0.09 * 1.6, Ay = sz * 0.05 * 1.6;
    jzAnimator(L, 'JZ Hold Echo Drift', [['ADBE Text Position 3D', [Ax, Ay, 0]]], H + EX2_K +
        'var x2f=x2k/1.6*100;[Math.sin(' + jzN(w) + '*time+' + jzN(ph) + ')*x2f,Math.sin(' + jzN(w * 1.37) + '*time+' + jzN(ph * 1.3) + ')*x2f,0]');
    if (ex2_matted(L) || ex2_crowded(m, 10)) return;
    var D = ex2_copy(m, 'JZ Hold Echo', false, false), col = ex2_pick(m.ctx.sc.sub, jzTextColor(L));
    try { D.moveAfter(L); } catch (e0) {}
    jzTextDoc(D, function (d) {
        try { if (d.applyFill !== false) d.fillColor = jzHex(col); } catch (e1) {}
        try { if (d.applyStroke) d.strokeColor = jzHex(col); } catch (e2) {}
    });
    jzSetExpr(jzXf(D, 'ADBE Opacity'), H + EX2_K + 'parent.transform.opacity*0.55*Math.min(1,x2k)*Math.max(0,1-PO*4)');
    var ec = jzEffect(D, 'ADBE Echo', 'JZ Hold Echo');
    jzEP(ec, 1, -0.3); jzEP(ec, 2, 3); jzEP(ec, 3, 1); jzEP(ec, 4, 0.62); jzEP(ec, 5, 5);
} });

// ================================================================ pack exitB part 1 (AE port of src/11p_exitB.js)
// exits: peelOff crumpleOut tearOut scorchOut overexposeOut scanOut stripesOut halftoneOut eraserOut vacuumOut sandOut shredOut
//        dominoOut hingeOut
// holds: glowFlicker windGust dangle eqBounce flashBox glintSweep flipSwap shadowSway magnetJiggle typeRattle focusRack pluckString
// Exits run on PO (0 at the exit start, 1 at the cut end); holds scale their motion by AMT \u00D7 fx.motion (browser motionK).
// Clips with a MOVING edge (peel line, burning front, stripes, halftone dots, eraser lanes) = a linked copy of the lyric ("twin":
// duplicate parented to L, takes L's final text style, visible only during the exit while L hides) seen through a shape-layer
// alpha track matte parented to L. Clips that stay in place in the layer (torn halves, shredder slot, scan regions) are masks on L
// itself (axis-aligned rects driven by mask expansion), so treatment copies of L keep them. Helper graphics (rims, scan line,
// eraser block, pins, dots, streaks) are shape layers parented to L (comp space when the browser draws them screen-fixed),
// marked jzNoGhost and alive only while they are needed.

// ---------------------------------------------------------------- shared helpers (xb1_*)
// easings not in JZ_FNS: o4 outQuart, io3 inOutCubic, ios inOutSine, sm smoothstep(a, b, x), win = the browser's win(p, o, spread)
var XB1_FNS = 'function o4(x){x=cl(x);return 1-Math.pow(1-x,4);}function io3(x){x=cl(x);return x<0.5?4*x*x*x:1-Math.pow(-2*x+2,3)/2;}' +
    'function ios(x){x=cl(x);return -(Math.cos(Math.PI*x)-1)/2;}function sm(a,b,x){x=cl((x-a)/(b-a));return x*x*(3-2*x);}' +
    'function win(p,o,s){return cl((p-o*s)/(1-s));}\n';
// hold strength: HK = AMT \u00D7 motionK (0..1.6), HKK = min(1, HK)
var XB1_MK = 'var MK=Math.min(1.6,Math.max(0,M/0.7)),HK=AMT*MK,HKK=Math.min(1,HK);\n';
// the lyric's real font size (layer units): layouts fit the text after asking for a size, so m.size can differ from it
function xb1_sz(m) { var f = 0; try { f = jzFontSize(m.L); } catch (e) {} return f > 1 ? f : (m.size || 100); }
function xb1_hd(m) { return m.HD + XB1_FNS + 'var SZ=' + jzN(xb1_sz(m)) + ';\n'; }
function xb1_T(m) { var c = m.c; return { IN: Math.max(0.02, c.inDur || 0.3), DL: (m.o.mi || 0) * (c.stagger || 0.04), OS: c.dur - (c.outDur || 0), DUR: c.dur }; }
// measuring time: entrance done, hold barely started
function xb1_tm(m) { var t = xb1_T(m); return Math.max(0, Math.min(t.OS - 0.01, t.DL + t.IN + 0.03)); }
function xb1_n(x) { return x < 0 ? '(' + jzN(x) + ')' : jzN(x); }
function xb1_arr(a) { var s = [], i; for (i = 0; i < a.length; i++) s.push(jzN(a[i])); return '[' + s.join(',') + ']'; }
function xb1_isHex(c) { return typeof c === 'string' && c.charAt(0) === '#' && (c.length === 7 || c.length === 4); }
function xb1_pick(a, b, c) { if (xb1_isHex(a)) return a; if (xb1_isHex(b)) return b; if (xb1_isHex(c)) return c; return '#FFFFFF'; }
function xb1_col(m) {
    var sc = m.ctx.sc, bg = xb1_pick(sc.bg, '#000000');
    return { bg: bg, acc: xb1_pick(sc.accent, sc.fg), ink: xb1_pick(sc.ink, sc.fg), c0: jzTextColor(m.L), dark: jzLum(bg) < 0.5, sc: sc };
}
// the browser's J.h / J.r / J.rs (seeded choices match the web version)
function xb1_h(a, b, c, d, e) {
    var h = 0x9e3779b9 ^ (a | 0);
    h = jzImul(h ^ (h >>> 16), 0x85ebca6b);
    h = (h + jzImul((b | 0) + 0x632be5ab, 0xc2b2ae35)) | 0;
    h = jzImul(h ^ (h >>> 13), 0xc2b2ae35);
    h = (h + jzImul((c | 0) + 0x5bd1e995, 0x27d4eb2f)) | 0;
    h = jzImul(h ^ (h >>> 15), 0x165667b1);
    h = (h + jzImul((d | 0) + 0x1b873593, 0x85ebca6b)) | 0;
    h = jzImul(h ^ (h >>> 16), 0x27d4eb2f);
    h = (h + jzImul((e | 0) + 0x68e31da4, 0x9e3779b1)) | 0;
    h ^= h >>> 15; h = jzImul(h, 0x2c1b3c6d); h ^= h >>> 12; h = jzImul(h, 0x297a2d39); h ^= h >>> 15;
    return h >>> 0;
}
function xb1_r(a, b, c, d) { return xb1_h(a, b, c, d) / 4294967296; }
function xb1_rs(a, b, c, d) { return xb1_r(a, b, c, d) * 2 - 1; }
function xb1_cutBit(m, k) { return (xb1_h(m.c.seed | 0, k, 1991) & 1) === 1; }
function xb1_noise(x, seed) { var i = Math.floor(x), f = x - i, a = xb1_rs(seed, i, 7), b = xb1_rs(seed, i + 1, 7), u = f * f * (3 - 2 * f); return a + (b - a) * u; }
function xb1_ios(x) { x = jzClamp(x, 0, 1); return -(Math.cos(Math.PI * x) - 1) / 2; }
function xb1_cutN(m) { return Math.max(1, jzCount(String(m.c.text || ''))); }
function xb1_ord(m) { var N = xb1_cutN(m); return N > 1 ? jzClamp((m.o.mi || 0) / (N - 1), 0, 1) : 0; }
function xb1_isSp(ch) { return ch === ' ' || ch === '\u3000' || ch === '\t'; }
function xb1_adv(ch) { var c = ch.charCodeAt(0); if (ch === ' ') return 0.3; if (c >= 0xFF61 && c <= 0xFF9F) return 0.5; return c < 0x2000 ? 0.56 : 1; }
function xb1_rect(L, t) { try { var r = L.sourceRectAtTime(t, false); if (r && r.width > 0 && r.height > 0) return r; } catch (e) {} return jzRect(L); }
// glyph geometry in the text layer's own space (textIndex order, spaces included):
//   G.g[i] {i, li, ci, nl (chars in the line), sp, k, x, y (centre), w}; G.r source rect; G.fs; G.vert (one glyph per line); G.n; G.nk
function xb1_geo(m) {
    var L = m.L, TP = L.property('ADBE Text Properties'), td = TP.property('ADBE Text Document').value, r = xb1_rect(L, xb1_tm(m)), i, j;
    var fs = td.fontSize || xb1_sz(m), text = String(td.text), trk = (td.tracking || 0) / 1000 * fs;
    var lines = text.split(/\r\n|\r|\n|\u0003/), nL = lines.length, lead = fs * 1.2;
    try { if (!td.autoLeading && td.leading > 0) lead = td.leading; } catch (e0) {}
    var just = 'c';
    try { if (td.justification === ParagraphJustification.LEFT_JUSTIFY) just = 'l'; else if (td.justification === ParagraphJustification.RIGHT_JUSTIFY) just = 'r'; } catch (e1) {}
    var G = { fs: fs, lead: lead, nL: nL, r: r, n: 0, nk: 0, g: [], vert: nL > 1 }, rows = [], maxW = 0;
    for (i = 0; i < nL; i++) {
        var cs = jzChars(lines[i]), ws = [], w = 0;
        for (j = 0; j < cs.length; j++) { var a = xb1_adv(cs[j]) * fs; ws.push(a); w += a + (j < cs.length - 1 ? trk : 0); }
        if (cs.length > 1) G.vert = false;
        rows.push({ cs: cs, ws: ws, w: w }); if (w > maxW) maxW = w;
    }
    var k = maxW > 0 ? r.width / maxW : 1; if (!(k > 0.5 && k < 2)) k = 1;
    var lh = r.height - (nL - 1) * lead; if (!(lh > fs * 0.5 && lh < fs * 1.6)) lh = fs * 0.95;
    var cx = r.left + r.width / 2;
    for (i = 0; i < nL; i++) {
        var R = rows[i], x = just === 'l' ? r.left : (just === 'r' ? r.left + r.width - R.w * k : cx - R.w * k / 2), y = r.top + lh / 2 + i * lead;
        for (j = 0; j < R.cs.length; j++) {
            var sp = xb1_isSp(R.cs[j]);
            G.g.push({ i: G.n, li: i, ci: j, nl: R.cs.length, sp: sp, k: sp ? -1 : G.nk, x: x + R.ws[j] * k / 2, y: y, w: Math.max(1, R.ws[j] * k) });
            x += (R.ws[j] + trk) * k; G.n++;
            if (!sp) G.nk++;
        }
    }
    G.cx = cx; G.cy = r.top + r.height / 2;
    return G;
}
function xb1_single(m, G) { return G.nk === 1 && xb1_cutN(m) > 1; }
// the lyric's main (opaque, filled) copy: helper graphics only go there
function xb1_main(m) {
    try {
        var td = m.L.property('ADBE Text Properties').property('ADBE Text Document').value;
        if (td.applyFill === false) return false;
        return jzXf(m.L, 'ADBE Opacity').value >= 85;
    } catch (e) { return true; }
}
// too many layers already (one glyph per layer with a long line, very long lines, crowded comps): no copies
function xb1_crowded(m, G) {
    if (G && (G.n > 60 || (xb1_single(m, G) && xb1_cutN(m) > 8))) return true;
    try { return m.ctx.comp.numLayers > 70; } catch (e) { return false; }
}
// static transform of a layer and conversions between its own space and comp space
function xb1_xf(L) { var tr = L.property('ADBE Transform Group'); return { a: tr.property('ADBE Anchor Point').value, p: tr.property('ADBE Position').value, s: tr.property('ADBE Scale').value, r: tr.property('ADBE Rotate Z').value }; }
function xb1_s0(L) { var s = xb1_xf(L).s, k = (Math.abs(s[0]) + Math.abs(s[1])) / 200; return k > 0.01 ? k : 1; }
function xb1_toComp(L, x, y) {
    var X = xb1_xf(L), rr = X.r * Math.PI / 180, ux = (x - X.a[0]) * X.s[0] / 100, uy = (y - X.a[1]) * X.s[1] / 100;
    return [X.p[0] + Math.cos(rr) * ux - Math.sin(rr) * uy, X.p[1] + Math.sin(rr) * ux + Math.cos(rr) * uy];
}
function xb1_fromComp(L, x, y) {
    var X = xb1_xf(L), rr = -X.r * Math.PI / 180, ux = x - X.p[0], uy = y - X.p[1];
    var vx = Math.cos(rr) * ux - Math.sin(rr) * uy, vy = Math.sin(rr) * ux + Math.cos(rr) * uy;
    return [X.a[0] + vx / ((X.s[0] / 100) || 1), X.a[1] + vy / ((X.s[1] / 100) || 1)];
}
// keep the point (px, py) of L's own space fixed while L is scaled by [fx, fy] and turned by rot degrees (expressions) -> parts.pos
function xb1_pivot(m, fx, fy, rot, px, py) {
    var X = xb1_xf(m.L), ux = (px - X.a[0]) * X.s[0] / 100, uy = (py - X.a[1]) * X.s[1] / 100;
    if (Math.abs(ux) + Math.abs(uy) < 0.5) return '';
    var rr = X.r * Math.PI / 180, vx = Math.cos(rr) * ux - Math.sin(rr) * uy, vy = Math.sin(rr) * ux + Math.cos(rr) * uy;
    return 'var xp_a=(' + jzN(X.r) + '+(' + rot + '))*Math.PI/180,xp_c=Math.cos(xp_a),xp_s=Math.sin(xp_a),xp_x=(' + fx + ')*' + jzN(ux) + ',xp_y=(' + fy + ')*' + jzN(uy) + ';' +
        'd=[d[0]+' + jzN(vx) + '-(xp_c*xp_x-xp_s*xp_y),d[1]+' + jzN(vy) + '-(xp_s*xp_x+xp_c*xp_y)];';
}
// rigid motion of L: extra rotation rotEx (deg) about the comp point Q, then a screen offset [ox, oy] (expressions)
function xb1_rigid(m, pre, rotEx, Q, off) {
    m.parts.rot.push(pre + 'r+=(' + rotEx + ');');
    m.parts.pos.push(pre + 'var xr_a=(' + rotEx + ')*Math.PI/180,xr_c=Math.cos(xr_a),xr_s=Math.sin(xr_a),xr_x=value[0]-' + xb1_n(Q[0]) + ',xr_y=value[1]-' + xb1_n(Q[1]) + ';' +
        'd=[d[0]+xr_c*xr_x-xr_s*xr_y-xr_x+(' + off[0] + '),d[1]+xr_s*xr_x+xr_c*xr_y-xr_y+(' + off[1] + ')];');
}
function xb1_matted(L) { try { return !!L.trackMatteType && L.trackMatteType !== TrackMatteType.NO_TRACK_MATTE; } catch (e) { return false; } }
// the layer new helper layers go above (above the lyric's own track matte when it has one)
function xb1_top(m) { var L = m.L; if (xb1_matted(L)) { try { return m.ctx.comp.layer(L.index - 1); } catch (e) {} } return L; }
function xb1_live(m, Lx, hold) { if (hold) return; var t = xb1_T(m); try { if (t.OS - 0.05 > Lx.inPoint) Lx.inPoint = t.OS - 0.05; } catch (e) {} }
// linked copy of the lyric: parented to L, takes its final text style, opacity = value \u00D7 fac (expression, header + pre)
function xb1_twin(m, tag, pre, fac, above, hold) {
    var L = m.L, top = above || xb1_top(m), D = L.duplicate(), i, mns = ['ADBE Position', 'ADBE Scale', 'ADBE Rotate Z', 'ADBE Opacity', 'ADBE Anchor Point'];
    try { D.name = String(L.name).substr(0, 20) + ' ' + tag; } catch (e0) {}
    var tr = D.property('ADBE Transform Group');
    for (i = 0; i < mns.length; i++) { try { if (tr.property(mns[i]).expression) tr.property(mns[i]).expression = ''; } catch (e1) {} }
    if (xb1_matted(D) && typeof D.setTrackMatte !== 'function') { try { D.trackMatteType = TrackMatteType.NO_TRACK_MATTE; } catch (e2) {} }
    D.parent = L;
    tr.property('ADBE Scale').setValue([100, 100]); tr.property('ADBE Rotate Z').setValue(0);
    jzSetExpr(tr.property('ADBE Position'), 'parent.transform.anchorPoint');
    jzSetExpr(tr.property('ADBE Opacity'), xb1_hd(m) + (hold ? XB1_MK : '') + (pre || '') + 'value*(' + fac + ')');
    try { D.property('ADBE Text Properties').property('ADBE Text Document').expression = 'parent.text.sourceText.style'; } catch (e3) {}
    xb1_live(m, D, hold);
    try { D.moveBefore(top); } catch (e4) {}
    return D;
}
function xb1_reset(S) {
    var tr = S.property('ADBE Transform Group');
    tr.property('ADBE Anchor Point').setValue([0, 0]); tr.property('ADBE Position').setValue([0, 0]);
    tr.property('ADBE Scale').setValue([100, 100]); tr.property('ADBE Rotate Z').setValue(0);
}
// shape layer in L's own space (parented to L): above `above` (default: above L / its matte) or right below L
function xb1_shape(m, name, above, below, hold) {
    var L = m.L, S = m.ctx.comp.layers.addShape();
    try { S.name = name; } catch (e0) {}
    try { if (below) S.moveAfter(L); else S.moveBefore(above || xb1_top(m)); } catch (e1) {}
    S.parent = L; xb1_reset(S); xb1_live(m, S, hold); jzNoGhost(S);
    return S;
}
// shape layer in comp space (graphics the browser draws fixed on the screen)
function xb1_cshape(m, name, above, hold) {
    var S = m.ctx.comp.layers.addShape();
    try { S.name = name; } catch (e0) {}
    try { S.moveBefore(above || xb1_top(m)); } catch (e1) {}
    xb1_reset(S); xb1_live(m, S, hold); jzNoGhost(S);
    return S;
}
// alpha (or inverted) track matte shape layer for the twin T, in L's space, right above T
function xb1_matteShape(m, T, name, inv, hold) {
    var S = m.ctx.comp.layers.addShape(), tp = inv ? TrackMatteType.ALPHA_INVERTED : TrackMatteType.ALPHA, ok = false;
    try { S.name = name; } catch (e0) {}
    try { S.moveBefore(T); } catch (e1) {}
    S.parent = m.L; xb1_reset(S); xb1_live(m, S, hold);
    if (typeof T.setTrackMatte === 'function') { try { T.setTrackMatte(S, tp); ok = true; } catch (e2) { ok = false; } }
    if (!ok) { try { T.trackMatteType = tp; } catch (e3) { jzWarn('xb1 matte: ' + e3.toString()); } }
    try { S.enabled = false; } catch (e4) {}
    return S;
}
// masks (layer space). 'sub' = subtract; 'add' = add when it is the layer's first active mask, else intersect
function xb1_nMasks(L) {
    var n = 0, i;
    try { var mp = L.property('ADBE Mask Parade'); for (i = 1; i <= mp.numProperties; i++) if (mp.property(i).maskMode !== MaskMode.NONE) n++; } catch (e) {}
    return n;
}
function xb1_mask(Lx, pts, mode, name) {
    var prior = xb1_nMasks(Lx), mk = Lx.property('ADBE Mask Parade').addProperty('ADBE Mask Atom'), sh = new Shape();
    sh.vertices = pts; sh.closed = true; mk.property('ADBE Mask Shape').setValue(sh);
    try { mk.maskMode = mode === 'sub' ? MaskMode.SUBTRACT : (prior ? MaskMode.INTERSECT : MaskMode.ADD); } catch (e) {}
    try { if (name) mk.name = name; } catch (e2) {}
    return mk;
}
function xb1_rpts(x0, y0, x1, y1) { return [[x1, y0], [x1, y1], [x0, y1], [x0, y0]]; }
// axis-aligned wipe on L (fallback without copies): the part behind an edge moving along +axis (sg 1) or -axis (sg -1) is removed
function xb1_wipe(m, r, axis, sg, prog, HD, feather) {
    var sz = xb1_sz(m), big = (r.width + r.height) * 4 + sz * 10, x0 = r.left - sz * 0.3, x1 = r.left + r.width + sz * 0.3, y0 = r.top - sz * 0.3, y1 = r.top + r.height + sz * 0.3, pts, ext;
    if (axis === 'x') { ext = x1 - x0; pts = sg > 0 ? xb1_rpts(x0 - big, y0 - big, x0, y1 + big) : xb1_rpts(x1, y0 - big, x1 + big, y1 + big); }
    else { ext = y1 - y0; pts = sg > 0 ? xb1_rpts(x0 - big, y0 - big, x1 + big, y0) : xb1_rpts(x0 - big, y1, x1 + big, y1 + big); }
    var mk = xb1_mask(m.L, pts, 'sub', 'JZ Out Wipe');
    jzSetExpr(mk.property('ADBE Mask Offset'), HD + 'Math.max(0,(' + prog + '))*' + jzN(ext + (feather || 0)));
    jzSetExpr(mk.property('ADBE Mask Opacity'), HD + '(' + prog + ')>0?100:0');
    if (feather) mk.property('ADBE Mask Feather').setValue([feather, feather]);
    return mk;
}
// L hides while its copies play the exit (PO >= 0.998: everything is gone, like the browser's safety net)
function xb1_hideL(m, pre, cond) { m.parts.op.push(XB1_FNS + (pre || '') + 'f*=(' + (cond || 'PO>0') + ')||PO>=0.998?0:1;'); }
function xb1_safe(m, pre, fac) { m.parts.op.push(XB1_FNS + (pre || '') + 'f*=PO>=0.998?0:(' + (fac || '1') + ');'); }

/* ================================================================ EXITS: paper */

/* ---- peelOff \u2014 \u30B9\u30C6\u30C3\u30AB\u30FC\u5265\u304C\u3057: a fold line crosses the text diagonally; the peeled part is its mirror image (the sticker's back,
   lifted with a shadow) folded over the stuck part; at the end the flap flies off. Stuck part and flap = twins through half-plane
   mattes that ride with the fold line; the flap is reflected about the fold line (rotation 2\u03B1 \u00B7 scale (1, \u22121)). */
jzReg('exit', 'peelOff', { apply: function (m) {
    var L = m.L, G = xb1_geo(m), r = G.r, sz = xb1_sz(m), C = xb1_col(m), i;
    var v = (xb1_h(m.c.seed | 0, 311) >>> 5) & 3, dx = [0.83, -0.83, 0.83, -0.83][v], dy = [0.56, 0.56, -0.56, -0.56][v], nl = Math.sqrt(dx * dx + dy * dy);
    dx /= nl; dy /= nl;
    var tx = -dy, ty = dx, pad = sz * 0.06, bx0 = r.left - pad, by0 = r.top - pad, bx1 = r.left + r.width + pad, by1 = r.top + r.height + pad;
    var cs = [[bx0, by0], [bx1, by0], [bx0, by1], [bx1, by1]], u0 = 1e9, u1 = -1e9;
    for (i = 0; i < 4; i++) { var uu = cs[i][0] * dx + cs[i][1] * dy; if (uu < u0) u0 = uu; if (uu > u1) u1 = uu; }
    var tc = (bx0 + bx1) / 2 * tx + (by0 + by1) / 2 * ty;
    var ev = 'var pq=' + (xb1_single(m, G) ? 'win(PO,' + jzN(xb1_ord(m)) + ',0.4)' : 'PO') + ',pf=cl(pq/0.74),pfly=cl((pq-0.74)/0.26),' +
        'ps=' + jzN(u0) + '+' + jzN(u1 - u0) + '*(0.1*pf+0.9*pf*pf*(3-2*pf)),px0=ps*' + jzN(dx) + '+' + jzN(tc * tx) + ',py0=ps*' + jzN(dy) + '+' + jzN(tc * ty) + ';';
    var HD = xb1_hd(m) + ev;
    if (xb1_crowded(m, G)) {       // no copies: a straight wipe along the fold's main axis
        var ax = Math.abs(dx) > Math.abs(dy) ? 'x' : 'y';
        xb1_wipe(m, r, ax, ax === 'x' ? (dx > 0 ? 1 : -1) : (dy > 0 ? 1 : -1), 'pf*1.02', HD, 0);
        xb1_safe(m, ev, 'pf>=1?0:1');
        return;
    }
    xb1_hideL(m, ev, 'pq>0');
    var BIG = (r.width + r.height) * 3 + sz * 12, ang = Math.atan2(dy, dx) * 180 / Math.PI;
    var half = function (S, name) {
        var g = jzGrp(S, name); jzAddRect(g, BIG, BIG, 0); jzAddFill(g, '#FFFFFF');
        jzGX(g).property('ADBE Vector Rotation').setValue(ang);
        jzSetExpr(jzGX(g).property('ADBE Vector Position'), HD + '[px0+' + jzN(BIG / 2 * dx) + ',py0+' + jzN(BIG / 2 * dy) + ']');
    };
    var S = xb1_twin(m, 'JZ Out Stuck', ev, 'pq>0&&pf<1?1:0');
    var MS = xb1_matteShape(m, S, 'JZ Out Stuck Matte');
    half(MS, 'stuck');
    var F = xb1_twin(m, 'JZ Out Flap', ev, 'pq>0?1-iq(pfly):0', MS);
    jzNoGhost(F);                  // the browser draws the flap on the main pass only
    var MF = xb1_matteShape(m, F, 'JZ Out Flap Matte');
    var gA = jzGrp(MF, 'free'); jzAddRect(gA, BIG * 2, BIG * 2, 0, (bx0 + bx1) / 2, (by0 + by1) / 2); jzAddFill(gA, '#FFFFFF');
    jzSetExpr(jzGX(gA).property('ADBE Vector Group Opacity'), HD + 'pfly>0?100:0');
    half(MF, 'flap');
    // mirror about the fold line through (px0, py0), then fly off along the peel direction and up
    var al = Math.atan2(ty, tx), tr = F.property('ADBE Transform Group');
    jzSetExpr(tr.property('ADBE Anchor Point'), HD + '[px0,py0]');
    jzSetExpr(tr.property('ADBE Position'), HD + 'var fm=SZ*2.2*pfly*pfly;[px0+' + jzN(dx) + '*fm,py0+' + jzN(dy) + '*fm-SZ*1.2*pfly]');
    tr.property('ADBE Rotate Z').setValue(al * 360 / Math.PI);
    tr.property('ADBE Scale').setValue([100, -100]);
    var back = jzMixHex(jzMixHex(C.c0, C.bg, 0.5), C.acc, 0.18);
    jzAnimator(F, 'JZ Out Back', [['ADBE Text Fill Color', jzHex(back)], ['ADBE Text Stroke Color', jzHex(back)]], '100');
    // lift shadow: the wanted screen offset w is mirrored into the flap's own space
    var c2 = Math.cos(2 * al), s2 = Math.sin(2 * al), wx = dx, wy = dy + 1, vx = c2 * wx + s2 * wy, vy = s2 * wx - c2 * wy;
    var ds = jzEffect(F, 'ADBE Drop Shadow', 'JZ Out Lift');
    jzEP(ds, 1, jzHex('#000000')); jzEP(ds, 2, (C.dark ? 0.5 : 0.28) * 255); jzEP(ds, 3, Math.atan2(vx, -vy) * 180 / Math.PI); jzEP(ds, 5, sz * 0.12);
    jzEX(ds, 4, HD + 'SZ*(0.05+0.1*pf)*' + jzN(Math.sqrt(wx * wx + wy * wy)));
} });

/* ---- crumpleOut \u2014 \u4E38\u3081\u3066\u6368\u3066\u308B: three quick squeezes gather the glyphs into a creased ball (per-glyph gather / turn / squash /
   skew, a little turbulence), then the ball is tossed away spinning on a parabola (layer rotation about the box centre + offset) */
jzReg('exit', 'crumpleOut', { apply: function (m) {
    var L = m.L, G = xb1_geo(m), sz = xb1_sz(m), C = xb1_col(m), W = m.W, H = m.H, i, cx = G.cx, cy = G.cy;
    var dir = xb1_cutBit(m, 21) ? 1 : -1;
    var ev = 'var cg1=ob(cl(PO/0.15),1.6),cg2=ob(cl((PO-0.17)/0.15),1.6),cg3=ob(cl((PO-0.34)/0.15),1.6),' +
        'cr=Math.max(0,Math.min(1.08,0.45*cg1+0.3*cg2+0.25*cg3)),cr1=Math.min(1,cr),ck=1-0.93*cr1,cv=cl((PO-0.5)/0.5),' +
        'csp=' + dir + '*(cr*25+620*cv*cv),ctx=' + jzN(dir * W * 0.42) + '*cv,cty=' + jzN(-H * 0.8) + '*cv+' + jzN(H * 1.75) + '*cv*cv;';
    var HD = xb1_hd(m) + ev;
    xb1_safe(m, ev, '1-sm(0.88,1,PO)');
    if (xb1_single(m, G)) {       // one glyph per layer: every glyph is pulled towards the screen centre, turned about it and tossed
        var mi = m.o.mi || 0, cs = m.c.seed | 0, P0 = xb1_toComp(L, cx, cy), s0 = xb1_s0(L);
        var jx = xb1_rs(cs, mi, 23) * sz * s0 * 0.25, jy = xb1_rs(cs, mi, 24) * sz * s0 * 0.25, rr = xb1_rs(cs, mi, 22) * 150, sk = xb1_rs(cs, mi, 25);
        m.parts.rot.push(XB1_FNS + ev + 'r+=csp+' + jzN(rr) + '*cr;');
        m.parts.sc.push(XB1_FNS + ev + 'f=[f[0]*(1-0.5*cr1),f[1]*(1-0.5*cr1)];');
        m.parts.pos.push(XB1_FNS + ev + xb1_pivot(m, '(1-0.5*cr1)', '(1-0.5*cr1)', 'csp+' + jzN(rr) + '*cr', cx, cy) +
            'var cxr=' + jzN(P0[0] - W / 2) + '*ck+' + jzN(jx) + '*cr,cyr=' + jzN(P0[1] - H / 2) + '*ck+' + jzN(jy) + '*cr,ca=csp*Math.PI/180;' +
            'd=[d[0]+' + jzN(W / 2 - P0[0]) + '+Math.cos(ca)*cxr-Math.sin(ca)*cyr+ctx,d[1]+' + jzN(H / 2 - P0[1]) + '+Math.sin(ca)*cxr+Math.cos(ca)*cyr+cty];');
        jzAnimator(L, 'JZ Out Crease', [['ADBE Text Skew', 22]], HD + jzN(sk) + '*cr*100');
        return;
    }
    m.parts.rot.push(XB1_FNS + ev + 'r+=csp;');
    m.parts.pos.push(XB1_FNS + ev + xb1_pivot(m, '1', '1', 'csp', cx, cy) + 'd=[d[0]+ctx,d[1]+cty];');
    var PX = [], PY = [], K = sz;
    for (i = 0; i < G.n; i++) { PX.push(G.g[i].x - cx); PY.push(G.g[i].y - cy); K = Math.max(K, Math.abs(PX[i]) + sz * 0.3, Math.abs(PY[i]) + sz * 0.3); }
    var q = HD + 'var i=textIndex-1,PX=' + xb1_arr(PX) + ',PY=' + xb1_arr(PY) + ',px=PX[i]||0,py=PY[i]||0,' +
        'jx=(hh(textIndex*26+SD)*2-1)*SZ*0.22,jy=(hh(textIndex*27+SD)*2-1)*SZ*0.22,r1=hh(textIndex*28+SD),r2=hh(textIndex*29+SD);';
    jzAnimator(L, 'JZ Out Gather', [['ADBE Text Position 3D', [K, K, 0]]], q + '[(px*(ck-1)+jx*cr)/' + jzN(K) + '*100,(py*(ck-1)+jy*cr)/' + jzN(K) + '*100,0]');
    jzAnimator(L, 'JZ Out Turn', [['ADBE Text Rotation', 170]], q + '(hh(textIndex*30+SD)*2-1)*cr*100');
    jzAnimator(L, 'JZ Out Crease', [['ADBE Text Skew', 26]], q + '(hh(textIndex*31+SD)*2-1)*cr*100');
    jzAnimator(L, 'JZ Out Squash', [['ADBE Text Scale 3D', [0, 0, 100]]], q + 'var s=1-0.55*cr1,sx=s*(1-0.35*r1*cr),sy=s*(1-0.35*r2*cr);[(1-sx)*100,(1-sy)*100,0]');
    jzAnimator(L, 'JZ Out Shade', [['ADBE Text Fill Color', jzHex(C.bg)]], q + 'r1<0.45?30*cr1:0');
    var td = jzEffect(L, 'ADBE Turbulent Displace', 'JZ Out Crumple');
    jzEP(td, 1, 1); jzEP(td, 3, Math.max(8, sz * 0.3)); jzEP(td, 5, 2); jzEP(td, 6, (m.c.seed | 0) % 360);
    jzEX(td, 2, HD + 'SZ*0.1*cr1');
} });

/* ---- tearOut \u2014 \u7834\u308A\u6368\u3066: torn in two along a jagged line; the halves turn apart about the tear's far end and fall away.
   Half A = L with a static polygon mask (its motion = layer rotation about the pivot + offset); half B = a twin with the other
   polygon, parented to L with the relative motion A\u207B\u00B9\u00B7B. Single-glyph layouts share one tear through the screen centre. */
jzReg('exit', 'tearOut', { apply: function (m) {
    var L = m.L, G = xb1_geo(m), r = G.r, sz = xb1_sz(m), W = m.W, H = m.H, vert = G.vert, s0 = xb1_s0(L), X = xb1_xf(L), cs = m.c.seed | 0, i, k;
    var ev = 'var to=oc(cl(PO/0.32)),tv=cl((PO-0.26)/0.74),tth=7*to+40*tv*tv,tfa=' + jzN(H * 1.1) + '*tv*tv,tsi=' + jzN(sz * s0 * 1.6) + '*tv;';
    var HD = xb1_hd(m) + ev;
    var rotOf = function (sg) { return (vert ? -sg : sg) + '*tth'; };
    var offOf = function (sg) {
        return vert ? ['(' + jzN(sg * 0.35 - 0.4) + ')*tsi', 'tfa+' + jzN(sg * sz * s0 * 0.3) + '*tv'] : [jzN(sg) + '*tsi', 'tfa*' + (sg < 0 ? '1' : '0.85')];
    };
    var polyA = [], polyB = [], piv, side = 0, big = sz * 4;
    if (xb1_single(m, G) && !vert) {
        var pc = [xb1_toComp(L, r.left, r.top), xb1_toComp(L, r.left + r.width, r.top), xb1_toComp(L, r.left, r.top + r.height), xb1_toComp(L, r.left + r.width, r.top + r.height)];
        var cx0 = 1e9, cx1 = -1e9, cy0 = 1e9, cy1 = -1e9, pd = sz * s0 * 0.3;
        for (i = 0; i < 4; i++) { cx0 = Math.min(cx0, pc[i][0]); cx1 = Math.max(cx1, pc[i][0]); cy0 = Math.min(cy0, pc[i][1]); cy1 = Math.max(cy1, pc[i][1]); }
        cy0 -= pd; cy1 += pd;
        var K1 = jzClamp(Math.ceil((cy1 - cy0) / (sz * s0 * 0.08)), 4, 40), jD = [], jx0 = 1e9, jx1 = -1e9;
        for (k = 0; k <= K1; k++) {
            var Y = cy0 + (cy1 - cy0) * k / K1, Xj = W / 2 + xb1_noise(Y / (H * 0.035), cs + 5) * H * 0.02 + xb1_rs(cs, Math.round(Y / (H * 0.008)), 331) * H * 0.006;
            jD.push([Xj, Y]); jx0 = Math.min(jx0, Xj); jx1 = Math.max(jx1, Xj);
        }
        if (cx1 < jx0) side = -1; else if (cx0 > jx1) side = 1;
        polyA.push(xb1_fromComp(L, -W, cy0 - pd)); polyA.push(xb1_fromComp(L, jD[0][0], cy0 - pd));
        polyB.push(xb1_fromComp(L, 2 * W, cy0 - pd)); polyB.push(xb1_fromComp(L, jD[0][0], cy0 - pd));
        for (k = 0; k <= K1; k++) { var pl = xb1_fromComp(L, jD[k][0], jD[k][1]); polyA.push(pl); polyB.push(pl); }
        polyA.push(xb1_fromComp(L, jD[K1][0], cy1 + pd)); polyA.push(xb1_fromComp(L, -W, cy1 + pd));
        polyB.push(xb1_fromComp(L, jD[K1][0], cy1 + pd)); polyB.push(xb1_fromComp(L, 2 * W, cy1 + pd));
        piv = xb1_fromComp(L, W / 2, H * 0.62);
    } else {
        var pad = sz * 0.3, bx0 = r.left - pad, by0 = r.top - pad, bx1 = r.left + r.width + pad, by1 = r.top + r.height + pad;
        var mid = vert ? (by0 + by1) / 2 : (bx0 + bx1) / 2, a0 = vert ? bx0 : by0, a1 = vert ? bx1 : by1;
        var K = jzClamp(Math.ceil((a1 - a0) / (sz * 0.08)), 6, 48), jag = [];
        for (k = 0; k <= K; k++) {
            var t = a0 + (a1 - a0) * k / K, off = xb1_rs(cs, k, 331) * sz * 0.05 + xb1_noise(k * 0.35, cs + 5) * sz * 0.16;
            jag.push(vert ? [t, mid + off] : [mid + off, t]);
        }
        if (vert) {
            polyA.push([bx0 - big, by0 - big]); polyA.push([bx0 - big, jag[0][1]]);
            polyB.push([bx0 - big, by1 + big]); polyB.push([bx0 - big, jag[0][1]]);
        } else {
            polyA.push([bx0 - big, by0 - big]); polyA.push([jag[0][0], by0 - big]);
            polyB.push([bx1 + big, by0 - big]); polyB.push([jag[0][0], by0 - big]);
        }
        for (k = 0; k <= K; k++) { polyA.push(jag[k]); polyB.push(jag[k]); }
        if (vert) {
            polyA.push([bx1 + big, jag[K][1]]); polyA.push([bx1 + big, by0 - big]);
            polyB.push([bx1 + big, jag[K][1]]); polyB.push([bx1 + big, by1 + big]);
        } else {
            polyA.push([jag[K][0], by1 + big]); polyA.push([bx0 - big, by1 + big]);
            polyB.push([jag[K][0], by1 + big]); polyB.push([bx1 + big, by1 + big]);
        }
        piv = jag[K];                    // the tear starts at the far end and opens from the near one
    }
    var Q = xb1_toComp(L, piv[0], piv[1]);
    xb1_safe(m, ev, '1-sm(0.82,1,PO)');
    if (side || xb1_crowded(m, G)) { xb1_rigid(m, XB1_FNS + ev, rotOf(side || -1), Q, offOf(side || -1)); return; }
    var B = xb1_twin(m, 'JZ Out Torn B', ev, 'PO>0?1-sm(0.82,1,PO):0');
    var mA = xb1_mask(L, polyA, 'add', 'JZ Out Torn A');
    jzSetExpr(mA.property('ADBE Mask Offset'), HD + 'PO>0?0:1e4');
    xb1_rigid(m, XB1_FNS + ev, rotOf(-1), Q, offOf(-1));
    xb1_mask(B, polyB, 'add', 'JZ Out Torn B');
    // B relative to A (both turn about the pivot): rotation rotB - rotA, offset Rot(-(rotA + r0)) (TB - TA) / s0
    var tr = B.property('ADBE Transform Group'), oA = offOf(-1), oB = offOf(1);
    tr.property('ADBE Anchor Point').setValue([piv[0], piv[1]]);
    jzSetExpr(tr.property('ADBE Rotate Z'), HD + '(' + rotOf(1) + ')-(' + rotOf(-1) + ')');
    jzSetExpr(tr.property('ADBE Position'), HD + 'var ra=-((' + rotOf(-1) + ')+' + jzN(X.r) + ')*Math.PI/180,wx=((' + oB[0] + ')-(' + oA[0] + '))/' + jzN(s0) + ',wy=((' + oB[1] + ')-(' + oA[1] + '))/' + jzN(s0) + ';' +
        '[' + jzN(piv[0]) + '+Math.cos(ra)*wx-Math.sin(ra)*wy,' + jzN(piv[1]) + '+Math.sin(ra)*wx+Math.cos(ra)*wy]');
} });

/* ---- scorchOut \u2014 \u7126\u3052\u3066\u6D88\u3048\u308B: a burning front with a rough edge eats across the text: glyphs next to the front glow in a heat
   colour, a glowing accent rim with a soot line rides on the edge, embers drift back and up. Unburnt part = twin through a
   rough-edged polygon matte moving with the front. */
jzReg('exit', 'scorchOut', { apply: function (m) {
    var L = m.L, G = xb1_geo(m), r = G.r, sz = xb1_sz(m), C = xb1_col(m), W = m.W, H = m.H, s0 = xb1_s0(L), cs = m.c.seed | 0, i, k;
    var single = xb1_single(m, G), vert = G.vert, seed = single ? cs : cs + (m.o.mi || 0) * 101;
    var U = single ? Math.min(W, H) * 0.16 / s0 : sz, dx = vert ? 0.22 : 1, dy = vert ? 1 : 0.22, nl = Math.sqrt(dx * dx + dy * dy);
    dx /= nl; dy /= nl;
    if (xb1_cutBit(m, 31)) { dx = -dx; dy = -dy; }
    var tx = -dy, ty = dx, pad = sz * 0.2, bx0 = r.left - pad, by0 = r.top - pad, bx1 = r.left + r.width + pad, by1 = r.top + r.height + pad;
    var cn = [[bx0, by0], [bx1, by0], [bx0, by1], [bx1, by1]], u0 = 1e9, u1 = -1e9, w0 = 1e9, w1 = -1e9;
    for (i = 0; i < 4; i++) {
        var uu = cn[i][0] * dx + cn[i][1] * dy, ww = cn[i][0] * tx + cn[i][1] * ty;
        u0 = Math.min(u0, uu); u1 = Math.max(u1, uu); w0 = Math.min(w0, ww); w1 = Math.max(w1, ww);
    }
    if (single) {                    // one screen-wide front for every glyph layer
        var sc4 = [[0, 0], [W, 0], [0, H], [W, H]]; u0 = 1e9; u1 = -1e9;
        for (i = 0; i < 4; i++) { var pl = xb1_fromComp(L, sc4[i][0], sc4[i][1]), us = pl[0] * dx + pl[1] * dy; u0 = Math.min(u0, us); u1 = Math.max(u1, us); }
    }
    var rough = U * 0.2, band = U * 0.4, sA = u0 - rough * 1.3, sB = u1 + rough * 1.3 + band;
    var ev = 'var ss=' + jzN(sA) + '+' + jzN(sB - sA) + '*ios(PO);', HD = xb1_hd(m) + ev, heat = jzMixHex(C.c0, C.acc, 0.85);
    var UG = [];
    for (i = 0; i < G.n; i++) UG.push(G.g[i].x * dx + G.g[i].y * dy);
    var hq = HD + 'var UG=' + xb1_arr(UG) + ',z=((UG[textIndex-1]||0)-ss+' + jzN(U * 0.35) + ')/' + jzN(band + U * 0.7) + ';PO>0&&z>-0.25?90*(1-sm(0,1,z)):0';
    if (xb1_crowded(m, G)) {
        var ax = Math.abs(dx) > Math.abs(dy) ? 'x' : 'y', sg = ax === 'x' ? (dx > 0 ? 1 : -1) : (dy > 0 ? 1 : -1);
        xb1_wipe(m, r, ax, sg, 'ios(PO)*1.08', HD, sz * 0.15);
        jzAnimator(L, 'JZ Out Heat', [['ADBE Text Fill Color', jzHex(heat)]], hq);
        xb1_safe(m, ev, '1');
        return;
    }
    var pt = function (u, w) { return [dx * u + tx * w, dy * u + ty * w]; };
    var FAR = (sB - sA) + U * 6, wa = w0 - U * 0.5, wb = w1 + U * 0.5, Kn = jzClamp(Math.ceil((wb - wa) / (U * 0.07)), 6, 60), edge = [];
    for (k = 0; k <= Kn; k++) {
        var w = wa + (wb - wa) * k / Kn;
        edge.push([rough * (0.75 * xb1_noise(w / U * 2.4, seed) + 0.3 * xb1_noise(w / U * 8, seed + 3)), w]);
    }
    xb1_hideL(m, ev, 'PO>0');
    var T = xb1_twin(m, 'JZ Out Unburnt', ev, 'PO>0?1:0');
    jzAnimator(T, 'JZ Out Heat', [['ADBE Text Fill Color', jzHex(heat)], ['ADBE Text Stroke Color', jzHex(heat)]], hq);
    var M = xb1_matteShape(m, T, 'JZ Out Burn Matte'), g = jzGrp(M, 'unburnt'), poly = [];
    for (k = 0; k <= Kn; k++) poly.push(pt(edge[k][0], edge[k][1]));
    poly.push(pt(FAR, wb)); poly.push(pt(FAR, wa));
    jzAddPath(g, poly, true); jzAddFill(g, '#FFFFFF');
    var at = HD + '[ss*' + jzN(dx) + ',ss*' + jzN(dy) + ']';
    jzSetExpr(jzGX(g).property('ADBE Vector Position'), at);
    if (!xb1_main(m)) return;
    // embers (on top), the glowing rim and the soot line behind it
    var S = xb1_shape(m, 'JZ Out Scorch', M), lw = Math.max(1.5 * m.u / s0, U * 0.03), soot = C.dark ? jzMixHex(C.bg, C.acc, 0.25) : jzMixHex(C.bg, '#000000', 0.45);
    var nE = single ? 5 : 14, j;
    for (j = 0; j < nE; j++) {
        var t0 = xb1_r(seed, j, 341) * 0.85, we = w0 + (w1 - w0) * xb1_r(seed, j, 342);
        var oe = rough * (0.75 * xb1_noise(we / U * 2.4, seed) + 0.3 * xb1_noise(we / U * 8, seed + 3));
        var p0 = pt(sA + (sB - sA) * xb1_ios(t0) + oe - U * 0.05, we), ag = 'var ag=(PO-' + xb1_n(t0) + ')/0.28,ac=cl(ag);';
        var ge = jzGrp(S, 'ember ' + (j + 1)), el = jzAddEllipse(ge, 4, 4);
        jzSetExpr(el.property('ADBE Vector Ellipse Size'), HD + ag + 'var R=ag>0&&ag<1?Math.max(0.8,' + jzN(U * 0.028) + '*(1-ag)):0;[2*R,2*R]');
        jzAddFill(ge, C.acc);          // (after the ellipse is configured: adding the fill invalidates the ellipse reference)
        jzSetExpr(jzGX(ge).property('ADBE Vector Position'), HD + ag + '[' + jzN(p0[0]) + '-' + xb1_n(dx * U * 0.3) + '*ac+Math.sin(ac*7+' + j + ')*' + jzN(U * 0.06) + ',' + jzN(p0[1]) + '-' + xb1_n(dy * U * 0.3) + '*ac-' + xb1_n(U * 0.9) + '*ac]');
        jzSetExpr(jzGX(ge).property('ADBE Vector Group Opacity'), HD + ag + 'ag>0&&ag<1?(1-ag)*100:0');
    }
    var line = [], sline = [];
    for (k = 0; k <= Kn; k++) { line.push(pt(edge[k][0], edge[k][1])); sline.push(pt(edge[k][0] - lw * 1.6, edge[k][1])); }
    var strokes = [['rim', line, C.acc, lw, 100], ['rim glow', line, C.acc, lw * 4, 22], ['soot', sline, soot, lw * 2.2, 80]];
    for (k = 0; k < strokes.length; k++) {
        var gs = jzGrp(S, strokes[k][0]); jzAddPath(gs, strokes[k][1], false); jzAddStroke(gs, strokes[k][2], strokes[k][3], strokes[k][4]);
        jzSetExpr(jzGX(gs).property('ADBE Vector Position'), at);
    }
    jzSetExpr(jzXf(S, 'ADBE Opacity'), HD + 'PO>0?(1-sm(0.86,1,PO))*100:0');
} });

/* ================================================================ EXITS: light / signal */

/* ---- overexposeOut \u2014 \u767D\u98DB\u3073: the text blows out to white (to the paper on light schemes) with a bloom, swells and spreads a
   little, and a lens streak flares across it while it fades */
jzReg('exit', 'overexposeOut', { apply: function (m) {
    var L = m.L, G = xb1_geo(m), r = G.r, sz = xb1_sz(m), C = xb1_col(m), s0 = xb1_s0(L);
    var hot = C.dark ? '#FFFFFF' : C.bg, glow = C.dark ? jzMixHex(C.acc, '#FFFFFF', 0.45) : C.acc;
    var ev = 'var oxa=iq(cl(PO/0.5)),oxe=oc(cl(PO/0.7));', HD = xb1_hd(m) + ev;
    jzAnimator(L, 'JZ Out Blowout', [['ADBE Text Fill Color', jzHex(hot)], ['ADBE Text Stroke Color', jzHex(hot)]], HD + 'oxa*100');
    jzAnimator(L, 'JZ Out Spread', [['ADBE Text Tracking Amount', 60]], HD + 'oxe*100');
    m.parts.sc.push(ev + 'f=[f[0]*(1+0.07*oxe),f[1]*(1+0.07*oxe)];');
    m.parts.pos.push(ev + xb1_pivot(m, '1+0.07*oxe', '1+0.07*oxe', '0', G.cx, G.cy));
    xb1_safe(m, ev, '1-ios((PO-0.38)/0.62)');
    var ds = jzEffect(L, 'ADBE Drop Shadow', 'JZ Out Bloom');
    jzEP(ds, 1, jzHex(glow)); jzEP(ds, 3, 135); jzEP(ds, 4, 0);
    jzEX(ds, 2, HD + 'PO>0?242:0'); jzEX(ds, 5, HD + 'SZ*(0.05+0.4*oxe)');
    var gl = jzEffect(L, 'ADBE Glo2', 'JZ Out Flare');
    jzEP(gl, 2, 25);
    jzEX(gl, 3, HD + 'Math.min(600,SZ*0.9*oxe)'); jzEX(gl, 4, HD + 'PO>0.12?0.9*oxe:0');
    if (!xb1_main(m)) return;
    var single = xb1_single(m, G);
    if (single && (m.o.mi || 0) !== 0) return;
    // the lens streak: three horizontal bars (soft wide, medium, hot core line)
    var S, cx, cy, bw, k1 = 1, i;
    if (single) { var P0 = xb1_toComp(L, G.cx, G.cy); S = xb1_cshape(m, 'JZ Out Streak'); cx = m.W / 2; cy = P0[1]; bw = m.W * 0.5; k1 = s0; }
    else { S = xb1_shape(m, 'JZ Out Streak'); cx = G.cx; cy = G.cy; bw = r.width; }
    var bars = [[1.8, 0.018, 95], [1.5, 0.06, 35], [1, 0.18, 14]];
    for (i = 0; i < bars.length; i++) {
        var g = jzGrp(S, 'streak ' + (i + 1)), rc = jzAddRect(g, 10, 10, 0);
        jzSetExpr(rc.property('ADBE Vector Rect Size'), HD + '[' + jzN(bw * bars[i][0]) + '*(0.8+0.9*oxe),' + jzN(sz * k1 * bars[i][1]) + ']');
        jzAddFill(g, glow);
        jzGX(g).property('ADBE Vector Position').setValue([cx, cy]); jzGX(g).property('ADBE Vector Group Opacity').setValue(bars[i][2]);
    }
    jzSetExpr(jzXf(S, 'ADBE Opacity'), HD + 'PO>0?Math.sin(Math.PI*cl((PO-0.12)/0.88))*100:0');
} });

/* ---- scanOut \u2014 \u8D70\u67FB\u7DDA\u6D88\u53BB: a bright scan line runs down; behind it the text breaks into interlaced lines that shear sideways
   in bands and thin out. Below the line = L (subtract mask above the line); the scanned band = a twin with masks for the band
   just above the line, Venetian Blinds as the interlace and a square Wave Warp as the band shear. */
jzReg('exit', 'scanOut', { apply: function (m) {
    var L = m.L, G = xb1_geo(m), r = G.r, sz = xb1_sz(m), C = xb1_col(m), s0 = xb1_s0(L), H = m.H;
    var pad = sz * 0.3, bx0 = r.left - pad, by0 = r.top - pad, bx1 = r.left + r.width + pad, by1 = r.top + r.height + pad, h = by1 - by0;
    var single = xb1_single(m, G), Lh = single ? H * 0.14 / s0 : Math.max(sz * 0.9, h * 0.3);
    var ya = single ? xb1_fromComp(L, m.W / 2, H * 0.2)[1] : by0 - sz * 0.02, yb = single ? xb1_fromComp(L, m.W / 2, H * 0.8)[1] + Lh : by1 + Lh;
    var pitch = Math.max(2.5 * m.u / s0, sz * 0.065); if (h / pitch > 64) pitch = h / 64;
    var ev = 'var sy=' + jzN(ya) + '+' + jzN(yb - ya) + '*PO,sx=Math.max(0,sy-' + xb1_n(ya) + ');', HD = xb1_hd(m) + ev, big = (r.width + r.height) * 4 + sz * 10;
    var above = xb1_rpts(bx0 - big, by0 - big, bx1 + big, ya);
    var T = xb1_crowded(m, G) ? null : xb1_twin(m, 'JZ Out Scanned', ev, 'PO>0?1:0');
    var mk = xb1_mask(L, above, 'sub', 'JZ Out Scan');
    jzSetExpr(mk.property('ADBE Mask Offset'), HD + 'sx'); jzSetExpr(mk.property('ADBE Mask Opacity'), HD + 'PO>0?100:0');
    xb1_safe(m, ev, '1');
    if (T) {
        var a1 = xb1_mask(T, above, 'add', 'JZ Out Scanned');
        jzSetExpr(a1.property('ADBE Mask Offset'), HD + 'sx');
        var a2 = xb1_mask(T, xb1_rpts(bx0 - big, by0 - big - Lh, bx1 + big, ya - Lh), 'sub', 'JZ Out Scan Tail');
        jzSetExpr(a2.property('ADBE Mask Offset'), HD + 'sx');
        a2.property('ADBE Mask Feather').setValue([Lh * 0.7, Lh * 0.7]);
        var vb = jzEffect(T, 'ADBE Venetian Blinds', 'JZ Out Interlace');
        jzEX(vb, 1, HD + '42+30*cl(PO*1.5)'); jzEP(vb, 2, 90); jzEP(vb, 3, pitch * 2); jzEP(vb, 4, 0);
        var ww = jzEffect(T, 'ADBE Wave Warp', 'JZ Out Shear');
        jzEP(ww, 1, 2); jzEP(ww, 3, Math.max(sz * 0.25, h / 3.5)); jzEP(ww, 4, 0); jzEP(ww, 5, 0); jzEP(ww, 6, 1); jzEP(ww, 7, (m.c.seed | 0) % 360);
        jzEX(ww, 2, HD + 'SZ*(0.04+0.45*cl(PO*1.5))');
    }
    if (!xb1_main(m)) return;
    var S = xb1_shape(m, 'JZ Out Scan Line'), lw = Math.max(1.5 * m.u / s0, sz * 0.025), bw = (bx1 - bx0) + sz * 0.4, cx = (bx0 + bx1) / 2, i;
    var lines = [[lw, 100], [lw * 6, 18]];
    for (i = 0; i < lines.length; i++) {
        var g = jzGrp(S, i ? 'glow' : 'line'); jzAddRect(g, bw, lines[i][0], 0); jzAddFill(g, C.acc);
        jzGX(g).property('ADBE Vector Group Opacity').setValue(lines[i][1]);
        jzSetExpr(jzGX(g).property('ADBE Vector Position'), HD + '[' + jzN(cx) + ',sy]');
    }
    jzSetExpr(jzXf(S, 'ADBE Opacity'), HD + 'PO>0&&sy<' + jzN(by1 + sz * 0.1) + '&&sy>' + jzN(by0 - sz * 0.1) + '?(1-sm(0.8,1,PO))*100:0');
} });

/* ================================================================ EXITS: graphic masks */

/* ---- stripesOut \u2014 \u30B9\u30C8\u30E9\u30A4\u30D7\u6D88\u53BB: slanted stripes, each wiped along its own length in alternating directions, staggered across
   the text, with an accent tick on every moving end (twin through a matte of per-stripe rects) */
jzReg('exit', 'stripesOut', { apply: function (m) {
    var L = m.L, G = xb1_geo(m), r = G.r, sz = xb1_sz(m), C = xb1_col(m), i, k;
    var pad = sz * 0.12, bx0 = r.left - pad, by0 = r.top - pad, bx1 = r.left + r.width + pad, by1 = r.top + r.height + pad;
    var ang = (xb1_cutBit(m, 41) ? 64 : 116) * Math.PI / 180, ldx = Math.cos(ang), ldy = Math.sin(ang), adx = -ldy, ady = ldx;
    var cn = [[bx0, by0], [bx1, by0], [bx0, by1], [bx1, by1]], a0 = 1e9, a1 = -1e9, l0 = 1e9, l1 = -1e9;
    for (i = 0; i < 4; i++) {
        var aa = cn[i][0] * adx + cn[i][1] * ady, ll = cn[i][0] * ldx + cn[i][1] * ldy;
        a0 = Math.min(a0, aa); a1 = Math.max(a1, aa); l0 = Math.min(l0, ll); l1 = Math.max(l1, ll);
    }
    var n = Math.max(2, Math.ceil((a1 - a0) / Math.max(6, sz * 0.38))); if (n > 32) n = 32;
    var sw = (a1 - a0) / n, rev = xb1_cutBit(m, 42), HD = xb1_hd(m), phi = ang * 180 / Math.PI - 90;
    if (xb1_crowded(m, G)) { xb1_wipe(m, r, 'x', (rev ? -1 : 1) * (adx > 0 ? 1 : -1), 'io3(PO)*1.05', HD, sz * 0.1); xb1_safe(m, '', '1'); return; }
    var qe = function (k2) {
        var o = n > 1 ? k2 / (n - 1) : 0, st = (rev ? 1 - o : o) * 0.42, fs = (k2 & 1) === 1;
        return 'var q=io3((PO-' + xb1_n(st) + ')/0.58),la=' + (fs ? jzN(l0) + '+' + jzN(l1 - l0) + '*q' : jzN(l0)) + ',lb=' + (fs ? jzN(l1) : jzN(l1) + '-' + xb1_n(l1 - l0) + '*q') + ';';
    };
    xb1_hideL(m, '', 'PO>0');
    var T = xb1_twin(m, 'JZ Out Striped', '', 'PO>0?1:0'), M = xb1_matteShape(m, T, 'JZ Out Stripes Matte');
    for (k = 0; k < n; k++) {
        var am = a0 + (k + 0.5) * sw, g = jzGrp(M, 'stripe ' + (k + 1)), rc = jzAddRect(g, sw + 0.6, 10, 0);
        jzSetExpr(rc.property('ADBE Vector Rect Size'), HD + qe(k) + '[' + jzN(sw + 0.6) + ',Math.max(0,lb-la)]');
        jzAddFill(g, '#FFFFFF');
        jzGX(g).property('ADBE Vector Rotation').setValue(phi);
        jzSetExpr(jzGX(g).property('ADBE Vector Position'), HD + qe(k) + 'var lm=(la+lb)/2;[' + jzN(adx * am) + '+' + jzN(ldx) + '*lm,' + jzN(ady * am) + '+' + jzN(ldy) + '*lm]');
    }
    if (!xb1_main(m)) return;
    // the ticks on the moving ends, only where the text box is
    var S = xb1_shape(m, 'JZ Out Stripe Ticks', M), th = Math.max(2 * m.u, sz * 0.045);
    for (k = 0; k < n; k++) {
        var fs2 = (k & 1) === 1, am2 = a0 + (k + 0.5) * sw, gt = jzGrp(S, 'tick ' + (k + 1));
        jzAddRect(gt, Math.max(1, sw - 1), th, 0); jzAddFill(gt, C.acc);
        jzGX(gt).property('ADBE Vector Rotation').setValue(phi);
        var lt = fs2 ? 'la-' + xb1_n(th / 2) : 'lb+' + jzN(th / 2);
        jzSetExpr(jzGX(gt).property('ADBE Vector Position'), HD + qe(k) + 'var lt=' + lt + ';[' + jzN(adx * am2) + '+' + jzN(ldx) + '*lt,' + jzN(ady * am2) + '+' + jzN(ldy) + '*lt]');
        jzSetExpr(jzGX(gt).property('ADBE Vector Group Opacity'), HD + qe(k) + 'q>0&&q<1?(1-sm(0.8,1,q))*100:0');
    }
    jzMaskRect(S, r.left - sz * 0.02, r.top - sz * 0.02, r.left + r.width + sz * 0.02, r.top + r.height + sz * 0.02);
    jzSetExpr(jzXf(S, 'ADBE Opacity'), HD + 'PO>0?100:0');
} });

/* ---- halftoneOut \u2014 \u7DB2\u70B9\u306B\u6D88\u3048\u308B: the fill breaks into a halftone screen whose dots shrink away in a sweep (twin through a
   hex dot lattice matte: one column of dots per shape group, repeated down the column; dot size follows the sweep) */
jzReg('exit', 'halftoneOut', { apply: function (m) {
    var L = m.L, G = xb1_geo(m), r = G.r, sz = xb1_sz(m), C = xb1_col(m), vert = G.vert, i, j, par;
    var pad = sz * 0.1, bx0 = r.left - pad, by0 = r.top - pad, bx1 = r.left + r.width + pad, by1 = r.top + r.height + pad, w = bx1 - bx0, h = by1 - by0;
    var c = Math.max(3 * m.u, sz * 0.15);
    while ((w / c + 2) * (h / (c * 0.866) + 2) > 220) c *= 1.1;
    var rows = Math.ceil(h / (c * 0.866)) + 1, cols = Math.ceil(w / c) + 2, R0 = c * 0.64, rev = xb1_cutBit(m, 51), HD = xb1_hd(m);
    var tone = [['ADBE Text Fill Color', jzHex(C.acc)]], toneEx = HD + '40*sm(0.05,0.5,PO)';
    if (xb1_crowded(m, G)) {
        var bd = jzEffect(L, 'ADBE Block Dissolve', 'JZ Out Halftone');
        jzEP(bd, 2, c); jzEP(bd, 3, c); jzEP(bd, 4, 0); jzEP(bd, 5, 0);
        jzEX(bd, 1, HD + 'ios(PO)*100');
        jzAnimator(L, 'JZ Out Tone', tone, toneEx);
        xb1_safe(m, '', '1');
        return;
    }
    xb1_hideL(m, '', 'PO>0');
    var T = xb1_twin(m, 'JZ Out Halftone', '', 'PO>0?1:0'), M = xb1_matteShape(m, T, 'JZ Out Halftone Matte');
    jzAnimator(T, 'JZ Out Tone', tone, toneEx);
    var dot = function (g, x, y, pos) {
        if (rev) pos = 1 - pos;
        var el = jzAddEllipse(g, R0 * 2, R0 * 2, x, y);
        jzSetExpr(el.property('ADBE Vector Ellipse Size'), HD + 'var k=cl(PO*1.55-' + xb1_n(pos) + '*0.55),rr=' + jzN(R0) + '*(1-ios(k));[2*rr,2*rr]');
    };
    var rep = function (g, copies, ox, oy) {
        var rp = jzVecs(g).addProperty('ADBE Vector Filter - Repeater');
        rp.property('ADBE Vector Repeater Copies').setValue(copies);
        rp.property('ADBE Vector Repeater Transform').property('ADBE Vector Repeater Position').setValue([ox, oy]);
    };
    if (!vert) {         // sweep along x: one group per column (even / odd rows), repeated down the column
        for (i = 0; i < cols; i++) for (par = 0; par < 2; par++) {
            var nr = par ? Math.floor(rows / 2) : Math.ceil(rows / 2); if (!nr) continue;
            var x = bx0 + (i + par * 0.5 - 0.5) * c, g = jzGrp(M, 'dots ' + (i + 1) + (par ? 'b' : 'a'));
            dot(g, x, by0 + par * c * 0.866, (x - bx0) / w * 0.8 + 0.1);
            rep(g, nr, 0, 2 * c * 0.866); jzAddFill(g, '#FFFFFF');
        }
    } else {             // sweep along y: one group per row
        for (j = 0; j < rows; j++) {
            var y = by0 + j * c * 0.866, gr = jzGrp(M, 'dots ' + (j + 1));
            dot(gr, bx0 + ((j & 1) * 0.5 - 0.5) * c, y, (y - by0) / h * 0.8 + 0.1);
            rep(gr, cols, c, 0); jzAddFill(gr, '#FFFFFF');
        }
    }
} });

/* ---- eraserOut \u2014 \u9ED2\u677F\u6D88\u3057: a felt eraser zig-zags lane by lane, erasing as it goes and leaving a faint chalky smear that fades
   (kept part = twin through per-lane rect matte; smear = blurred, stretched twin through the complementary matte) */
function xb1_eraserBlock(m, S, E, lanes, felt) {
    // E: expression prefix defining el (lane), ex (block centre along the lane from its start); lanes: {vert, lh, len, L0, A0, fwdSign}
    // (each sub-group / item is fully set up before its next sibling is added: adding one invalidates references to the others)
    var g = jzGrp(S, 'eraser'), ew = lanes.ew, lh = lanes.lh, vert = lanes.vert, bw = vert ? lh * 1.06 : ew, bh = vert ? ew : lh * 1.06;
    var sf = jzVecs(g).addProperty('ADBE Vector Group');      // felt (first = drawn on top of the body)
    sf.name = 'felt';
    var rf = jzAddRect(sf, vert ? bw : bw * 0.3, vert ? bh * 0.3 : bh, Math.min(bw, bh) * 0.12);
    // felt on the trailing side of the stroke
    jzSetExpr(rf.property('ADBE Vector Rect Position'), E + 'var fw=el%2==0?-1:1;' + (vert ? '[0,fw*' + jzN(bh * 0.35) + ']' : '[fw*' + jzN(bw * 0.35) + ',0]'));
    jzAddFill(sf, felt);
    var sb = jzVecs(g).addProperty('ADBE Vector Group');      // body
    sb.name = 'body';
    jzAddRect(sb, bw, bh, Math.min(bw, bh) * 0.18); jzAddFill(sb, lanes.acc);
    jzSetExpr(jzGX(g).property('ADBE Vector Position'),E + 'var c=Math.max(' + jzN(-ew / 2) + ',Math.min(' + jzN(lanes.len + ew / 2) + ',ex)),t=el%2==0?' + jzN(lanes.L0) + '+c:' + jzN(lanes.L0 + lanes.len) + '-c,a=' + (vert ? jzN(lanes.A0) + '-(el+0.5)*' + jzN(lh) : jzN(lanes.A0) + '+(el+0.5)*' + jzN(lh)) + ';' + (vert ? '[a,t]' : '[t,a]'));
}
jzReg('exit', 'eraserOut', { apply: function (m) {
    var L = m.L, G = xb1_geo(m), r = G.r, sz = xb1_sz(m), C = xb1_col(m), vert = G.vert, s0 = xb1_s0(L), i, j;
    var felt = jzMixHex(C.acc, C.bg, 0.45), HD = xb1_hd(m);
    if (xb1_single(m, G)) {       // one screen-wide eraser (3 lanes) for every glyph layer; each glyph goes when the eraser reaches it
        var W = m.W, H = m.H, X0 = W * 0.03, X1 = W * 0.97, Y0 = H * 0.3, Y1 = H * 0.7, lhS = (Y1 - Y0) / 3, ewS = H * 0.13 * 0.8, lenS = X1 - X0, legS = lenS + ewS;
        var E1 = 'var epos=cl(PO/0.9)*3*' + jzN(legS) + ',el=Math.min(2,Math.floor(epos/' + jzN(legS) + ')),ex=epos-el*' + jzN(legS) + '-' + xb1_n(ewS / 2) + ';';
        var P0 = xb1_toComp(L, G.cx, G.cy), jl = jzClamp(Math.floor((P0[1] - Y0) / lhS), 0, 2), sg = jl % 2 === 0 ? P0[0] - X0 : X0 + lenS - P0[0];
        var hw = Math.max(4, r.width * s0 * 0.5);
        xb1_safe(m, E1, 'el>' + jl + '?0:(el==' + jl + '?1-cl((ex+' + jzN(ewS * 0.3) + '-' + xb1_n(sg - hw) + ')/' + jzN(2 * hw) + '):1)');
        if ((m.o.mi || 0) !== 0 || !xb1_main(m)) return;
        var SE = xb1_cshape(m, 'JZ Out Eraser');
        xb1_eraserBlock(m, SE, HD + E1, { vert: false, lh: lhS, len: lenS, L0: X0, A0: Y0, ew: ewS, acc: C.acc }, felt);
        jzSetExpr(jzXf(SE, 'ADBE Opacity'), HD + 'PO>0?(1-sm(0.9,1,PO))*100:0');
        return;
    }
    var pad = sz * 0.12, bx0 = r.left - pad, by0 = r.top - pad, bx1 = r.left + r.width + pad, by1 = r.top + r.height + pad;
    var span = vert ? bx1 - bx0 : by1 - by0, len = vert ? by1 - by0 : bx1 - bx0, ink = vert ? span : Math.min(span, (G.nL - 1) * G.lead + G.fs * 0.92 + pad * 2);
    var K = jzClamp(Math.round(ink / (sz * 0.7)), 2, 10), lh = span / K, ew = sz * 0.8, leg = len + ew;     // lane count from the ink height (as the browser's box)
    var L0 = vert ? by0 : bx0, A0 = vert ? bx1 : by0;
    var E = HD + 'var epos=cl(PO/0.9)*' + K + '*' + jzN(leg) + ',el=Math.min(' + (K - 1) + ',Math.floor(epos/' + jzN(leg) + ')),ex=epos-el*' + jzN(leg) + '-' + xb1_n(ew / 2) + ',ecut=Math.max(0,Math.min(' + jzN(len) + ',ex+' + jzN(ew * 0.3) + '));' +
        'function LR(j,a,b){var a0=' + (vert ? jzN(A0) + '-(j+1)*' + jzN(lh) : jzN(A0) + '+j*' + jzN(lh)) + ',f=j%2==0,t0=f?' + jzN(L0) + '+a:' + jzN(L0 + len) + '-b,t1=f?' + jzN(L0) + '+b:' + jzN(L0 + len) + '-a;' +
        'return ' + (vert ? '[a0,t0,' + jzN(lh) + ',Math.max(0,t1-t0)]' : '[t0,a0,Math.max(0,t1-t0),' + jzN(lh) + ']') + ';}';
    if (xb1_crowded(m, G)) { xb1_wipe(m, r, vert ? 'y' : 'x', 1, 'cl(PO/0.9)', HD, 0); xb1_safe(m, '', '1'); return; }
    xb1_hideL(m, '', 'PO>0');
    var lane = function (Mx, keep) {
        for (j = 0; j < K; j++) {
            var g = jzGrp(Mx, 'lane ' + (j + 1)), rc = jzAddRect(g, 10, 10, 0);
            var q = E + 'var j=' + j + ',q=' + (keep ? 'LR(j,j>el?0:(j==el?ecut:' + jzN(len) + '),' + jzN(len) + ')' : 'LR(j,0,j<el?' + jzN(len) + ':(j==el?ecut:0))') + ';';
            jzSetExpr(rc.property('ADBE Vector Rect Size'), q + '[q[2]>0.01&&q[3]>0.01?q[2]+0.4:0,q[3]>0.01&&q[2]>0.01?q[3]+0.4:0]');
            jzAddFill(g, '#FFFFFF');
            jzSetExpr(jzGX(g).property('ADBE Vector Position'), q + '[q[0]+q[2]/2,q[1]+q[3]/2]');
        }
    };
    var T = xb1_twin(m, 'JZ Out Unerased', '', 'PO>0?1:0'), MK = xb1_matteShape(m, T, 'JZ Out Unerased Matte');
    lane(MK, true);
    if (!xb1_main(m)) return;
    // chalk smear in the erased part: blurred, stretched along the stroke, fading
    var cxm = (bx0 + bx1) / 2, cym = (by0 + by1) / 2;
    var T2 = xb1_twin(m, 'JZ Out Smear', '', 'PO>0?0.26*(1-sm(0.35,0.95,PO)):0', MK), MG = xb1_matteShape(m, T2, 'JZ Out Smear Matte');
    jzNoGhost(T2);
    lane(MG, false);
    var tr2 = T2.property('ADBE Transform Group');
    tr2.property('ADBE Anchor Point').setValue([cxm, cym]);
    jzSetExpr(tr2.property('ADBE Position'), '[' + jzN(cxm) + ',' + jzN(cym) + ']');
    tr2.property('ADBE Scale').setValue(vert ? [100, 112] : [110, 100]);
    jzAnimator(T2, 'JZ Out Chalk', [['ADBE Text Fill Color', jzHex(jzMixHex(C.c0, C.bg, 0.25))], ['ADBE Text Stroke Color', jzHex(jzMixHex(C.c0, C.bg, 0.25))]], '100');
    var gb = jzEffect(T2, 'ADBE Gaussian Blur 2', 'JZ Out Smudge'); jzEP(gb, 1, Math.min(sz * 0.1, 28 * m.u / s0));
    // the eraser block
    var SE2 = xb1_shape(m, 'JZ Out Eraser', MG);
    xb1_eraserBlock(m, SE2, E, { vert: vert, lh: lh, len: len, L0: L0, A0: A0, ew: ew, acc: C.acc }, felt);
    jzSetExpr(jzXf(SE2, 'ADBE Opacity'), HD + 'PO>0?(1-sm(0.9,1,PO))*100:0');
} });

/* ---- vacuumOut \u2014 \u4E00\u70B9\u306B\u5438\u308F\u308C\u308B: sucked into a point beyond the end of the line, nearest glyph first, each glyph stretching along
   its path; an accent dot at the target swells with what it swallowed and pops as a ring */
function xb1_vacDot(m, S, cx, cy, frEx, szc) {
    // (each item is configured before the next one is added to the same contents: adding invalidates the earlier references)
    var HD = xb1_hd(m), gr = jzGrp(S, 'ring'), er = jzAddEllipse(gr, 4, 4, cx, cy);
    var rg = 'var rg=cl((PO-0.86)/0.14);';
    jzSetExpr(er.property('ADBE Vector Ellipse Size'), HD + rg + 'var R=' + jzN(szc) + '*(0.15+0.5*oc(rg));[2*R,2*R]');
    var st = jzAddStroke(gr, m.ctx.sc.accent || '#FFFFFF', 2);
    jzSetExpr(st.property('ADBE Vector Stroke Width'), HD + rg + 'Math.max(1,' + jzN(szc * 0.03) + '*(1-rg))');
    jzSetExpr(jzGX(gr).property('ADBE Vector Group Opacity'), HD + rg + 'rg>0&&rg<1?(1-rg)*100:0');
    var gd = jzGrp(S, 'dot'), ed = jzAddEllipse(gd, 4, 4, cx, cy);
    jzSetExpr(ed.property('ADBE Vector Ellipse Size'), HD + frEx + 'var pu=1+0.25*Math.sin(PO*40)*(1-PO),R=' + jzN(szc) + '*(0.05+0.09*fr)*pu*(1-sm(0.9,1,PO));[2*R,2*R]');
    jzAddFill(gd, m.ctx.sc.accent || '#FFFFFF');
    jzSetExpr(jzGX(gd).property('ADBE Vector Group Opacity'), HD + 'PO>0?100:0');
}
jzReg('exit', 'vacuumOut', { apply: function (m) {
    var L = m.L, G = xb1_geo(m), r = G.r, sz = xb1_sz(m), vert = G.vert, rev = xb1_cutBit(m, 61), W = m.W, H = m.H, s0 = xb1_s0(L), cs = m.c.seed | 0, i, HD = xb1_hd(m);
    if (xb1_single(m, G)) {       // whole glyph layers travel to one screen point
        var mi = m.o.mi || 0, Tc = [rev ? W * 0.08 : W * 0.92, H / 2], P0 = xb1_toComp(L, G.cx, G.cy), ddx = Tc[0] - P0[0], ddy = Tc[1] - P0[1];
        var d0 = Math.sqrt(ddx * ddx + ddy * ddy), o = jzClamp(d0 / (W * 0.85), 0, 1), bend = (xb1_r(cs, mi, 62) - 0.5) * d0 * 0.5, nx = -ddy / Math.max(1, d0), ny = ddx / Math.max(1, d0);
        var ev = 'var vq=win(PO,' + jzN(o) + ',0.6),ve=ic(vq),vb=Math.sin(Math.PI*ve)*' + jzN(bend) + ',vfx=(1-0.85*ve)*(1+1.3*vq*vq),vfy=(1-0.85*ve)*(1-0.4*vq*vq);';
        m.parts.sc.push(XB1_FNS + ev + 'f=[f[0]*vfx,f[1]*vfy];');
        m.parts.pos.push(XB1_FNS + ev + xb1_pivot(m, 'vfx', 'vfy', '0', G.cx, G.cy) + 'd=[d[0]+' + jzN(ddx) + '*ve+' + jzN(nx) + '*vb,d[1]+' + jzN(ddy) + '*ve+' + jzN(ny) + '*vb];');
        xb1_safe(m, ev, 'vq>=1?0:1');
        if (mi === 0 && xb1_main(m)) xb1_vacDot(m, xb1_cshape(m, 'JZ Out Vacuum Dot'), Tc[0], Tc[1], 'var fr=cl(PO*1.2);', sz * s0);
        return;
    }
    var bx0 = r.left, by0 = r.top, bx1 = r.left + r.width, by1 = r.top + r.height, bcx = (bx0 + bx1) / 2, bcy = (by0 + by1) / 2;
    var Tl = vert ? [bcx, rev ? by0 - sz * 0.8 : by1 + sz * 0.8] : [rev ? bx0 - sz * 0.8 : bx1 + sz * 0.8, bcy], dm = 1, VX = [], VY = [], O = [], RS = [], OK = [];
    for (i = 0; i < G.n; i++) { var g = G.g[i], vx = Tl[0] - g.x, vy = Tl[1] - g.y; VX.push(vx); VY.push(vy); dm = Math.max(dm, Math.sqrt(vx * vx + vy * vy)); }
    for (i = 0; i < G.n; i++) {
        var dd = Math.sqrt(VX[i] * VX[i] + VY[i] * VY[i]);
        O.push(jzClamp((dd - sz * 0.8) / Math.max(1, dm - sz * 0.8), 0, 1)); RS.push(xb1_rs(cs + (m.o.mi || 0) * 101, i, 63));
        if (!G.g[i].sp) OK.push(O[i]);
    }
    var K = dm * 1.2 + sz;
    var q = HD + 'var i=textIndex-1,VX=' + xb1_arr(VX) + ',VY=' + xb1_arr(VY) + ',O=' + xb1_arr(O) + ',RS=' + xb1_arr(RS) + ',vx=VX[i]||0,vy=VY[i]||0,dd=Math.max(1,Math.sqrt(vx*vx+vy*vy)),' +
        'q=win(PO,O[i]||0,0.5),e=Math.min(1,iq(q/0.94)),bd=(RS[i]||0)*dd*0.1*Math.sin(Math.PI*e),nx=-vy/dd,ny=vx/dd,st=Math.min(1.8,3*q*q),s=1-0.8*e,sa=s*(1+st),sb=s*(1-0.3*st/1.8);';
    jzAnimator(L, 'JZ Out Suck', [['ADBE Text Position 3D', [K, K, 0]]], q + 'q<=0?[0,0,0]:[(vx*e+nx*bd)/' + jzN(K) + '*100,(vy*e+ny*bd)/' + jzN(K) + '*100,0]');
    jzAnimator(L, 'JZ Out Stretch', [['ADBE Text Scale 3D', [300, 300, 100]]], q + 'q<=0?[0,0,0]:' + (vert ? '[(sb-1)/2*100,(sa-1)/2*100,0]' : '[(sa-1)/2*100,(sb-1)/2*100,0]'));
    jzAnimator(L, 'JZ Out Gone', [['ADBE Text Opacity', 0]], q + 'q>=0.94?100:0');
    xb1_safe(m, '', '1');
    if (!xb1_main(m)) return;
    xb1_vacDot(m, xb1_shape(m, 'JZ Out Vacuum Dot'), Tl[0], Tl[1], 'var O=' + xb1_arr(OK) + ',n=0;for(var k=0;k<O.length;k++)if(win(PO,O[k],0.5)>=0.94)n++;var fr=n/' + Math.max(1, OK.length) + ';', sz);
} });

/* ================================================================ EXITS: particles */

/* ---- sandOut \u2014 \u7802\u306B\u306A\u3063\u3066\u98DB\u3076: weathered into sand: glyphs blur, lift and stream away on the wind from the leading side,
   shedding grains (a particle shape layer: small stretched grains from every glyph, drifting on the same wind) */
jzReg('exit', 'sandOut', { apply: function (m) {
    var L = m.L, G = xb1_geo(m), r = G.r, sz = xb1_sz(m), C = xb1_col(m), vert = G.vert, s0 = xb1_s0(L), single = xb1_single(m, G), cs = m.c.seed | 0, i, j;
    var dir = xb1_cutBit(m, 71) ? 1 : -1, og = xb1_ord(m), bx0 = r.left, bw = Math.max(1, r.width), by0 = r.top, bh = Math.max(1, r.height), HD = xb1_hd(m);
    var posOf = function (x, y) {
        var p = vert ? (y - by0) / bh : (x - bx0) / bw;
        if (!vert && dir > 0) p = 1 - p;
        if (single) p = 0.8 * (dir > 0 ? 1 - og : og) + 0.2 * p;
        return jzClamp(p, 0, 1);
    };
    var D = sz * 3 + m.W * 0.18 / s0, K = D + sz, PS = [];
    for (i = 0; i < G.n; i++) PS.push(posOf(G.g[i].x, G.g[i].y));
    var q = HD + 'var PS=' + xb1_arr(PS) + ',x=cl((PO-(PS[textIndex-1]||0)*0.5-0.04)/0.4),k1=hh(textIndex*8.2+SD);';
    jzAnimator(L, 'JZ Out Blow', [['ADBE Text Position 3D', [K, K, 0]]], q + 'x<=0?[0,0,0]:[' + dir + '*' + jzN(D) + '*x*x/' + jzN(K) + '*100,-(SZ*0.55*x+Math.sin(x*5+k1*6)*SZ*0.07*x)/' + jzN(K) + '*100,0]');
    jzAnimator(L, 'JZ Out Grainy', [['ADBE Text Scale 3D', [300, 300, 100]], ['ADBE Text Blur', [sz * 0.06, sz * 0.06]]], q + 'var s=1-0.4*x;[((s*(1+1.4*x))-1)/2*100,(s-1)/2*100,0]');
    jzAnimator(L, 'JZ Out Fade', [['ADBE Text Opacity', 0]], q + 'x>=1?100:Math.pow(x,0.6)*100');
    xb1_safe(m, '', '1');
    if (!xb1_main(m) || xb1_crowded(m, G) || !G.nk) return;
    // grains: born inside the glyphs, streaming away on the wind, stretched along it, turning and fading
    var S = xb1_shape(m, 'JZ Out Sand'), vis = [], nG;
    try { S.comment = ''; } catch (e0) {}       // the grains are pieces of the text: they stay in the ghost passes
    for (i = 0; i < G.n; i++) if (!G.g[i].sp) vis.push(G.g[i]);
    nG = single ? 10 : Math.min(96, vis.length * 9);
    for (j = 0; j < nG; j++) {
        var g = vis[j % vis.length], gx = g.x + xb1_rs(cs, j, 84) * g.w * 0.38, gy = g.y + xb1_rs(cs, j, 85) * G.fs * 0.38;
        var t0 = posOf(gx, gy) * 0.5 + xb1_r(cs, j, 81) * 0.18, k1 = xb1_r(cs, j, 82), gd = sz * (0.1 + 0.14 * xb1_r(cs, j, 86));
        var hx = 'var x=(PO-' + xb1_n(t0) + ')/0.32,xc=cl(x);';
        var gg = jzGrp(S, 'grain ' + (j + 1)), rc = jzAddRect(gg, gd, gd * 0.45, 0);
        jzSetExpr(rc.property('ADBE Vector Rect Size'), HD + hx + '[' + jzN(gd) + '*(1+2.4*xc)*(1-0.55*xc),' + jzN(gd * 0.45) + '*(1-0.55*xc)]');
        jzAddFill(gg, C.c0);
        jzSetExpr(jzGX(gg).property('ADBE Vector Position'), HD + hx + '[' + jzN(gx) + '+' + dir + '*' + jzN(D) + '*xc*xc,' + jzN(gy) + '-(SZ*0.55*xc+Math.sin(xc*5+' + jzN(k1 * 6) + ')*SZ*0.07*xc)]');
        jzSetExpr(jzGX(gg).property('ADBE Vector Rotation'), HD + hx + jzN(xb1_rs(cs, j, 83) * 140) + '*xc*0.3');
        jzSetExpr(jzGX(gg).property('ADBE Vector Group Opacity'), HD + hx + 'x>0&&x<1?(1-x*x)*100:0');
    }
} });

/* ---- shredOut \u2014 \u30B7\u30E5\u30EC\u30C3\u30C0\u30FC: the text is fed down into a slot (an ink bar); above it the text is intact (L, clipped by a mask
   that follows the slot), below it comes out as strips that fan out and drop (a twin with the part below the slot, strip gaps as
   masks, widened about the slot) */
jzReg('exit', 'shredOut', { apply: function (m) {
    var L = m.L, G = xb1_geo(m), r = G.r, sz = xb1_sz(m), C = xb1_col(m), H = m.H, s0 = xb1_s0(L), X = xb1_xf(L), single = xb1_single(m, G), k;
    var pad = sz * 0.06, bx0 = r.left - pad, by0 = r.top - pad, bx1 = r.left + r.width + pad, by1 = r.top + r.height + pad, bw = bx1 - bx0, big = (r.width + r.height) * 3 + sz * 10;
    var slot = single ? Math.max(by1 + sz * 0.1, xb1_fromComp(L, m.W / 2, H * 0.66)[1]) : by1 + sz * 0.12, travel = slot - by0 + sz * 0.1;
    var ev = 'var hf=cl((PO-0.08)/0.72),hD=' + jzN(travel) + '*(0.2*hf*hf+0.8*hf),hv=cl((PO-0.72)/0.28),hdr=' + jzN(H * 0.9 / s0) + '*hv*hv,hc=cl(Math.max(0,' + jzN(by1) + '+hD-' + xb1_n(slot) + ')/' + jzN(sz * 1.5) + ');';
    var HD = xb1_hd(m) + ev, rr = X.r * Math.PI / 180;
    m.parts.pos.push(XB1_FNS + ev + 'd=[d[0]-' + xb1_n(Math.sin(rr) * s0) + '*hD,d[1]+' + jzN(Math.cos(rr) * s0) + '*hD];');
    var crowd = xb1_crowded(m, G), T = crowd ? null : xb1_twin(m, 'JZ Out Shreds', ev, 'PO>0?1-sm(0.8,0.97,PO):0');
    var mk = xb1_mask(L, xb1_rpts(bx0 - big, by0 - big, bx1 + big, slot), 'add', 'JZ Out Slot');
    jzSetExpr(mk.property('ADBE Mask Offset'), HD + 'PO>0?-hD:1e4');
    xb1_safe(m, ev, '1');
    if (T) {
        jzNoGhost(T);
        var mb = xb1_mask(T, xb1_rpts(bx0 - big, slot, bx1 + big, by1 + big), 'add', 'JZ Out Below Slot');
        jzSetExpr(mb.property('ADBE Mask Offset'), HD + 'hD');
        var n = jzClamp(Math.round(bw / (sz * 0.2)), 5, 20), sw = bw / n, xc = (bx0 + bx1) / 2;
        for (k = 0; k < n; k++) xb1_mask(T, xb1_rpts(bx0 + k * sw + sw * 0.74, by0 - big, bx0 + (k + 1) * sw, by1 + big), 'sub', 'JZ Out Strip Gap ' + (k + 1));
        var tr = T.property('ADBE Transform Group');
        jzSetExpr(tr.property('ADBE Anchor Point'), HD + '[' + jzN(xc) + ',' + jzN(slot) + '-hD]');
        jzSetExpr(tr.property('ADBE Position'), HD + '[' + jzN(xc) + ',' + jzN(slot) + '-hD+hdr]');
        jzSetExpr(tr.property('ADBE Scale'), HD + '[100*(1+0.28*hc),100]');
        var ww = jzEffect(T, 'ADBE Wave Warp', 'JZ Out Curl');
        jzEP(ww, 1, 1); jzEP(ww, 3, sz * 2.4); jzEP(ww, 4, 90); jzEP(ww, 5, 0); jzEP(ww, 6, 1);
        jzEX(ww, 2, HD + 'SZ*0.05*hc'); jzEX(ww, 7, HD + 'PO*520');
    }
    if (!xb1_main(m)) return;
    // the slot: an ink bar fixed on the screen
    var P = xb1_toComp(L, (bx0 + bx1) / 2, slot), S = xb1_cshape(m, 'JZ Out Slot Bar'), g = jzGrp(S, 'bar'), rc = jzAddRect(g, 10, 10, 0);
    jzSetExpr(rc.property('ADBE Vector Rect Size'), HD + '[' + jzN((bw + sz * 0.5) * s0) + '*oc(sm(0,0.12,PO)),' + jzN(Math.max(2 * m.u, sz * s0 * 0.05)) + ']');
    jzAddFill(g, C.ink); jzGX(g).property('ADBE Vector Position').setValue(P);
    jzSetExpr(jzXf(S, 'ADBE Opacity'), HD + 'PO>0?sm(0,0.1,PO)*(1-sm(0.82,0.98,PO))*100:0');
} });

/* ================================================================ EXITS: mechanics */

/* ---- dominoOut \u2014 \u30C9\u30DF\u30CE\u5012\u3057: each glyph topples over its bottom corner onto the next, in a chain; the fallen row sinks and fades */
jzReg('exit', 'dominoOut', { apply: function (m) {
    var L = m.L, G = xb1_geo(m), fs = G.fs, rev = xb1_cutBit(m, 91), sg = rev ? -1 : 1, i, Wd = [];
    for (i = 0; i < G.n; i++) Wd.push(G.g[i].w);
    var ordE = xb1_single(m, G) ? jzN(rev ? 1 - xb1_ord(m) : xb1_ord(m)) : (rev ? '1-' : '') + '(textTotal>1?(textIndex-1)/(textTotal-1):0)', K = fs * 2;
    var q = xb1_hd(m) + 'var W=' + xb1_arr(Wd) + ',q=win(PO,' + ordE + ',0.55),u=(q-0.62)/0.38,th=q<0.62?90*ic(q/0.62):90-10*Math.sin(Math.PI*Math.min(1,u*1.6))*(1-u),' +
        'a=' + sg + '*th*Math.PI/180,px=' + sg + '*(W[textIndex-1]||SZ)/2,py=' + jzN(fs * 0.12) + ',dx=q>0?px-(Math.cos(a)*px-Math.sin(a)*py):0,dy=q>0?py-(Math.sin(a)*px+Math.cos(a)*py):0,sk=SZ*0.9*iq(cl((PO-0.7)/0.3));';
    jzAnimator(L, 'JZ Out Topple', [['ADBE Text Rotation', 90]], q + 'q<=0?0:' + sg + '*th/90*100');
    jzAnimator(L, 'JZ Out Pivot', [['ADBE Text Position 3D', [K, K, 0]]], q + '[dx/' + jzN(K) + '*100,(dy+sk)/' + jzN(K) + '*100,0]');
    xb1_safe(m, '', '1-sm(0.74,0.98,PO)');
} });

/* ---- hingeOut \u2014 \u7247\u7559\u3081\u304C\u5916\u308C\u308B: one pin pops off; the text swings down on the other pin with a damped swing, settles, then
   drops away (layer rotation about the pin + fall), pins drawn in the accent colour */
jzReg('exit', 'hingeOut', { apply: function (m) {
    var L = m.L, G = xb1_geo(m), r = G.r, sz = xb1_sz(m), C = xb1_col(m), H = m.H, s0 = xb1_s0(L), vert = G.vert, cs = m.c.seed | 0;
    var pad = sz * 0.02, x0 = r.left - pad, y0 = r.top - pad, x1 = r.left + r.width + pad, y1 = r.top + r.height + pad, bw = x1 - x0, bh = y1 - y0;
    var left = !xb1_cutBit(m, 102), sg = left ? 1 : -1;
    var ax = (left ? x0 : x1) + sg * Math.min(sz * 0.14, bw * 0.2), ay = y0 + Math.min(sz * 0.12, bh * 0.2), reach = Math.max(1, bw - sz * 0.14);
    var hang = vert ? 24 : Math.min(58, Math.max(14, Math.asin(jzClamp(H * 0.3 / (reach * s0), 0, 1)) * 180 / Math.PI));
    var qx = xb1_single(m, G) ? 'win(PO,' + jzN(xb1_r(cs, m.o.mi || 0, 101)) + ',0.3)' : 'PO';
    var ev = 'var hq=' + qx + ',hu=cl(hq/0.64),hv=cl((hq-0.64)/0.36),hth=' + jzN(hang) + '*(1-Math.exp(-hu*4.5)*Math.cos(hu*12))+30*hv*hv;', HD = xb1_hd(m) + ev;
    var Q = xb1_toComp(L, ax, ay), bxp = left ? x1 - sz * 0.14 : x0 + sz * 0.14, Q1 = xb1_toComp(L, bxp, ay);
    xb1_rigid(m, XB1_FNS + ev, sg + '*hth', Q, [jzN(sg * sz * s0 * 0.4) + '*hv', jzN(H * 1.25) + '*hv*hv']);
    xb1_safe(m, ev, '1-sm(0.9,1,hq)');
    if (!xb1_main(m)) return;
    var S = xb1_cshape(m, 'JZ Out Pins'), rp = Math.max(2 * m.u, sz * s0 * 0.045), szc = sz * s0;
    var g1 = jzGrp(S, 'popped pin'); jzAddEllipse(g1, rp * 2, rp * 2); jzAddFill(g1, C.acc);
    jzSetExpr(jzGX(g1).property('ADBE Vector Position'), HD + 'var fl=cl(hq/0.16);[' + jzN(Q1[0]) + '+' + jzN(sg * szc * 0.5) + '*fl,' + jzN(Q1[1]) + '-' + xb1_n(szc * 0.6) + '*fl+' + jzN(szc * 1.6) + '*fl*fl]');
    jzSetExpr(jzGX(g1).property('ADBE Vector Group Opacity'), HD + 'var fl=cl(hq/0.16);hq>0&&fl<1?(1-fl)*100:0');
    var g0 = jzGrp(S, 'pin'); jzAddEllipse(g0, rp * 2, rp * 2); jzAddFill(g0, C.acc);
    jzGX(g0).property('ADBE Vector Position').setValue(Q);
    jzSetExpr(jzGX(g0).property('ADBE Vector Group Opacity'), HD + 'hq>0?(1-sm(0.05,0.25,hv))*100:0');
} });

/* ================================================================ HOLDS */

/* ---- glowFlicker \u2014 \u706F\u706B\u306E\u3086\u3089\u304E: a warm candle halo (drop shadow glow) that breathes and gutters irregularly, a faint warm tint
   and a tiny upward lick of the glyphs */
jzReg('hold', 'glowFlicker', { apply: function (m) {
    var L = m.L, C = xb1_col(m), glow = C.dark ? jzMixHex(C.acc, '#FFFFFF', 0.3) : C.acc;
    var ev = XB1_MK + 'var gf=0.6+0.28*noise([time*3.3,SD])+0.12*noise([time*11,SD+5]),glv=cl(gf*(1-0.45*sm(0.35,0.8,noise([time*1.1,SD+9]))));';
    var HD = xb1_hd(m) + ev;
    var ds = jzEffect(L, 'ADBE Drop Shadow', 'JZ Hold Candle');
    jzEP(ds, 1, jzHex(glow)); jzEP(ds, 3, 0);
    jzEX(ds, 2, HD + jzN((C.dark ? 0.9 : 0.6) * 255) + '*cl(HK*4)');
    jzEX(ds, 4, HD + 'SZ*0.02*glv*HKK'); jzEX(ds, 5, HD + 'SZ*(0.06+0.3*glv)*HKK');
    m.parts.op.push(XB1_FNS + ev + 'f*=1-0.16*(1-glv)*HKK;');
    jzAnimator(L, 'JZ Hold Warm', [['ADBE Text Fill Color', jzHex(C.acc)]], HD + '10*glv*HKK');
    var lk = HD + 'var st=0.025*HK*(0.5+0.5*noise([time*6+textIndex*2.3,SD+textIndex]));';
    jzAnimator(L, 'JZ Hold Lick', [['ADBE Text Scale 3D', [100, 110, 100]]], lk + '[0,st/0.1*100,0]');
    jzAnimator(L, 'JZ Hold Lick Base', [['ADBE Text Position 3D', [0, xb1_sz(m) * 0.1, 0]]], lk + '[0,-st/0.1*50,0]');
} });

/* ---- windGust \u2014 \u7A81\u98A8: every ~2.2 s a gust sweeps along the line: the glyphs are pushed, lean (skew) and spring back */
jzReg('hold', 'windGust', { apply: function (m) {
    var L = m.L, G = xb1_geo(m), sz = xb1_sz(m), cs = (m.c.seed | 0) % 99991;
    var ordE = xb1_single(m, G) ? jzN(xb1_ord(m)) : '(textTotal>1?(textIndex-1)/(textTotal-1):0)';
    var q = xb1_hd(m) + XB1_MK + 'var T=time-0.5-' + xb1_n((cs % 5) * 0.06) + ',cy=Math.floor(T/2.2),si=T-cy*2.2,dr=hh(cy*7.13+' + cs + ')<0.5?1:-1,str=0.7+0.3*hh(cy*3.71+' + cs + '+2),o=' + ordE + ',tau=si-(dr>0?o:1-o)*0.4,' +
        'rr=tau<=0||tau>1.7?0:(tau<0.25?sm(0,0.25,tau):(tau<0.6?1+0.08*Math.sin((tau-0.25)*30):Math.cos((tau-0.6)*9)*Math.exp(-(tau-0.6)*4))),w=dr*rr*HK*str/1.8*100;';
    jzAnimator(L, 'JZ Hold Gust', [['ADBE Text Position 3D', [sz * 0.12 * 1.8, 0, 0]], ['ADBE Text Rotation', 3 * 1.8]], q + 'w');
    jzAnimator(L, 'JZ Hold Lean', [['ADBE Text Skew', 18 * 1.8]], q + '-w');
} });

/* ---- dangle \u2014 \u3076\u3089\u4E0B\u304C\u308A: every glyph hangs from its own top edge and swings like a small pendulum at its own tempo */
jzReg('hold', 'dangle', { apply: function (m) {
    var L = m.L, G = xb1_geo(m), a = G.fs * 0.5, K = a * 0.25;
    var q = xb1_hd(m) + XB1_MK + 'var w=6.2832*(0.5+0.28*hh(textIndex*61.1+SD)),ph=hh(textIndex*61.7+SD)*6.2832,th=(6.5*Math.sin(w*time+ph)+1.6*Math.sin(w*2.7*time+ph*2))*HK,tr=th*Math.PI/180;';
    jzAnimator(L, 'JZ Hold Swing', [['ADBE Text Rotation', 14]], q + 'th/14*100');
    jzAnimator(L, 'JZ Hold Hang', [['ADBE Text Position 3D', [K, K, 0]]], q + '[-' + xb1_n(a) + '*Math.sin(tr)/' + jzN(K) + '*100,' + jzN(a) + '*(Math.cos(tr)-1)/' + jzN(K) + '*100,0]');
} });

/* ---- eqBounce \u2014 \u97F3\u5727\u3067\u4F38\u3073\u308B: glyphs stretch up from the baseline like level-meter bars (noise-driven level, as the browser does
   without audio energy); peaks light up in the accent colour */
jzReg('hold', 'eqBounce', { apply: function (m) {
    var L = m.L, G = xb1_geo(m), C = xb1_col(m), mi = m.o.mi || 0, vert = G.vert;
    var q = xb1_hd(m) + XB1_MK + 'var base=0.45+0.4*noise([time*2.6,SD]),bd=cl(base*(0.3+0.7*(0.5+0.5*noise([time*6.5+(textIndex-1+' + mi + ')*1.9,SD+3])))*1.15),st=0.32*bd*HK/0.6*100;';
    jzAnimator(L, 'JZ Hold Level', [['ADBE Text Scale 3D', vert ? [160, 100, 100] : [100, 160, 100]]], q + (vert ? '[st,0,0]' : '[0,st,0]'));
    if (!vert) jzAnimator(L, 'JZ Hold Level Base', [['ADBE Text Position 3D', [0, G.fs * 0.3, 0]]], q + '[0,-st,0]');
    jzAnimator(L, 'JZ Hold Peak', [['ADBE Text Fill Color', jzHex(C.acc)]], q + 'bd>0.72?(bd-0.72)/0.28*0.7*HKK*100:0');
} });

/* ---- flashBox \u2014 \u62CD\u3067\u53CD\u8EE2: on every beat (0.62 s) one glyph is punched out in inverse video: an accent block pops behind it and
   the glyph turns to the background colour */
jzReg('hold', 'flashBox', { apply: function (m) {
    var L = m.L, G = xb1_geo(m), C = xb1_col(m), single = xb1_single(m, G), N = xb1_cutN(m), cs = (m.c.seed | 0) % 99991, i, VI = [], X = [], Y = [], WW = [];
    for (i = 0; i < G.n; i++) if (!G.g[i].sp) { VI.push(i + 1); X.push(G.g[i].x); Y.push(G.g[i].y); WW.push(G.vert ? G.fs : G.g[i].w); }
    if (!VI.length) return;
    var knock = jzContrast(C.bg, C.acc) >= 2.5 ? C.bg : jzOnCol(C.sc, C.acc);
    var ev = XB1_MK + 'var fbi=Math.floor(time/0.62),fbu=(time-fbi*0.62)/0.372,fbp=Math.floor(hh(fbi*7.7+' + cs + '+0.5)*' + N + ')%' + N + ',fbk=' + (single ? '0' : 'fbp%' + VI.length) +
        ',VI=' + xb1_arr(VI) + ',fbon=HK>=0.35&&fbu<1&&time>=0' + (single ? '&&fbp==' + (m.o.mi || 0) : '') + ';';
    var HD = xb1_hd(m) + ev;
    jzAnimator(L, 'JZ Hold Knockout', [['ADBE Text Fill Color', jzHex(knock)], ['ADBE Text Stroke Color', jzHex(knock)]], HD + 'fbon&&textIndex==VI[fbk]?100:0');
    jzAnimator(L, 'JZ Hold Punch', [['ADBE Text Scale 3D', [105, 105, 100]]], HD + 'fbon&&textIndex==VI[fbk]?(1-fbu)*100:0');
    if (!xb1_main(m) || xb1_crowded(m, G)) return;
    var S = xb1_shape(m, 'JZ Hold Flash Box', null, true, true), g = jzGrp(S, 'box'), rc = jzAddRect(g, 10, 10, 0);
    var at = 'var X=' + xb1_arr(X) + ',Y=' + xb1_arr(Y) + ',WW=' + xb1_arr(WW) + ',pop=ob(cl(fbu/0.18),2.2);';
    jzSetExpr(rc.property('ADBE Vector Rect Size'), HD + at + '[(WW[fbk]||SZ)*1.06*pop,' + jzN(G.fs) + '*1.06*pop]');
    jzAddFill(g, C.acc);           // (after the rect is configured: adding the fill invalidates the rect reference)
    jzSetExpr(jzGX(g).property('ADBE Vector Position'), HD + at + '[X[fbk]||0,Y[fbk]||0]');
    jzSetExpr(jzGX(g).property('ADBE Vector Group Opacity'), HD + 'fbon&&PO<=0?100:0');
} });

/* ---- glintSweep \u2014 \u5149\u6CA2: every 2.4 s a slanted band of light slides across the letters (a highlight-coloured twin seen through two
   slanted bands of a moving matte) */
jzReg('hold', 'glintSweep', { apply: function (m) {
    var L = m.L, G = xb1_geo(m), r = G.r, sz = xb1_sz(m), C = xb1_col(m), mi = m.o.mi || 0, i;
    var pad = sz * 0.2, x0 = r.left - pad, y0 = r.top - pad, x1 = r.left + r.width + pad, y1 = r.top + r.height + pad, hh2 = (y1 - y0) / 2, yc = (y0 + y1) / 2;
    var gc = jzLum(C.c0) > 0.72 ? C.acc : jzMixHex(C.c0, '#FFFFFF', 0.85), bw = sz * 0.34, sl = (y1 - y0) * 0.45;
    var ev = XB1_MK + 'var gT=time-0.5-' + xb1_n(mi * 0.04) + ',gu=((gT%2.4)+2.4)%2.4/0.75,gx=' + jzN(x0 - bw - sl) + '+' + jzN((x1 - x0) + 2 * (bw + sl)) + '*ios(gu),ga=Math.min(1,HK)*0.9;';
    var HD = xb1_hd(m) + ev;
    if (xb1_crowded(m, G) || !xb1_main(m)) {      // per glyph: lit while the band passes over it
        var XX = [], YY = [];
        for (i = 0; i < G.n; i++) { XX.push(G.g[i].x); YY.push(G.g[i].y); }
        jzAnimator(L, 'JZ Hold Glint', [['ADBE Text Fill Color', jzHex(gc)]], HD + 'var X=' + xb1_arr(XX) + ',Y=' + xb1_arr(YY) + ',z=((X[textIndex-1]||0)-gx-' + xb1_n(sl) + '*(' + jzN(yc) + '-(Y[textIndex-1]||0))/' + jzN(hh2) + ')/' + jzN(bw) + ';gu<1&&PO<=0?Math.max(0,1-Math.abs(2*z))*ga*100:0');
        return;
    }
    var T = xb1_twin(m, 'JZ Hold Glint', ev, 'PO<=0&&gu<1&&HK>0.05?ga:0', null, true);
    jzNoGhost(T);
    jzAnimator(T, 'JZ Hold Glint', [['ADBE Text Fill Color', jzHex(gc)], ['ADBE Text Stroke Color', jzHex(gc)]], '100');
    var M = xb1_matteShape(m, T, 'JZ Hold Glint Matte', false, true), g = jzGrp(M, 'glint');
    var band = function (a, w) { jzAddPath(g, [[a + sl, y0], [a + w + sl, y0], [a + w - sl, y1], [a - sl, y1]], true); };
    band(-bw / 2, bw); band(bw * 0.75, bw * 0.28);
    jzAddFill(g, '#FFFFFF');
    jzSetExpr(jzGX(g).property('ADBE Vector Position'), HD + '[gx,0]');
} });

/* ---- flipSwap \u2014 \u6642\u3005\u88CF\u8FD4\u308B: every 1.6 s one glyph flips over like a card, shows another character on its back (accent colour,
   Character Offset) and flips home */
jzReg('hold', 'flipSwap', { apply: function (m) {
    var L = m.L, G = xb1_geo(m), single = xb1_single(m, G), N = xb1_cutN(m), cs = (m.c.seed | 0) % 99991, C = xb1_col(m), i, VI = [];
    for (i = 0; i < G.n; i++) if (!G.g[i].sp) VI.push(i + 1);
    if (!VI.length) return;
    var q = xb1_hd(m) + XB1_MK + 'var fT=time-0.45,fc=Math.floor(fT/1.6),fu=(fT-fc*1.6)/0.95,fp=Math.floor(hh(fc*5.3+' + cs + '+0.25)*' + N + ')%' + N + ',fk=' + (single ? '0' : 'fp%' + VI.length) + ',VI=' + xb1_arr(VI) + ',' +
        'on=HK>=0.5&&fu<1&&fT>=0' + (single ? '&&fp==' + (m.o.mi || 0) : '') + '&&textIndex==VI[fk],sx=1,alt=0;' +
        'if(fu<0.18){sx=Math.cos(fu/0.18*Math.PI/2);}else if(fu<0.36){sx=Math.sin((fu-0.18)/0.18*Math.PI/2);alt=1;}else if(fu<0.64){alt=1;}else if(fu<0.82){sx=Math.cos((fu-0.64)/0.18*Math.PI/2);alt=1;}else{sx=Math.sin((fu-0.82)/0.18*Math.PI/2);}' +
        'sx=Math.max(0.02,sx);';
    jzAnimator(L, 'JZ Hold Flip', [['ADBE Text Scale 3D', [0, 100, 100]]], q + 'on?[(1-sx)*100,0,0]:[0,0,0]');
    jzAnimator(L, 'JZ Hold Back', [['ADBE Text Character Offset', 40], ['ADBE Text Fill Color', jzHex(C.acc)]], q + 'on&&alt?(1+Math.floor(hh(fc*9.1+' + cs + ')*38))/40*100:0');
} });

/* ---- shadowSway \u2014 \u5F71\u304C\u63FA\u308C\u308B: a long soft shadow that swings slowly as if the light source were moving (two stacked drop shadows
   along the swinging angle: a tight base and a long soft tail) */
jzReg('hold', 'shadowSway', { apply: function (m) {
    var L = m.L, C = xb1_col(m), col = C.dark ? jzMixHex(C.bg, C.acc, 0.42) : jzMixHex(C.bg, C.c0, 0.3), ph = (m.c.seed | 0) % 10;
    var ev = XB1_MK + 'var sa=60+45*Math.sin(time*6.2832/5.5+' + ph + '),sL=SZ*(0.12+0.05*Math.sin(time*6.2832/3.3+1))*Math.min(1.3,HK);';
    var HD = xb1_hd(m) + ev, k, sh = [[0.45, 0.3, 0.9], [1, 0.75, 0.55]];
    for (k = 0; k < sh.length; k++) {
        var ds = jzEffect(L, 'ADBE Drop Shadow', 'JZ Hold Sway ' + (k + 1));
        jzEP(ds, 1, jzHex(col));
        jzEX(ds, 2, HD + jzN(sh[k][2] * 255) + '*cl(HK*5)'); jzEX(ds, 3, HD + 'sa+90');
        jzEX(ds, 4, HD + 'sL*' + jzN(sh[k][0])); jzEX(ds, 5, HD + 'sL*' + jzN(sh[k][1]));
    }
} });

/* ---- magnetJiggle \u2014 \u78C1\u529B: an invisible magnet wanders round the text; nearby glyphs lean and buzz towards it */
jzReg('hold', 'magnetJiggle', { apply: function (m) {
    var L = m.L, G = xb1_geo(m), r = G.r, sz = xb1_sz(m), i, sd9 = ((m.c.seed | 0) + (m.o.mi || 0) * 101) % 9;
    if (xb1_single(m, G)) {       // the magnet wanders over the screen; the glyph layer is pulled as a whole
        var W = m.W, H = m.H, s0 = xb1_s0(L), P0 = xb1_toComp(L, G.cx, G.cy), szc = sz * s0;
        var ev = XB1_MK + 'var mx=' + jzN(W / 2) + '+Math.sin(time*0.9+' + ((m.c.seed | 0) % 9) + ')*' + jzN(W * 0.36) + '-' + xb1_n(P0[0]) + ',my=' + jzN(H / 2) + '+Math.sin(time*1.7+1)*' + jzN(H * 0.12) + '-' + xb1_n(P0[1]) + ',' +
            'md=Math.max(1,Math.sqrt(mx*mx+my*my)),mf=1/(1+Math.pow(md/' + jzN(szc * 1.1) + ',2)),mp=' + jzN(szc * 0.2) + '*mf*HK,mb=mf>0.35?(hh(Math.floor(time*24)*13+SD)*2-1)*' + jzN(szc * 0.014) + '*HK*mf:0;';
        m.parts.pos.push(XB1_FNS + ev + 'd=[d[0]+mx/md*mp+mb,d[1]+my/md*mp];');
        m.parts.rot.push(XB1_FNS + ev + 'r+=mx/md*7*mf*HK;');
        return;
    }
    var X = [], Y = [], K = sz * 0.4;
    for (i = 0; i < G.n; i++) { X.push(G.g[i].x); Y.push(G.g[i].y); }
    var q = xb1_hd(m) + XB1_MK + 'var X=' + xb1_arr(X) + ',Y=' + xb1_arr(Y) + ',mx=' + jzN(r.left + r.width / 2) + '+Math.sin(time*0.9+' + sd9 + ')*' + jzN(r.width * 0.5) + '-(X[textIndex-1]||0),' +
        'my=' + jzN(r.top + r.height / 2) + '+Math.sin(time*1.7+1)*' + jzN(r.height * 0.9) + '-(Y[textIndex-1]||0),md=Math.max(1,Math.sqrt(mx*mx+my*my)),mf=1/(1+Math.pow(md/(SZ*1.1),2)),mp=SZ*0.2*mf*HK,' +
        'mb=mf>0.35?(hh(Math.floor(time*24)*13+textIndex*7+SD)*2-1)*SZ*0.014*HK*mf:0;';
    jzAnimator(L, 'JZ Hold Pull', [['ADBE Text Position 3D', [K, K, 0]]], q + '[(mx/md*mp+mb)/' + jzN(K) + '*100,my/md*mp/' + jzN(K) + '*100,0]');
    jzAnimator(L, 'JZ Hold Lean', [['ADBE Text Rotation', 11.2]], q + 'mx/md*7*mf*HK/11.2*100');
} });

/* ---- typeRattle \u2014 \u30BF\u30A4\u30D7\u306E\u9707\u3048: an uneven, hand-struck baseline; on each beat (0.45 s) a few keys are struck again: a dip, then
   a new resting place */
jzReg('hold', 'typeRattle', { apply: function (m) {
    var L = m.L, sz = xb1_sz(m), K = sz * 0.14;
    var q = xb1_hd(m) + XB1_MK + 'var bi=Math.floor(time/0.45),bs=time-bi*0.45,ep=-99;for(var b=bi;b>bi-7;b--){if(hh(b*3.17+textIndex*7.31+SD)<0.3){ep=b;break;}}' +
        'var hit=ep==bi&&bs<0.1?1-bs/0.1:0,r1=hh(ep*1.31+textIndex*5.7+SD+0.1)*2-1,r2=hh(ep*2.17+textIndex*3.3+SD+0.2)*2-1,r3=hh(ep*4.1+textIndex*1.9+SD+0.3)*2-1,r4=hh(ep*0.77+textIndex*8.9+SD+0.4);';
    jzAnimator(L, 'JZ Hold Strike', [['ADBE Text Position 3D', [K, K, 0]]], q + '[r2*SZ*0.01*HK/' + jzN(K) + '*100,(r1*SZ*0.045*HK+hit*SZ*0.04*HK)/' + jzN(K) + '*100,0]');
    jzAnimator(L, 'JZ Hold Tilt', [['ADBE Text Rotation', 5.2]], q + 'r3*3.2*HK/5.2*100');
    jzAnimator(L, 'JZ Hold Ink', [['ADBE Text Opacity', 0]], q + '(0.2*r4*HKK+0.15*hit*HKK)*100');
} });

/* ---- focusRack \u2014 \u30D4\u30F3\u30C8\u9001\u308A: a plane of focus drifts along the line; glyphs away from it soften, dim and grow slightly */
jzReg('hold', 'focusRack', { apply: function (m) {
    var L = m.L, G = xb1_geo(m), sz = xb1_sz(m), B = Math.min(sz * 0.065, 18 * m.u), cs7 = (m.c.seed | 0) % 7;
    var ordE = xb1_single(m, G) ? jzN(xb1_ord(m)) : '(textTotal>1?(textIndex-1)/(textTotal-1):0)';
    var q = xb1_hd(m) + XB1_MK + 'var fp=0.5+0.62*Math.sin(time*6.2832/4.6+' + cs7 + '),df=cl((Math.abs(' + ordE + '-fp)-0.1)/0.5);';
    jzAnimator(L, 'JZ Hold Defocus', [['ADBE Text Blur', [B, B]]], q + 'df*df*HKK*100');
    jzAnimator(L, 'JZ Hold Dim', [['ADBE Text Opacity', 0]], q + '28*df*HKK');
    jzAnimator(L, 'JZ Hold Swell', [['ADBE Text Scale 3D', [110, 110, 100]]], q + 'var a=0.035*df*HK/0.1*100;[a,a,0]');
} });

/* ---- pluckString \u2014 \u5F26\u306E\u632F\u52D5: the line vibrates as a standing wave between its ends, plucked again every 2.4 s and ringing down */
jzReg('hold', 'pluckString', { apply: function (m) {
    var L = m.L, G = xb1_geo(m), sz = xb1_sz(m), vert = G.vert, single = xb1_single(m, G), N = xb1_cutN(m), i, XS = [], K = sz * 0.18 * 1.6 * 1.3;
    for (i = 0; i < G.n; i++) XS.push(single ? ((m.o.mi || 0) + 1) / (N + 1) : vert ? (G.g[i].li + 1) / (G.nL + 1) : (G.g[i].ci + 1) / (G.g[i].nl + 1));
    var q = xb1_hd(m) + XB1_MK + 'var XS=' + xb1_arr(XS) + ',tau=(((time-0.45)%2.4)+2.4)%2.4,A=SZ*0.18*HK*Math.exp(-tau*1.7)*Math.min(1,tau/0.04),x=XS[textIndex-1]||0.5,' +
        'y=A*(Math.sin(Math.PI*x)*Math.cos(tau*6.2832*3.2)+0.3*Math.sin(6.2832*x)*Math.cos(tau*6.2832*6.6+1)),a=y/' + jzN(K) + '*100;';
    jzAnimator(L, 'JZ Hold Pluck', [['ADBE Text Position 3D', vert ? [K, 0, 0] : [0, K, 0]]], q + '[a,a,0]');
} });

// ================================================================ pack exit B part 2 (AE port of src/11p_exitB.js)
// rocketOff bounceOff balloonOff deflateOut hazeOut glassBreak zipOut clapShut lampOff slotOut clockOut matrixOut tornadoOut
// rollUpOut snakeOut flutterOut rollOff fanClose rgbSplitOut shockOut floodOut slashOut mosaicOut scribbleOut candleOut
// Exit progress = PO (0 \u2192 1, everything gone at 1). Per-glyph motion = text animators with Expression Selectors; screen-space
// directions (down / right, distances to the frame edge) are converted to the text layer's own space at build time.
// Clips are layer masks on the lyric (static shapes, moved by mask expansion, active only while the exit runs) or, where a
// mask cannot draw the shape (per-glyph clock sweeps), an alpha track matte on an exit-only copy of the lyric (never on the
// lyric itself, so treatment copies made later keep working). Helper graphics (flames, strings, sliders, rings, water line,
// scribbles, smoke \u2026) are shape layers parented to the text layer, alive only during the exit and kept out of the ghosts.
// Copies ("twins") are duplicates of the lyric made before the exit's own animators, parented to it, visible only in the exit.

// ---------------------------------------------------------------- shared helpers (xb2_*)
// easings not in JZ_FNS: ios inOutSine, io3 inOutCubic, sm smoothstep(a, b, x), wn browser win(p, o, spread), n1 smooth 1-D noise (-1..1)
var XB2_FNS = 'function ios(x){x=cl(x);return -(Math.cos(Math.PI*x)-1)/2;}' +
    'function io3(x){x=cl(x);return x<0.5?4*x*x*x:1-Math.pow(-2*x+2,3)/2;}' +
    'function sm(a,b,x){x=cl((x-a)/(b-a));return x*x*(3-2*x);}' +
    'function wn(p,o,s){return cl((p-o*s)/(1-s));}' +
    'function n1(x,s){var i=Math.floor(x),f=x-i,u=f*f*(3-2*f);return (hh(i*1.37+s)*(1-u)+hh(i*1.37+1.37+s)*u)*2-1;}\n';
var XB2_BIG = 30000;

// size reference = the text document's real (fitted) font size, like the browser's it.size (m.size / SZ is the size the layout asked for)
function xb2_sz(m) { var td = xb2_td(m.L); return td && td.fontSize > 0 ? td.fontSize : m.size; }
function xb2_hd(m) { return m.HD + XB2_FNS + 'SZ=' + jzN(xb2_sz(m)) + ';\n'; }
function xb2_T(m) { var c = m.c; return { OS: c.dur - (c.outDur || 0), OD: Math.max(0.001, c.outDur || 0), DUR: c.dur }; }
function xb2_n(x) { return '(' + jzN(x) + ')'; }
function xb2_arr(a) { var s = [], i; for (i = 0; i < a.length; i++) s.push(jzN(a[i])); return '[' + s.join(',') + ']'; }
function xb2_pt(x, y) { return '[' + jzN(x) + ',' + jzN(y) + ']'; }
// the browser's J.h / J.r (seeded directions match the web version)
function xb2_h(a, b, c, d, e) {
    var h = 0x9e3779b9 ^ (a | 0);
    h = jzImul(h ^ (h >>> 16), 0x85ebca6b);
    h = (h + jzImul((b | 0) + 0x632be5ab, 0xc2b2ae35)) | 0;
    h = jzImul(h ^ (h >>> 13), 0xc2b2ae35);
    h = (h + jzImul((c | 0) + 0x5bd1e995, 0x27d4eb2f)) | 0;
    h = jzImul(h ^ (h >>> 15), 0x165667b1);
    h = (h + jzImul((d | 0) + 0x1b873593, 0x85ebca6b)) | 0;
    h = jzImul(h ^ (h >>> 16), 0x27d4eb2f);
    h = (h + jzImul((e | 0) + 0x68e31da4, 0x9e3779b1)) | 0;
    h ^= h >>> 15; h = jzImul(h, 0x2c1b3c6d); h ^= h >>> 12; h = jzImul(h, 0x297a2d39); h ^= h >>> 15;
    return h >>> 0;
}
function xb2_r(a, b, c, d, e) { return xb2_h(a, b, c, d, e) / 4294967296; }
function xb2_rs(a, b, c, d, e) { return xb2_r(a, b, c, d, e) * 2 - 1; }
function xb2_bit(m, k) { return (xb2_h(m.c.seed | 0, k, 1991) & 1) === 1; }      // browser cutBit
function xb2_cr(m, k) { return xb2_r(m.c.seed | 0, k, 1993); }                     // browser cutR
function xb2_isHex(c) { return typeof c === 'string' && c.charAt(0) === '#' && (c.length === 7 || c.length === 4); }
function xb2_pick(a, b, c) { if (xb2_isHex(a)) return a; if (xb2_isHex(b)) return b; if (xb2_isHex(c)) return c; return '#FFFFFF'; }
function xb2_mix(a, b, t) { return jzMixHex(xb2_pick(a), xb2_pick(b), jzClamp(t, 0, 1)); }
function xb2_col(m) {
    var sc = m.ctx.sc || {}, bg = xb2_pick(sc.bg, '#000000');
    return { bg: bg, fg: xb2_pick(sc.fg, '#FFFFFF'), acc: xb2_pick(sc.accent, sc.fg), sub: xb2_pick(sc.sub, sc.fg), dark: jzLum(bg) < 0.5,
        c0: xb2_pick(jzTextColor(m.L), sc.fg), gA: xb2_pick(sc.ghostA, sc.accent, sc.fg), gB: xb2_pick(sc.ghostB, sc.fg) };
}
function xb2_td(L) { try { return L.property('ADBE Text Properties').property('ADBE Text Document').value; } catch (e) { return null; } }
function xb2_isSp(ch) { return /^[\s\u3000]$/.test(ch); }
function xb2_layers(m) { try { return m.ctx.comp.numLayers; } catch (e) { return 0; } }
// order 0..1 of a one-glyph layer within the cut (browser orderOf for single-glyph items)
function xb2_og(m) { var N = jzCount(m.c.text || ''), mi = m.o.mi || 0; return N > 1 ? jzClamp(mi / (N - 1), 0, 1) : 0; }
// per-glyph index / order: in a selector (ti = textIndex) or for glyph i of n in helper-layer expressions
function xb2_sel(m) { return 'var ti=textIndex,tn=textTotal,o=tn>1?(ti-1)/(tn-1):' + jzN(xb2_og(m)) + ';'; }
function xb2_at(m, i, n) { return 'var ti=' + (i + 1) + ',tn=' + n + ',o=tn>1?(ti-1)/(tn-1):' + jzN(xb2_og(m)) + ';'; }
// screen frame of the layer: s = scale, k = layer px per comp px, dn / rt = screen down / right as unit vectors in layer space
function xb2_frame(m) {
    var tr = m.L.property('ADBE Transform Group'), s = 1, r = 0;
    try { s = Math.abs(tr.property('ADBE Scale').value[0]) / 100; } catch (e0) {}
    if (!(s > 0.01)) s = 1;
    try { r = tr.property('ADBE Rotate Z').value * Math.PI / 180; } catch (e1) {}
    return { s: s, k: 1 / s, r: r, deg: r * 180 / Math.PI, dn: [Math.sin(r), Math.cos(r)], rt: [Math.cos(r), -Math.sin(r)] };
}
function xb2_dirs(F) { return 'var DN0=' + jzN(F.dn[0]) + ',DN1=' + jzN(F.dn[1]) + ',RT0=' + jzN(F.rt[0]) + ',RT1=' + jzN(F.rt[1]) + ';'; }
function xb2_toComp(L, x, y) {
    var tr = L.property('ADBE Transform Group'), a = tr.property('ADBE Anchor Point').value, p = tr.property('ADBE Position').value;
    var s = tr.property('ADBE Scale').value, r = tr.property('ADBE Rotate Z').value * Math.PI / 180;
    var ux = (x - a[0]) * s[0] / 100, uy = (y - a[1]) * s[1] / 100;
    return [p[0] + Math.cos(r) * ux - Math.sin(r) * uy, p[1] + Math.sin(r) * ux + Math.cos(r) * uy];
}
function xb2_toLayer(L, X, Y) {
    var tr = L.property('ADBE Transform Group'), a = tr.property('ADBE Anchor Point').value, p = tr.property('ADBE Position').value;
    var s = tr.property('ADBE Scale').value, r = tr.property('ADBE Rotate Z').value * Math.PI / 180;
    var dx = X - p[0], dy = Y - p[1], c = Math.cos(r), sn = Math.sin(r), sx = (s[0] || 100) / 100, sy = (s[1] || 100) / 100;
    return [a[0] + (c * dx + sn * dy) / sx, a[1] + (-sn * dx + c * dy) / sy];
}
// comp-space bounding box of a layer-space rect
function xb2_cbox(L, r) {
    var P = [[r.left, r.top], [r.left + r.width, r.top], [r.left, r.top + r.height], [r.left + r.width, r.top + r.height]], x0 = 1e9, y0 = 1e9, x1 = -1e9, y1 = -1e9, i;
    for (i = 0; i < 4; i++) { var q = xb2_toComp(L, P[i][0], P[i][1]); x0 = Math.min(x0, q[0]); x1 = Math.max(x1, q[0]); y0 = Math.min(y0, q[1]); y1 = Math.max(y1, q[1]); }
    return { x0: x0, y0: y0, x1: x1, y1: y1, cx: (x0 + x1) / 2, cy: (y0 + y1) / 2 };
}
function xb2_meas(T, L, s, just) {
    var d = L.property('ADBE Text Properties').property('ADBE Text Document').value;
    d.text = s; d.justification = just;
    T.property('ADBE Text Properties').property('ADBE Text Document').setValue(d);
    var q = T.sourceRectAtTime(0, false);
    return { l: q.left, r: q.left + q.width };
}
// Geometry of the lyric in the text layer's own space (call BEFORE adding animators).
//   G.fs, G.lead, G.rect, G.vert (one glyph per line), G.nL, G.lines[{cy, x0, x1, chars}], G.N = textTotal, G.nk (glyphs without spaces)
//   G.g[] per AE character (textIndex order, spaces included): {i, li, sp, x0, x1, cx, cy, w, h}; G.arranged / G.masked
function xb2_geo(m, measure) {
    var L = m.L, TP = L.property('ADBE Text Properties'), td = TP.property('ADBE Text Document').value, i, j;
    var fs = td.fontSize, lead = (!td.autoLeading && td.leading > 0) ? td.leading : fs * 1.2;
    var lines = String(td.text).split(/\r\n|\r|\n|\u0003/), nL = lines.length, r = jzRect(L);
    var G = { fs: fs, lead: lead, nL: nL, rect: r, g: [], lines: [], N: 0, nk: 0, vert: nL > 1, arranged: false, masked: false };
    try { G.arranged = TP.property('ADBE Text Animators').numProperties > 0; } catch (e0) {}
    try { if (TP.property('ADBE Text Path Options').property('ADBE Text Path').value > 0) G.arranged = true; } catch (e1) {}
    try { G.masked = L.property('ADBE Mask Parade').numProperties > 0; } catch (e2) {}
    var inkH = r.height - (nL - 1) * lead; if (!(inkH > fs * 0.3 && inkH < fs * 2)) inkH = fs * 0.9;
    var cy0 = r.top + inkH / 2, trk = (td.tracking || 0) / 1000 * fs;
    for (i = 0; i < nL; i++) { var cs0 = jzChars(lines[i]); if (cs0.length > 1) G.vert = false; G.lines.push({ chars: cs0, cy: cy0 + i * lead, x0: r.left, x1: r.left + r.width }); }
    var T = null;
    if (measure && !G.arranged && jzCount(td.text) <= 60) { try { T = m.ctx.comp.layers.addText('x'); } catch (e3) { T = null; } }
    var LEFT = ParagraphJustification.LEFT_JUSTIFY;
    try {
        for (i = 0; i < nL; i++) {
            var ln = G.lines[i], cs = ln.chars, n = cs.length;
            if (!n) continue;
            var F = null, F0 = null, off = 0, prev = 0;
            if (T && !G.vert) {
                F = xb2_meas(T, L, lines[i], td.justification); ln.x0 = F.l; ln.x1 = F.r;
                if (n > 1) { F0 = xb2_meas(T, L, lines[i], LEFT); off = F.l - F0.l; }
                prev = F.l - trk;
            } else if (!G.vert) {
                var wEst = r.width / Math.max(1, n), lx = r.left + (r.width - wEst * n) / 2;
                ln.x0 = lx; ln.x1 = lx + wEst * n; prev = lx - trk;
            }
            for (j = 0; j < n; j++) {
                var sp = xb2_isSp(cs[j]), x0, x1;
                if (G.vert) { x0 = r.left; x1 = r.left + r.width; }
                else if (T) {
                    x0 = prev + trk;
                    x1 = (j === n - 1) ? F.r : off + xb2_meas(T, L, cs.slice(0, j + 1).join(''), LEFT).r;
                    if (x1 < x0) x1 = x0;
                    prev = x1;
                } else { x0 = prev + trk; x1 = x0 + (r.width + trk) / Math.max(1, n) - trk; prev = x1; }
                var gi = { i: G.N, li: i, sp: sp, x0: x0, x1: x1, cx: (x0 + x1) / 2, cy: ln.cy, w: Math.max(1, x1 - x0), h: fs };
                if (G.vert) gi.w = Math.min(r.width, fs);
                G.g.push(gi); G.N++; if (!sp) G.nk++;
            }
        }
    } catch (e4) { jzWarn('xb2 geometry: ' + e4.toString()); }
    if (T) { try { T.remove(); } catch (e5) {} }
    if (!G.N) { G.g.push({ i: 0, li: 0, sp: false, x0: r.left, x1: r.left + r.width, cx: r.left + r.width / 2, cy: r.top + r.height / 2, w: Math.max(1, r.width), h: fs }); G.N = 1; G.nk = 1; }
    return G;
}
// reading lines [{u0, u1, v}] (u along the reading direction, v = line centre across it); vertical text = one column
function xb2_lines(G) {
    var r = G.rect, out = [], i, j;
    if (G.vert) return [{ li: -1, u0: r.top, u1: r.top + r.height, v: r.left + r.width / 2 }];
    for (i = 0; i < G.nL; i++) {
        var u0 = 1e9, u1 = -1e9;
        for (j = 0; j < G.N; j++) { var g = G.g[j]; if (g.li !== i || g.sp) continue; if (g.x0 < u0) u0 = g.x0; if (g.x1 > u1) u1 = g.x1; }
        if (u1 > u0) out.push({ li: i, u0: u0, u1: u1, v: G.lines[i].cy });
    }
    return out;
}
// one glyph per layer inside a longer lyric (browser isSingle)
function xb2_single(m, G) { return G.nk <= 1 && jzCount(m.c.text || '') > 1; }
// helper layers / twins are skipped for very long lines, one-glyph-per-layer layouts of long lyrics and crowded comps
function xb2_crowded(m, G) {
    if (G && (G.N > 60 || (G.nk <= 1 && jzCount(m.c.text || '') > 14))) return true;
    return xb2_layers(m) > 60;
}
// the lyric's opaque, filled copy (helper graphics only go there)
function xb2_main(m, G) {
    var td = xb2_td(m.L);
    if (td && td.applyFill === false) return false;
    try { if (jzXf(m.L, 'ADBE Opacity').value < 85) return false; } catch (e) {}
    return !xb2_crowded(m, G);
}
// per-glyph helper groups are only drawn for lines of reasonable length
function xb2_fewGlyphs(G) { return G.nk <= 24 && !G.arranged; }
function xb2_matted(L) { try { return L.trackMatteType !== TrackMatteType.NO_TRACK_MATTE; } catch (e) { return false; } }
// layer that helper layers go above (above the lyric's own layout matte if it has one)
function xb2_top(m) { var L = m.L; if (xb2_matted(L)) { try { return m.ctx.comp.layer(L.index - 1); } catch (e) {} } return L; }
// only alive during the exit
function xb2_live(m, Lx) { var t = xb2_T(m); try { Lx.inPoint = Math.max(0, Math.min(t.OS - 0.04, t.DUR - 0.05)); } catch (e) {} }
// helper shape layer drawn in the text layer's own space (parented to it, above it), exit only, main pass only
function xb2_shape(m, name, above) {
    var L = m.L, S = m.ctx.comp.layers.addShape();
    try { S.name = name; } catch (e0) {}
    try { S.moveBefore(above || xb2_top(m)); } catch (e1) {}
    S.parent = L;
    var tr = S.property('ADBE Transform Group');
    tr.property('ADBE Anchor Point').setValue([0, 0]); tr.property('ADBE Position').setValue([0, 0]);
    tr.property('ADBE Scale').setValue([100, 100]); tr.property('ADBE Rotate Z').setValue(0);
    xb2_live(m, S);
    jzNoGhost(S);
    return S;
}
function xb2_sub(g, name) { var s = jzVecs(g).addProperty('ADBE Vector Group'); if (name) s.name = name; return s; }
// copy of the lyric shown only during the exit: parented to L (follows every move of it), takes L's final text style,
// made before the exit's own animators. pre: expression statements; opEx: opacity factor; posEx: position (default on L)
function xb2_twin(m, tag, pre, opEx, posEx, below) {
    var L = m.L, top = xb2_top(m), D = L.duplicate(), i, mns = ['ADBE Position', 'ADBE Scale', 'ADBE Rotate Z', 'ADBE Opacity', 'ADBE Anchor Point'];
    try { D.name = String(L.name).substr(0, 20) + ' ' + tag; } catch (e0) {}
    var tr = D.property('ADBE Transform Group');
    for (i = 0; i < mns.length; i++) { try { if (tr.property(mns[i]).expression) tr.property(mns[i]).expression = ''; } catch (e1) {} }
    try { if (xb2_matted(D)) D.trackMatteType = TrackMatteType.NO_TRACK_MATTE; } catch (e2) {}
    try { if (below) D.moveAfter(L); else D.moveBefore(top); } catch (e3) {}
    D.parent = L;
    tr.property('ADBE Scale').setValue([100, 100]); tr.property('ADBE Rotate Z').setValue(0);
    jzSetExpr(tr.property('ADBE Position'), posEx ? xb2_hd(m) + (pre || '') + posEx : 'parent.transform.anchorPoint');
    jzSetExpr(tr.property('ADBE Opacity'), xb2_hd(m) + (pre || '') + 'PO>0&&PO<0.998?value*(' + (opEx || '1') + '):0');
    try { D.property('ADBE Text Properties').property('ADBE Text Document').expression = 'parent.text.sourceText.style'; } catch (e4) {}
    xb2_live(m, D);
    return D;
}
// masks in the layer's space; intersect with masks the layout already put on the layer
function xb2_maskPoly(Lx, pts, name, expEx, feather) {
    var n0 = 0; try { n0 = Lx.property('ADBE Mask Parade').numProperties; } catch (e0) {}
    var mk = Lx.property('ADBE Mask Parade').addProperty('ADBE Mask Atom'), sh = new Shape();
    sh.vertices = pts; sh.closed = true;
    mk.property('ADBE Mask Shape').setValue(sh);
    try { if (name) mk.name = name; } catch (e1) {}
    if (n0 > 0) { try { mk.maskMode = MaskMode.INTERSECT; } catch (e2) {} }
    if (feather) { try { mk.property('ADBE Mask Feather').setValue([feather, feather]); } catch (e3) {} }
    if (expEx) jzSetExpr(mk.property('ADBE Mask Offset'), expEx);
    return mk;
}
function xb2_maskRect(Lx, x0, y0, x1, y1, name, expEx, feather) { return xb2_maskPoly(Lx, [[x1, y0], [x1, y1], [x0, y1], [x0, y0]], name, expEx, feather); }
// alpha track matte: D shows only inside S (S right above D)
function xb2_matte(D, S) {
    var ok = false;
    try { S.moveBefore(D); } catch (e0) {}
    if (typeof D.setTrackMatte === 'function') { try { D.setTrackMatte(S, TrackMatteType.ALPHA); ok = true; } catch (e1) { ok = false; } }
    if (!ok) { try { D.trackMatteType = TrackMatteType.ALPHA; } catch (e2) { jzWarn('xb2 matte: ' + e2.toString()); } }
}
// Transform effect (layer space): anchor at (cx, cy); position / rotation expressions
function xb2_xform(Lx, name, cx, cy, posEx, rotEx) {
    var e = jzEffect(Lx, 'ADBE Geometry2', name);
    jzEP(e, 1, [cx, cy]); jzEP(e, 2, [cx, cy]);
    if (posEx) jzEX(e, 2, posEx);
    if (rotEx) jzEX(e, 8, rotEx);
    return e;
}
// keep the point (px, py) of L's own space fixed while the layer is scaled by fx / fy (expression strings) -> m.parts.pos snippet
function xb2_pivot(m, fx, fy, px, py, pre) {
    var tr = m.L.property('ADBE Transform Group'), a = tr.property('ADBE Anchor Point').value, s = tr.property('ADBE Scale').value;
    var r0 = tr.property('ADBE Rotate Z').value * Math.PI / 180, ux = (px - a[0]) * s[0] / 100, uy = (py - a[1]) * s[1] / 100;
    if (Math.abs(ux) + Math.abs(uy) < 0.5) return '';
    var c = Math.cos(r0), sn = Math.sin(r0);
    return (pre || '') + 'var pvx=(1-(' + fx + '))*' + xb2_n(ux) + ',pvy=(1-(' + fy + '))*' + xb2_n(uy) + ';d=[d[0]+' + xb2_n(c) + '*pvx+' + xb2_n(-sn) + '*pvy,d[1]+' + xb2_n(sn) + '*pvx+' + xb2_n(c) + '*pvy];';
}
// sine Wave Warp (height / phase expressions; direction 0 = horizontal displacement in slices, 90 = vertical)
function xb2_wave(Lx, name, dir, width, hEx, phEx) {
    var ww = jzEffect(Lx, 'ADBE Wave Warp', name);
    jzEP(ww, 1, 1); jzEP(ww, 3, Math.max(2, width)); jzEP(ww, 4, dir); jzEP(ww, 5, 0); jzEP(ww, 6, 1);
    jzEX(ww, 2, hEx); jzEX(ww, 7, phEx);
    return ww;
}
// every exit ends fully gone (the browser's p >= 0.998 safety net)
// (the layer transform snippets are composed with m.HD only: XB2_FNS goes in front of the first snippet this exit adds)
function xb2_reg(key, fn) {
    jzReg('exit', key, { apply: function (m) {
        var ks = ['sc', 'pos', 'op', 'rot'], n0 = [], i;
        for (i = 0; i < ks.length; i++) n0.push(m.parts[ks[i]].length);
        fn(m);
        m.parts.op.push('f*=PO>=0.998?0:1;');
        for (i = 0; i < ks.length; i++) if (m.parts[ks[i]].length > n0[i]) m.parts[ks[i]].splice(n0[i], 0, XB2_FNS);
    } });
}
// fallback when helper layers would be too many: glyphs burst apart, tumble and drop
function xb2_burst(m) {
    var L = m.L, sz = xb2_sz(m), K = sz * 3, F = xb2_frame(m);
    var q = xb2_hd(m) + xb2_dirs(F) + xb2_sel(m) + 'var q=wn(PO,hh(ti*3.1+SD+7)*0.35,0.35),a=hh(ti*5.3+SD+9)*6.2832,e=q*q,fa=e*e;';
    jzAnimator(L, 'JZ Out Burst', [['ADBE Text Position 3D', [K, K, 0]]], q + 'q>0?[(Math.cos(a)*0.5*e+DN0*0.5*fa)*100,(Math.sin(a)*0.5*e+DN1*0.5*fa)*100,0]:[0,0,0]');
    jzAnimator(L, 'JZ Out Tumble', [['ADBE Text Rotation', 220]], q + 'q>0?(hh(ti*7.7+SD+3)*2-1)*e*100:0');
    jzAnimator(L, 'JZ Out Fade', [['ADBE Text Opacity', 0]], q + 'q>=1?100:sm(0.55,1,q)*100');
}

/* ================================================================ MECHANICS */

/* ---- rocketOff \u2014 \u6253\u3061\u4E0A\u3052: each glyph squats, then blasts off upward on a flame trail, leaving a puff of smoke on the pad */
xb2_reg('rocketOff', function (m) {
    var L = m.L, G = xb2_geo(m, true), F = xb2_frame(m), sz = xb2_sz(m), H = xb2_hd(m), C = xb2_col(m), i;
    var cb = xb2_cbox(L, G.rect), D = Math.max(sz * 2, cb.y1 * F.k + sz * 1.6), K = D + sz;
    var base = 'var q=wn(PO,hh(ti*7.31+SD+111)*0.85,0.55),u=q<0.3?0:(q-0.3)/0.7,Lf=' + jzN(D) + '*Math.pow(u,2.3),hz=SZ*0.5,st=1+Math.min(0.8,3*u*u);';
    var sel = H + xb2_dirs(F) + xb2_sel(m) + base;
    jzAnimator(L, 'JZ Out Rocket', [['ADBE Text Position 3D', [K, K, 0]]], sel +
        'var dx=0,dy=0;if(q>0&&q<0.3){var uu=q/0.3,sq=Math.sin(uu*Math.PI/2),k=1-0.22*sq;seedRandom(ti*31+SD+Math.floor(time*24),true);var sh=random(-1,1)*SZ*0.02*uu;' +
        'dx=DN0*(1-k)*hz+RT0*sh;dy=DN1*(1-k)*hz+RT1*sh;}else if(q>=0.3){dx=-DN0*Lf;dy=-DN1*Lf;}[dx/' + jzN(K) + '*100,dy/' + jzN(K) + '*100,0]');
    jzAnimator(L, 'JZ Out Rocket Squash', [['ADBE Text Scale 3D', [200, 200, 100]]], sel +
        'var fx=1,fy=1;if(q>0&&q<0.3){var sq=Math.sin(q/0.3*Math.PI/2);fy=1-0.22*sq;fx=1+0.12*sq;}else if(q>=0.3){fy=st;fx=1-0.12*(st-1);}[(fx-1)*100,(fy-1)*100,0]');
    jzAnimator(L, 'JZ Out Hide', [['ADBE Text Opacity', 0]], sel + 'q>=1?100:0');
    if (!xb2_main(m, G) || !xb2_fewGlyphs(G)) return;
    // flame trail + nose flame on every glyph, smoke puffs on its launch pad
    var S = xb2_shape(m, 'JZ Out Rocket Flames'), T = sz * 2.6;
    for (i = 0; i < G.N; i++) {
        var g = G.g[i]; if (g.sp) continue;
        var E = H + xb2_at(m, i, G.N) + base;
        // (group transforms are fetched after the group's contents are complete)
        var fg = jzGrp(S, 'flame ' + (i + 1));
        var tail = xb2_sub(fg, 'trail');
        var s1 = xb2_sub(tail, 'glow'); jzAddRect(s1, sz * 0.2, T, 0, 0, T / 2); jzAddFill(s1, C.acc, 28);
        var s2 = xb2_sub(tail, 'core'); jzAddRect(s2, sz * 0.08, T * 0.75, 0, 0, T * 0.375); jzAddFill(s2, C.acc, 95);
        jzSetExpr(jzGX(tail).property('ADBE Vector Scale'), E + '[100,Math.min(Lf,' + jzN(T) + ')/' + jzN(T) + '*100]');
        var tip = xb2_sub(fg, 'tip'); jzAddPath(tip, [[-sz * 0.1, 0], [sz * 0.1, 0], [0, sz * 0.45]], true); jzAddFill(tip, C.acc);
        var gx = jzGX(fg);
        gx.property('ADBE Vector Rotation').setValue(-F.deg);
        jzSetExpr(gx.property('ADBE Vector Position'), E + '[' + jzN(g.cx) + '+' + xb2_n(F.dn[0]) + '*(hz*st-Lf),' + jzN(g.cy) + '+' + xb2_n(F.dn[1]) + '*(hz*st-Lf)]');
        jzSetExpr(gx.property('ADBE Vector Group Opacity'), E + 'q>=0.3&&q<1?(1-u*0.8)*100:0');
        var sm = jzGrp(S, 'smoke ' + (i + 1)), sx, k;
        for (k = 0; k < 3; k++) { var d = sz * 0.6 * (1 - Math.abs(k - 1) * 0.3); jzAddEllipse(sm, d, d, (k - 1) * sz * 0.35, -sz * 0.05); }
        jzAddFill(sm, C.sub, 35);
        sx = jzGX(sm);
        sx.property('ADBE Vector Position').setValue([g.cx + F.dn[0] * g.h * 0.5, g.cy + F.dn[1] * g.h * 0.5]);
        sx.property('ADBE Vector Rotation').setValue(-F.deg);
        jzSetExpr(sx.property('ADBE Vector Scale'), E + 'var s=(0.08+0.22*u)/0.3*100;[s,s]');
        jzSetExpr(sx.property('ADBE Vector Group Opacity'), E + 'q>=0.3&&q<1?(1-u)*100:0');
    }
});

/* ---- bounceOff \u2014 \u5F3E\u3093\u3067\u53BB\u308B: glyphs hop away sideways in shrinking bounces, squashing on every landing */
var XB2_HOPS = 'function hops(q){var R=0.64,S=0,j,d,t;for(j=0;j<4;j++)S+=Math.pow(R,j);t=q*S;for(j=0;j<4;j++){d=Math.pow(R,j);if(t<=d||j==3){var u=cl(t/d),hk=d*d;' +
    'return [hk*4*u*(1-u),Math.max(0,1-Math.min(u,1-u)/0.09)*Math.sqrt(hk)];}t-=d;}return [0,0];}';
// distance (layer px) each glyph travels to leave the frame sideways
function xb2_edgeDist(m, G, F, dir, extra) {
    var DI = [], i, mx = 0;
    for (i = 0; i < G.N; i++) {
        var c = xb2_toComp(m.L, G.g[i].cx, G.g[i].cy), d = (dir > 0 ? m.W - c[0] : c[0]) * F.k + extra;
        d = Math.max(extra, d); DI.push(d); if (d > mx) mx = d;
    }
    return { DI: DI, mx: mx };
}
xb2_reg('bounceOff', function (m) {
    var L = m.L, G = xb2_geo(m, true), F = xb2_frame(m), sz = xb2_sz(m), dir = xb2_bit(m, 121) ? 1 : -1;
    var ED = xb2_edgeDist(m, G, F, dir, sz * 1.2), K = ED.mx + sz * 2;
    var sel = xb2_hd(m) + XB2_HOPS + xb2_dirs(F) + xb2_sel(m) + (dir > 0 ? 'o=1-o;' : '') + 'var DI=' + xb2_arr(ED.DI) + ',q=wn(PO,o,0.42),on=q>0&&q<1,dist=DI[ti-1]||DI[0],' +
        'along=' + dir + '*dist*Math.pow(q,1.25),hp=hops(q),h=hp[0]*SZ*1.4,sq=on?0.3*hp[1]:0,kk=1-sq,hz=SZ*0.5;';
    jzAnimator(L, 'JZ Out Hop', [['ADBE Text Position 3D', [K, K, 0]]], sel + 'on?[(RT0*along-DN0*h+DN0*(1-kk)*hz)/' + jzN(K) + '*100,(RT1*along-DN1*h+DN1*(1-kk)*hz)/' + jzN(K) + '*100,0]:[0,0,0]');
    jzAnimator(L, 'JZ Out Hop Tilt', [['ADBE Text Rotation', 14]], sel + 'on?' + dir + '*Math.sin(Math.PI*Math.min(1,hp[0]*4))*100:0');
    jzAnimator(L, 'JZ Out Hop Squash', [['ADBE Text Scale 3D', [200, 200, 100]]], sel + '[sq*70,-sq*100,0]');
    jzAnimator(L, 'JZ Out Hide', [['ADBE Text Opacity', 0]], sel + 'q>=1?100:0');
});

/* ---- balloonOff \u2014 \u98A8\u8239\u3067\u98DB\u3076: each glyph floats up on a string, swaying like a pendulum below its balloon */
xb2_reg('balloonOff', function (m) {
    var L = m.L, G = xb2_geo(m, true), F = xb2_frame(m), sz = xb2_sz(m), H = xb2_hd(m), C = xb2_col(m), i;
    var cb = xb2_cbox(L, G.rect), D = Math.max(sz * 2, cb.y1 * F.k + sz * 1.8), K = D + sz;
    var base = 'var q=wn(PO,hh(ti*5.17+SD+141),0.45),on=q>0&&q<1,Lf=' + jzN(D) + '*Math.pow(q,1.7),ph=hh(ti*3.7+SD+142)*6.2832,w=7+3*hh(ti*2.9+SD+143),am=Math.min(1,q*3),' +
        'sw=Math.sin(q*w+ph)*SZ*0.22*am,rr=-Math.cos(q*w+ph)*11*am,inf=1+0.07*Math.sin(Math.PI*Math.min(1,q*2.5));';
    var sel = H + xb2_dirs(F) + xb2_sel(m) + base;
    jzAnimator(L, 'JZ Out Balloon', [['ADBE Text Position 3D', [K, K, 0]]], sel + 'on?[(RT0*sw-DN0*Lf)/' + jzN(K) + '*100,(RT1*sw-DN1*Lf)/' + jzN(K) + '*100,0]:[0,0,0]');
    jzAnimator(L, 'JZ Out Sway', [['ADBE Text Rotation', 11]], sel + 'on?rr/11*100:0');
    jzAnimator(L, 'JZ Out Inflate', [['ADBE Text Scale 3D', [200, 200, 100]]], sel + 'on?[(inf-1)*100,(inf-1)*100,0]:[0,0,0]');
    jzAnimator(L, 'JZ Out Hide', [['ADBE Text Opacity', 0]], sel + 'q>=1?100:0');
    if (!xb2_main(m, G) || !xb2_fewGlyphs(G)) return;
    // the strings hang from the glyph bottoms and swing with them
    var S = xb2_shape(m, 'JZ Out Balloon Strings'), lw = Math.max(1 * m.u, sz * 0.014);
    for (i = 0; i < G.N; i++) {
        var g = G.g[i]; if (g.sp) continue;
        var E = H + xb2_dirs(F) + xb2_at(m, i, G.N) + base, hz = g.h * 0.5;
        var sg = jzGrp(S, 'string ' + (i + 1));
        jzAddPath(sg, [[0, hz], [sz * 0.05, hz + sz * 0.35], [-sz * 0.03, hz + sz * 0.65], [0, hz + sz * 0.95]], false);
        var st = jzAddStroke(sg, C.sub, lw); try { st.property('ADBE Vector Stroke Line Join').setValue(2); } catch (e0) {}
        var gx = jzGX(sg);             // (fetched after the group's contents are complete)
        jzSetExpr(gx.property('ADBE Vector Position'), E + '[' + jzN(g.cx) + '+RT0*sw-DN0*Lf,' + jzN(g.cy) + '+RT1*sw-DN1*Lf]');
        jzSetExpr(gx.property('ADBE Vector Rotation'), E + 'rr');
        jzSetExpr(gx.property('ADBE Vector Group Opacity'), E + 'on?85*Math.min(1,q*8):0');
    }
});

/* ---- deflateOut \u2014 \u3057\u307C\u3093\u3067\u98DB\u3076: each glyph puffs up, then zips around erratically like a let-go balloon while it shrinks away */
xb2_reg('deflateOut', function (m) {
    var L = m.L, sz = xb2_sz(m), K = sz * 9;
    var sel = xb2_hd(m) + xb2_sel(m) + 'var q=wn(PO,hh(ti*6.1+SD+151)*0.8,0.4),x=0,y=0,rt=0,s=1,sx=1,sy=1;' +
        'if(q>0&&q<0.16){var u=q/0.16;s=1+0.16*oc(u);sx=1+0.05*Math.sin(u*25);sy=1-0.05*Math.sin(u*25);rt=(hh(ti*4.3+SD+152)*2-1)*4*u;}' +
        'else if(q>=0.16){var u=Math.min(1,(q-0.16)/0.81),th0=hh(ti*3.3+SD+153)*6.2832,th=th0,k;' +
        'for(k=0;k<14;k++){var t=(k+0.5)/14*u;th=th0+7*n1(t*5,ti*7+SD)+3*t;var v=SZ*7.5*(0.5+t)*(u/14);x+=Math.cos(th)*v;y+=Math.sin(th)*v;}' +
        'var wb=Math.sin(u*70)*0.14*(1-u);rt=(th-th0)*28.648;s=1.16*(1-Math.pow(u,1.3));sx=1+wb;sy=1-wb;}';
    jzAnimator(L, 'JZ Out Deflate', [['ADBE Text Position 3D', [K, K, 0]]], sel + '[x/' + jzN(K) + '*100,y/' + jzN(K) + '*100,0]');
    jzAnimator(L, 'JZ Out Deflate Spin', [['ADBE Text Rotation', 720]], sel + 'rt/7.2');
    jzAnimator(L, 'JZ Out Deflate Size', [['ADBE Text Scale 3D', [200, 200, 100]]], sel + '[(s*sx-1)*100,(s*sy-1)*100,0]');
    jzAnimator(L, 'JZ Out Hide', [['ADBE Text Opacity', 0]], sel + 'q>=0.97?100:0');
});

/* ---- hazeOut \u2014 \u967D\u708E\u306B\u6D88\u3048\u308B: the text wobbles in rippling slices, stretches upward, tints and thins into the air */
xb2_reg('hazeOut', function (m) {
    var L = m.L, G = xb2_geo(m, false), r = G.rect, F = xb2_frame(m), sz = xb2_sz(m), C = xb2_col(m), H = xb2_hd(m), vert = G.vert;
    var E = 'var he=iq(PO);';
    m.parts.sc.push(E + 'f=[f[0]*(1-0.05*he),f[1]*(1+0.3*he)];');
    m.parts.pos.push(xb2_pivot(m, '1-0.05*he', '1+0.3*he', r.left + r.width / 2, r.top + r.height, E));
    m.parts.pos.push(E + 'd=[d[0],d[1]-' + jzN(sz * F.s * 0.3) + '*he];');
    m.parts.op.push('f*=1-sm(0.3,0.94,PO);');
    jzAnimator(L, 'JZ Out Haze Tint', [['ADBE Text Fill Color', jzHex(C.acc)]], H + '30*sm(0.1,0.7,PO)');
    // browser: band shift A*sin(c*6.5-t*12)*(0.6+0.4*sin(c*2.3+t*5)), c = band centre / size -> two sine Wave Warps across the slices
    var A = H + 'var A=PO>0?SZ*(0.02+0.26*ios(PO)):0;';
    xb2_wave(L, 'JZ Out Haze 1', vert ? 90 : 0, sz * 2 * Math.PI / 6.5, A + 'A*0.42', 'time*687.5');
    xb2_wave(L, 'JZ Out Haze 2', vert ? 90 : 0, sz * 2 * Math.PI / 2.3, A + 'A*0.16', '90-time*286.5');
});

/* ---- glassBreak \u2014 \u30AC\u30E9\u30B9\u5272\u308C: an impact cracks the text like a pane of glass, then the shards tumble and drop away
   (browser: inner + outer shard per spoke -> AE: one pie-shaped shard per spoke, each a masked copy with a Transform effect) */
xb2_reg('glassBreak', function (m) {
    var L = m.L, G = xb2_geo(m, false), r = G.rect, F = xb2_frame(m), sz = xb2_sz(m), seed = m.c.seed | 0, C = xb2_col(m), H = xb2_hd(m), k, j;
    var single = xb2_single(m, G), nl = xb2_layers(m);
    if (xb2_crowded(m, G) || (single && nl > 26) || nl > 48) { xb2_burst(m); return; }
    var VE = (single ? 'var q=wn(PO,' + jzN(xb2_og(m)) + ',0.4);' : 'var q=PO;') + 'var cr=cl(q/0.2),v=cl((q-0.18)/0.82);';
    var pad = sz * 0.12, bx0 = r.left - pad, by0 = r.top - pad, bx1 = r.left + r.width + pad, by1 = r.top + r.height + pad;
    var bw = bx1 - bx0, bh = by1 - by0, ix = (bx0 + bx1) / 2 + xb2_rs(seed, 151) * bw * 0.2, iy = (by0 + by1) / 2 + xb2_rs(seed, 152) * bh * 0.12;
    var K = single ? 3 : jzClamp(Math.round(5 + bw / Math.max(1, bh) * 0.8), 6, 8);
    if (nl > 30) K = Math.min(K, 5);
    var B = [], R = [], AN = [], TAU = Math.PI * 2;
    for (k = 0; k < K; k++) {
        var a = (k + 0.35 * xb2_rs(seed, k, 153)) / K * TAU, c = Math.cos(a), s = Math.sin(a);
        var tx = c > 0 ? (bx1 - ix) / c : (c < 0 ? (bx0 - ix) / c : 1e9), ty = s > 0 ? (by1 - iy) / s : (s < 0 ? (by0 - iy) / s : 1e9);
        var tt = Math.min(tx, ty), f = 0.3 + 0.25 * xb2_r(seed, k, 154);
        B.push([ix + c * tt, iy + s * tt]); R.push([ix + c * tt * f, iy + s * tt * f]); AN.push(Math.atan2(s * tt, c * tt));
    }
    // shards: centre -> spoke k -> box corners between the spokes -> spoke k+1
    var CO = [[bx1, by1], [bx0, by1], [bx0, by0], [bx1, by0]], first = null, fall = m.H * F.k * 1.1;
    for (k = 0; k < K; k++) {
        var k2 = (k + 1) % K, a1 = AN[k], dd = AN[k2] - a1, poly = [[ix, iy], B[k]], cs = [];
        dd = ((dd % TAU) + TAU) % TAU;
        for (j = 0; j < 4; j++) {
            var da = Math.atan2(CO[j][1] - iy, CO[j][0] - ix) - a1; da = ((da % TAU) + TAU) % TAU;
            if (da > 0 && da < dd) cs.push([da, CO[j][0], CO[j][1]]);
        }
        cs.sort(function (p1, p2) { return p1[0] - p2[0]; });
        for (j = 0; j < cs.length; j++) poly.push([cs[j][1], cs[j][2]]);
        poly.push(B[k2]);
        var cx = 0, cy = 0; for (j = 0; j < poly.length; j++) { cx += poly[j][0]; cy += poly[j][1]; } cx /= poly.length; cy /= poly.length;
        var dl = xb2_r(seed, k, 155) * 0.3, rot = xb2_rs(seed, k, 156) * 120, ox = (cx - ix) / Math.max(1, bw) * sz * 1.2, oy = (cy - iy) / Math.max(1, bh) * sz * 0.5;
        var UE = VE + 'var u=cl((v-' + jzN(dl) + ')/' + jzN(1 - dl) + ');';
        var D = xb2_twin(m, 'JZ Out Shard ' + (k + 1), UE, 'v>0?1-sm(0.75,1,u):0');
        jzNoGhost(D);                    // the browser draws the shards on the main pass only
        if (!first) first = D;
        xb2_maskPoly(D, poly, 'JZ Out Shard');
        xb2_xform(D, 'JZ Out Shard Fall', cx, cy, H + UE + '[' + jzN(cx) + '+' + xb2_n(ox) + '*u+' + xb2_n(F.dn[0] * fall) + '*u*u,' + jzN(cy) + '+' + xb2_n(oy) + '*u+' + xb2_n(F.dn[1] * fall) + '*u*u]', H + UE + jzN(rot) + '*u*u');
    }
    m.parts.op.push(VE + 'f*=v>0?0:1;');
    // the cracks run out from the impact point (background colour), then fade while the shards drop
    var S = xb2_shape(m, 'JZ Out Cracks', first || null), lw = Math.max(1.5 * m.u, sz * 0.032);
    // (spokes, ring, impact \u2014 in this stacking order \u2014 each finished before the next group is added: adding a group
    //  invalidates references to its siblings)
    var sp = jzGrp(S, 'spokes');
    for (k = 0; k < K; k++) jzAddPath(sp, [[ix, iy], B[k]], false);
    jzAddTrimPaths(sp, H + VE + 'cr*100');
    var s1 = jzAddStroke(sp, C.bg, lw);
    jzSetExpr(s1.property('ADBE Vector Stroke Width'), H + VE + jzN(lw) + '*(v>0?Math.max(0,1-v*4):1)');
    jzSetExpr(jzGX(sp).property('ADBE Vector Group Opacity'), H + VE + 'q>0&&v<0.25?100:0');
    var rg = jzGrp(S, 'ring');
    for (k = 0; k < K; k++) jzAddPath(rg, [R[k], R[(k + 1) % K]], false);
    jzAddTrimPaths(rg, H + VE + 'cl((cr-0.5)*2)*100');
    var s2 = jzAddStroke(rg, C.bg, lw * 0.8);
    jzSetExpr(s2.property('ADBE Vector Stroke Width'), H + VE + jzN(lw * 0.8) + '*(v>0?Math.max(0,1-v*4):1)');
    jzSetExpr(jzGX(rg).property('ADBE Vector Group Opacity'), H + VE + 'cr>0.5&&v<0.25?100:0');
    var fl = jzGrp(S, 'impact'), fe = jzAddEllipse(fl, 10, 10, ix, iy);
    jzSetExpr(fe.property('ADBE Vector Ellipse Size'), H + VE + 'var d=SZ*(0.06+0.2*cr)*2;[d,d]');
    jzAddFill(fl, C.acc);
    jzSetExpr(jzGX(fl).property('ADBE Vector Group Opacity'), H + VE + 'q>0&&v<=0?50*(1-cr):0');
});

/* ---- zipOut \u2014 \u30B8\u30C3\u30D1\u30FC: a slider runs along each line; behind it the glyphs are pinched shut onto a row of teeth */
xb2_reg('zipOut', function (m) {
    var L = m.L, G = xb2_geo(m, true), sz = xb2_sz(m), C = xb2_col(m), H = xb2_hd(m), rev = xb2_bit(m, 161), single = xb2_single(m, G), vert = G.vert, i;
    var N = jzCount(m.c.text || ''), lines = xb2_lines(G), OA = [];
    for (i = 0; i < G.N; i++) {
        var g = G.g[i], ln = null, j;
        for (j = 0; j < lines.length; j++) if (vert || lines[j].li === g.li) { ln = lines[j]; break; }
        var o = 0; if (ln) o = ((vert ? g.cy : g.cx) - ln.u0) / Math.max(1, ln.u1 - ln.u0);
        OA.push(rev ? 1 - o : o);
    }
    var og = xb2_og(m), band = single ? 1.2 / Math.max(1, N - 1) : 0.22, PS = 'var ps=-0.12+1.24*ios(cl(PO/0.8)),fa=PO>0?1-sm(0.76,0.96,PO):0;';
    var sel = H + PS + (single ? 'var o=' + jzN(rev ? 1 - og : og) + ';' : 'var OA=' + xb2_arr(OA) + ',o=OA[textIndex-1]||0;') + 'var c=PO>0?ios(cl((ps-o)/' + jzN(band) + '+0.15)):0;';
    jzAnimator(L, 'JZ Out Zip', [['ADBE Text Scale 3D', vert ? [0, 100, 100] : [100, 0, 100]]], sel + 'c*100');
    jzAnimator(L, 'JZ Out Hide', [['ADBE Text Opacity', 0]], sel + 'c>=0.99?100:0');
    if (single || !xb2_main(m, G) || G.arranged) return;
    // teeth (a zigzag drawn up to the slider) + slider with its pull tab, per line
    var S = xb2_shape(m, 'JZ Out Zip'), w = sz * 0.3, h = sz * 0.46, tp = Math.max(3 * m.u, sz * 0.09), th = sz * 0.05;
    for (i = 0; i < lines.length; i++) {
        var a0 = lines[i].u0, a1 = lines[i].u1, mid = lines[i].v, len = a1 - a0, z0 = a0 - sz * 0.1, z1 = a1 + sz * 0.1;
        var SP = H + PS + 'var sp=' + jzN(a0) + '+' + jzN(len) + '*ps;' + (rev ? 'sp=' + jzN(a0 + a1) + '-sp;' : '') + 'var tf=cl((sp-' + xb2_n(z0) + ')/' + jzN(z1 - z0) + ')*100;';
        var tg = jzGrp(S, 'teeth ' + (i + 1));
        jzAddPath(tg, vert ? [[mid, z0], [mid, z1]] : [[z0, mid], [z1, mid]], false);
        var zz = jzVecs(tg).addProperty('ADBE Vector Filter - Zigzag');
        try { zz.property('ADBE Vector Zigzag Size').setValue(th * 0.5); zz.property('ADBE Vector Zigzag Detail').setValue(Math.max(2, Math.round((z1 - z0) / tp))); zz.property('ADBE Vector Zigzag Points').setValue(1); } catch (e0) {}
        if (rev) jzAddTrimPaths(tg, null, SP + 'tf'); else jzAddTrimPaths(tg, SP + 'tf', null);
        jzAddStroke(tg, C.sub, th * 0.7);
        jzSetExpr(jzGX(tg).property('ADBE Vector Group Opacity'), SP + 'fa*100');
        var sg = jzGrp(S, 'slider ' + (i + 1));
        if (vert) { jzAddRect(sg, h, w, w * 0.3); jzAddRect(sg, sz * 0.36, w * 0.4, w * 0.2, h / 2 - sz * 0.02 + sz * 0.18, 0); }
        else { jzAddRect(sg, w, h, w * 0.3); jzAddRect(sg, w * 0.4, sz * 0.36, w * 0.2, 0, h / 2 - sz * 0.02 + sz * 0.18); }
        jzAddFill(sg, C.acc);
        jzSetExpr(jzGX(sg).property('ADBE Vector Position'), SP + (vert ? '[' + jzN(mid) + ',sp]' : '[sp,' + jzN(mid) + ']'));
        jzSetExpr(jzGX(sg).property('ADBE Vector Group Opacity'), SP + 'sp>' + jzN(a0 - sz * 0.5) + '&&sp<' + jzN(a1 + sz * 0.5) + '?fa*100:0');
    }
});

/* ---- clapShut \u2014 \u4E2D\u592E\u3067\u9589\u3058\u308B: both halves slam into a seam at the centre and vanish into it, with an impact flash */
xb2_reg('clapShut', function (m) {
    var L = m.L, G = xb2_geo(m, false), r = G.rect, F = xb2_frame(m), sz = xb2_sz(m), C = xb2_col(m), H = xb2_hd(m), vert = G.vert, BIG = XB2_BIG, k;
    var E = 'var e=ic(cl(PO/0.62)),hit=cl((PO-0.6)/0.4);';
    if (xb2_single(m, G)) {       // one glyph per layer: slide towards the frame centre and vanish into it
        var cc = xb2_toLayer(L, m.W / 2, m.H / 2), pc = xb2_toComp(L, r.left + r.width / 2, r.top + r.height / 2), left = pc[0] < m.W / 2, Dd = m.W * 0.47 * F.k * (left ? 1 : -1);
        jzAnimator(L, 'JZ Out Clap', [['ADBE Text Position 3D', [F.rt[0] * Dd, F.rt[1] * Dd, 0]]], H + E + 'e*100');
        // keep the glyph's own side of the vertical line through the frame centre (a half plane in the layer's rotated space)
        var sg = left ? -1 : 1, rt = F.rt, dn = F.dn, Q1 = [cc[0] - dn[0] * BIG, cc[1] - dn[1] * BIG], Q2 = [cc[0] + dn[0] * BIG, cc[1] + dn[1] * BIG];
        xb2_maskPoly(L, [Q1, Q2, [Q2[0] + sg * rt[0] * BIG, Q2[1] + sg * rt[1] * BIG], [Q1[0] + sg * rt[0] * BIG, Q1[1] + sg * rt[1] * BIG]], 'JZ Out Clap', H + 'PO>0?0:1e4');
        m.parts.op.push(E + 'f*=e>=0.999?0:1;');
        if (Math.round(m.o.mi || 0) !== 0 || xb2_crowded(m)) return;
        var S1 = xb2_shape(m, 'JZ Out Clap Flash'), g1 = jzGrp(S1, 'flash'), rc = jzAddRect(g1, 10, 10, 0);
        jzSetExpr(rc.property('ADBE Vector Rect Size'), H + E + '[' + jzN(sz * 0.12) + '*(1-hit),' + jzN(m.H * 0.3 * F.k) + '*(1+0.6*oc(hit))]');
        jzGX(g1).property('ADBE Vector Position').setValue(cc); jzGX(g1).property('ADBE Vector Rotation').setValue(-F.deg);
        jzAddFill(g1, C.acc);          // (after the rect is configured: adding the fill invalidates the rect reference)
        jzSetExpr(jzGX(g1).property('ADBE Vector Group Opacity'), H + E + 'hit>0&&hit<1?(1-hit)*100:0');
        return;
    }
    var pad = sz * 0.04, bx0 = r.left - pad, bx1 = r.left + r.width + pad, by0 = r.top - pad, by1 = r.top + r.height + pad;
    var seam = vert ? (by0 + by1) / 2 : (bx0 + bx1) / 2, mc = vert ? (bx0 + bx1) / 2 : (by0 + by1) / 2, half = (vert ? by1 - by0 : bx1 - bx0) / 2 + sz * 0.05, cross = vert ? bx1 - bx0 : by1 - by0;
    // the far half is a masked copy sliding the other way
    var D = xb2_twin(m, 'JZ Out Clap B', E, '1');
    var mv = function (Lx, sg) {
        jzAnimator(Lx, 'JZ Out Clap', [['ADBE Text Position 3D', vert ? [0, sg * half, 0] : [sg * half, 0, 0]], ['ADBE Text Scale 3D', vert ? [100, 88, 100] : [88, 100, 100]]], H + E + 'e*100');
    };
    mv(L, 1); mv(D, -1);
    if (vert) { xb2_maskRect(L, mc - BIG, seam - BIG, mc + BIG, seam, 'JZ Out Clap', H + 'PO>0?0:1e4'); xb2_maskRect(D, mc - BIG, seam, mc + BIG, seam + BIG, 'JZ Out Clap'); }
    else { xb2_maskRect(L, seam - BIG, mc - BIG, seam, mc + BIG, 'JZ Out Clap', H + 'PO>0?0:1e4'); xb2_maskRect(D, seam, mc - BIG, seam + BIG, mc + BIG, 'JZ Out Clap'); }
    if (!xb2_main(m, G)) return;
    // seam line, then the impact flash + sparks
    var S = xb2_shape(m, 'JZ Out Clap Flash'), lw = Math.max(1.5 * m.u, sz * 0.025), sx = vert ? mc : seam, sy = vert ? seam : mc;
    // (rects are configured before their fills are added: adding a fill invalidates the rect reference)
    var fg = jzGrp(S, 'seam'), fr = jzAddRect(fg, 10, 10, 0, sx, sy);
    var SE = H + E + 'var sa=sm(0,0.15,PO)*(1-sm(0.6,0.7,PO)),ln=hit>0?' + jzN(sz * 0.14) + '*(1-oc(hit)):' + jzN(lw) + ',ex=hit>0?' + jzN(cross) + '*(1+0.7*oc(hit)):' + jzN(cross * 1.1) + '*oc(sm(0,0.15,PO));';
    jzSetExpr(fr.property('ADBE Vector Rect Size'), SE + (vert ? '[ex,ln]' : '[ln,ex]'));
    jzAddFill(fg, C.acc);
    jzSetExpr(jzGX(fg).property('ADBE Vector Group Opacity'), SE + 'PO<=0||hit>=1?0:(hit>0?(1-hit):sa)*100');
    for (k = 0; k < 4; k++) {
        var kg = jzGrp(S, 'spark ' + (k + 1)), kr = jzAddRect(kg, 10, Math.max(1 * m.u, sz * 0.03), 0);
        var KE = H + E + 'var r0=SZ*(0.2+0.6*hit),r1=r0+SZ*0.25*(1-hit);';
        jzSetExpr(kr.property('ADBE Vector Rect Size'), KE + '[Math.max(0.1,r1-r0),' + jzN(Math.max(1 * m.u, sz * 0.03)) + ']');
        jzSetExpr(kr.property('ADBE Vector Rect Position'), KE + '[(r0+r1)/2,0]');
        jzAddFill(kg, C.acc);
        jzGX(kg).property('ADBE Vector Position').setValue([sx, sy]);
        jzGX(kg).property('ADBE Vector Rotation').setValue(((k + 0.5) / 4 * Math.PI * 2 + 0.3) * 180 / Math.PI);
        jzSetExpr(jzGX(kg).property('ADBE Vector Group Opacity'), H + E + 'hit>0&&hit<1?(1-hit)*100:0');
    }
});

/* ---- lampOff \u2014 \u6D88\u706F: the sign loses power: glyphs flicker on a failing supply and die one by one, leaving dark tubes that fade */
xb2_reg('lampOff', function (m) {
    var L = m.L, G = xb2_geo(m, false), C = xb2_col(m), H = xb2_hd(m), sz = xb2_sz(m);
    var dimC = xb2_mix(C.c0, C.bg, 0.78), hot = C.dark ? xb2_mix(C.c0, '#FFFFFF', 0.5) : C.c0;
    var base = H + 'var ti=textIndex,q=(PO-hh(ti*8.3+SD+171)*0.46)/0.36;seedRandom(ti*31+SD+Math.floor(time*12)*7,true);var off=random()<0.3+0.7*(q/0.55);';
    jzAnimator(L, 'JZ Out Lamp Hot', [['ADBE Text Fill Color', jzHex(hot)]], base + 'q>0&&q<=0.55&&!off?100:0');
    jzAnimator(L, 'JZ Out Lamp Dim', [['ADBE Text Fill Color', jzHex(dimC)]], base + 'q>0.55||(q>0&&off)?100:0');
    jzAnimator(L, 'JZ Out Lamp Fade', [['ADBE Text Opacity', 0]], base + 'q>=1?100:(q>0.55?(q-0.55)/0.45*100:0)');
    if (!C.dark || !xb2_main(m, G)) return;
    // the glow of the lit tubes shrinks as they die
    var ds = jzEffect(L, 'ADBE Drop Shadow', 'JZ Out Lamp Glow');
    jzEP(ds, 1, jzHex(xb2_mix(C.c0, C.acc, 0.4))); jzEP(ds, 3, 0); jzEP(ds, 4, 0); jzEP(ds, 6, 0);
    jzEX(ds, 2, H + 'PO>0?217:0');
    jzEX(ds, 5, H + 'var N=' + G.N + ',lit=0,i;for(i=1;i<=N;i++){var qq=(PO-hh(i*8.3+SD+171)*0.46)/0.36;lit+=qq<=0?1:(qq<0.5?0.5:0);}' + jzN(sz * 0.44) + '*lit/N');   // softness ~ 2x the canvas shadowBlur (sz*0.22)
});

/* ---- slotOut \u2014 \u30B9\u30ED\u30C3\u30C8\u56DE\u8EE2: every glyph spins up like a slot reel, faster and faster, and stops on an empty cell
   (browser: cells scroll through a clipped window -> AE: cells are projected on a drum, no clip needed) */
function xb2_reel(Lx, q, fs, acc, twin) {
    var K = fs * 0.6;
    jzAnimator(Lx, 'JZ Out Reel Roll', [['ADBE Text Position 3D', [0, K, 0]]], q + 'on?-Math.sin(f*Math.PI/2)*100:0');
    jzAnimator(Lx, 'JZ Out Reel Drum', [['ADBE Text Scale 3D', [100, 0, 100]]], q + 'on?(1-Math.cos(f*Math.PI/2))*100:0');
    jzAnimator(Lx, 'JZ Out Reel Blur', [['ADBE Text Scale 3D', [100, 200, 100]]], q + 'on?[0,sp*100,0]:[0,0,0]');
    jzAnimator(Lx, 'JZ Out Reel Char', [['ADBE Text Character Offset', 100]], q + 'on&&cj>0?1+Math.floor(hh(ti*31+cj*7+SD)*40):0');
    jzAnimator(Lx, 'JZ Out Reel Acc', [['ADBE Text Fill Color', jzHex(acc)]], q + 'on&&cj>0&&hh(ti*17+cj*5+SD+183)<0.3?100:0');
    jzAnimator(Lx, 'JZ Out Reel Hide', [['ADBE Text Opacity', 0]], q + (twin ? '!on?100:' : 'q>0&&cj>MM?100:!on?0:') + '35*Math.abs(f)');
}
xb2_reg('slotOut', function (m) {
    var L = m.L, G = xb2_geo(m, false), C = xb2_col(m), D = xb2_crowded(m, G) ? null : xb2_twin(m, 'JZ Out Reel B', '', '1');
    var base = xb2_hd(m) + xb2_sel(m) + 'var MM=3+Math.floor(hh(ti*9.1+SD+182)*4),q=wn(PO,o,0.35),ph=(MM+1)*io3(q),j=Math.floor(ph),fr=ph-j,sp=Math.min(0.6,0.72*Math.sin(Math.PI*q));';
    xb2_reel(L, base + 'var cj=j,f=fr,on=q>0&&cj<=MM;', G.fs, C.acc, false);
    if (D) xb2_reel(D, base + 'var cj=j+1,f=fr-1,on=q>0&&q<1&&cj<=MM;', G.fs, C.acc, true);
});

/* ---- clockOut \u2014 \u6642\u8A08\u30EF\u30A4\u30D7: every glyph is swept away by its own little clock hand, one after another
   (AE: the lyric hands over to two exit-only copies \u2014 odd / even glyphs \u2014 each matted by per-glyph pies, so neighbouring
   pies never overlap; browser square cells -> round pies) */
xb2_reg('clockOut', function (m) {
    var L = m.L, G = xb2_geo(m, true), r = G.rect, sz = xb2_sz(m), C = xb2_col(m), H = xb2_hd(m), n = G.N, i, p;
    var cw = !xb2_bit(m, 191), cx = r.left + r.width / 2, cy = r.top + r.height / 2;
    if (G.arranged || xb2_crowded(m, G) || n > 40 || xb2_layers(m) > 40 || G.nk <= 1) {   // one clock sweep over the whole item
        var rw = jzEffect(L, 'ADBE Radial Wipe', 'JZ Out Clock');
        var qs = G.nk <= 1 ? 'var q=wn(PO,' + jzN(xb2_og(m)) + ',0.55);' : 'var q=PO;';
        jzEP(rw, 3, [cx, cy]); jzEP(rw, 4, cw ? 1 : 2); jzEX(rw, 1, H + qs + 'ios(q)*100');
        return;
    }
    var QE = function (idx) { return H + xb2_at(m, idx, n) + 'var q=wn(PO,o,0.55),e=ios(q);'; };
    var TW = [], MT = [];
    for (p = 0; p < 2; p++) {
        var D = xb2_twin(m, 'JZ Out Clock ' + (p ? 'B' : 'A'), '', '1');
        jzAnimator(D, 'JZ Out Clock Parity', [['ADBE Text Opacity', 0]], '(textIndex-1)%2==' + p + '?0:100');
        TW.push(D);
    }
    for (p = 0; p < 2; p++) { MT.push(xb2_shape(m, 'JZ Out Clock Matte ' + (p ? 'B' : 'A'), TW[p])); xb2_matte(TW[p], MT[p]); }
    m.parts.op.push('f*=PO>0?0:1;');
    var T = xb2_main(m, G) ? xb2_shape(m, 'JZ Out Clock Hands', MT[0]) : null, lw = Math.max(1.5 * m.u, sz * 0.03);
    for (i = 0; i < n; i++) {
        var g = G.g[i]; if (g.sp) continue;
        var R = Math.sqrt(g.w * g.w + g.h * g.h) * 0.56 + sz * 0.04, E = QE(i);
        // pie = circle of radius R/2 stroked R wide, trimmed to the part the hand has not reached yet (starts at 12 o'clock, clockwise)
        var pg = jzGrp(MT[i % 2], 'pie ' + (i + 1));
        jzAddEllipse(pg, R, R);
        if (cw) jzAddTrimPaths(pg, null, E + 'e*100'); else jzAddTrimPaths(pg, E + '(1-e)*100', null);
        jzAddStroke(pg, '#FFFFFF', R);
        jzGX(pg).property('ADBE Vector Position').setValue([g.cx, g.cy]);
        jzSetExpr(jzGX(pg).property('ADBE Vector Group Opacity'), E + 'q<1?100:0');
        if (!T) continue;
        var AE2 = E + 'var a=q>0&&q<1?Math.min(1,(1-e)*6)*100:0;';
        var wg = jzGrp(T, 'glow ' + (i + 1)), Rg = R * 0.8;
        jzAddEllipse(wg, Rg, Rg);
        if (cw) jzAddTrimPaths(wg, E + 'e*100', E + 'Math.max(0,e-0.0955)*100');
        else jzAddTrimPaths(wg, E + 'Math.min(1,1-e+0.0955)*100', E + '(1-e)*100');
        jzAddStroke(wg, C.acc, Rg, 22);
        jzGX(wg).property('ADBE Vector Position').setValue([g.cx, g.cy]);
        jzSetExpr(jzGX(wg).property('ADBE Vector Group Opacity'), AE2 + 'a');
        var hg = jzGrp(T, 'hand ' + (i + 1));
        jzAddPath(hg, [[0, 0], [0, -R * 0.82]], false);
        jzAddStroke(hg, C.acc, lw);
        jzAddEllipse(hg, lw * 2.6, lw * 2.6);
        jzAddFill(hg, C.acc);
        jzGX(hg).property('ADBE Vector Position').setValue([g.cx, g.cy]);
        jzSetExpr(jzGX(hg).property('ADBE Vector Rotation'), E + (cw ? '' : '-') + 'e*360');
        jzSetExpr(jzGX(hg).property('ADBE Vector Group Opacity'), AE2 + 'a');
    }
});

/* ---- matrixOut \u2014 \u30C7\u30B8\u30BF\u30EB\u96E8: glyphs decode into falling columns of flickering characters that pour off the bottom */
xb2_reg('matrixOut', function (m) {
    var L = m.L, G = xb2_geo(m, false), F = xb2_frame(m), sz = xb2_sz(m), C = xb2_col(m), fs = G.fs, k, Ds = [];
    var head = C.dark ? xb2_mix(C.c0, '#FFFFFF', 0.4) : C.c0, cb = xb2_cbox(L, G.rect), D = Math.max(sz * 2, (m.H - cb.y0) * F.k + sz * 1.2), K = D + sz * 6, pt = fs * 0.92;
    var nl = xb2_layers(m), nT = xb2_crowded(m, G) ? 0 : (nl > 44 ? 1 : (nl > 34 ? 2 : (nl > 24 ? 3 : 4)));
    for (k = nT; k >= 1; k--) Ds[k - 1] = xb2_twin(m, 'JZ Out Rain ' + k, '', '1', null, true);
    var base = xb2_hd(m) + xb2_sel(m) + 'var q=wn(PO,hh(ti*4.7+SD+201)*0.9,0.5),u=cl((q-0.22)/0.78),Lf=' + jzN(D) + '*Math.pow(u,1.7);';
    var SG = 'seedRandom(ti*31+SD+Math.floor(time*12)*7', vK = [F.dn[0] * K, F.dn[1] * K, 0];
    jzAnimator(L, 'JZ Out Rain Fall', [['ADBE Text Position 3D', vK]], base + 'var a=q>0&&q<1?Lf/' + jzN(K) + '*100:0;[a,a,0]');
    jzAnimator(L, 'JZ Out Rain Sign', [['ADBE Text Character Offset', 60]], base + SG + ',true);var rr=random(15,100),dec=random()<q/0.22+0.2;q>0&&q<1&&(q>=0.22||dec)?rr:0');
    jzAnimator(L, 'JZ Out Rain Decode', [['ADBE Text Fill Color', jzHex(C.acc)]], base + SG + ',true);var rr=random(15,100),dec=random()<q/0.22+0.2;q>0&&q<0.22&&dec?100:0');
    jzAnimator(L, 'JZ Out Rain Head', [['ADBE Text Fill Color', jzHex(head)]], base + 'q>=0.22&&q<1?100:0');
    jzAnimator(L, 'JZ Out Hide', [['ADBE Text Opacity', 0]], base + 'q>=1?100:0');
    for (k = 1; k <= nT; k++) {
        var T = Ds[k - 1], bk = base + 'var on=q>=0.22&&q<1&&Lf>=' + jzN(k * pt * 0.6) + ';';
        jzAnimator(T, 'JZ Rain Fall', [['ADBE Text Position 3D', vK]], bk + 'var a=on?(Lf-' + jzN(k * pt) + ')/' + jzN(K) + '*100:0;[a,a,0]');
        jzAnimator(T, 'JZ Rain Sign', [['ADBE Text Character Offset', 60]], bk + SG + '+' + (k * 1009) + ',true);var rr=random(15,100);on?rr:0');
        jzAnimator(T, 'JZ Rain Tint', [['ADBE Text Fill Color', jzHex(C.acc)]], '100');
        jzAnimator(T, 'JZ Rain Fade', [['ADBE Text Opacity', 0]], bk + 'on?(1-Math.min(1,' + jzN(1.25 * (1 - k / 6)) + '*(1-0.5*u)))*100:100');
    }
});

/* ---- tornadoOut \u2014 \u7ADC\u5DFB: the glyphs are caught in a vortex, orbiting a vertical axis (front / back) while they are lifted away */
xb2_reg('tornadoOut', function (m) {
    var L = m.L, G = xb2_geo(m, true), F = xb2_frame(m), sz = xb2_sz(m), dir = xb2_bit(m, 211) ? 1 : -1, single = xb2_single(m, G), i;
    var cb = xb2_cbox(L, G.rect), up = Math.max(sz * 2, cb.y1 * F.k + sz * 1.8), OX = [], R0 = 1;
    var cx = single ? m.W / 2 : cb.cx;
    for (i = 0; i < G.N; i++) { var c = xb2_toComp(L, G.g[i].cx, G.g[i].cy), ox = (c[0] - cx) * F.k; OX.push(ox); R0 = Math.max(R0, Math.abs(ox)); }
    if (single) R0 = m.W * 0.45 * F.k;
    R0 += sz * 0.3;
    var K = R0 * 2.5 + up * 1.5 + sz * 3;
    var sel = xb2_hd(m) + xb2_dirs(F) + xb2_sel(m) + 'var OX=' + xb2_arr(OX) + ',R0=' + jzN(R0) + ',q=wn(PO,hh(ti*6.7+SD+214)*0.5,0.25),on=q>0,ox=OX[ti-1]||0,r=hh(ti*6.3+SD+212),hx=' + (single ? jzN(xb2_og(m)) : 'o') + ',' +
        'th0=Math.asin(Math.max(-1,Math.min(1,ox/R0))),th=th0+' + dir + '*Math.PI*(3.2+r)*q*q,lift=' + jzN(up) + '*Math.pow(q,2.2)*(0.8+0.3*r)+SZ*1.4*hx*oc(q),' +
        'R=R0*(1-0.35*oc(q))+lift*0.25+SZ*0.6*q,dx=R*Math.sin(th)-ox,dy=-lift,z=Math.cos(th),al=1-sm(0.8,0.98,PO);';
    jzAnimator(L, 'JZ Out Vortex', [['ADBE Text Position 3D', [K, K, 0]]], sel + 'on?[(RT0*dx+DN0*dy)/' + jzN(K) + '*100,(RT1*dx+DN1*dy)/' + jzN(K) + '*100,0]:[0,0,0]');
    jzAnimator(L, 'JZ Out Vortex Tilt', [['ADBE Text Rotation', 14]], sel + 'on?-Math.sin(th)*q*100:0');
    jzAnimator(L, 'JZ Out Vortex Depth', [['ADBE Text Scale 3D', [200, 200, 100]]], sel + 'on?[(z-1)*28,(z-1)*28,0]:[0,0,0]');
    jzAnimator(L, 'JZ Out Vortex Fade', [['ADBE Text Opacity', 0]], sel + 'on?(1-(0.3+0.35*(z+1))*al)*100:0');
});

/* ---- rollUpOut \u2014 \u5DFB\u304D\u53D6\u308B: a paper roll travels along the line, swallowing the text as it goes, then is lifted away */
xb2_reg('rollUpOut', function (m) {
    var L = m.L, G = xb2_geo(m, true), r = G.rect, F = xb2_frame(m), sz = xb2_sz(m), C = xb2_col(m), H = xb2_hd(m), vert = G.vert, rev = xb2_bit(m, 221), single = xb2_single(m, G), i;
    var pad = sz * 0.08, a0 = (vert ? r.top : r.left) - pad, a1 = (vert ? r.top + r.height : r.left + r.width) + pad;
    var c0x = (vert ? r.left : r.top) - pad, c1x = (vert ? r.left + r.width : r.top + r.height) + pad, r0 = sz * 0.26, sg = rev ? -1 : 1, S0 = a0, S1 = a1, XR;
    if (single) {
        var pc = xb2_toComp(L, r.left + r.width / 2, r.top + r.height / 2);
        S0 = xb2_toLayer(L, m.W * 0.03, pc[1])[0]; S1 = xb2_toLayer(L, m.W * 0.97, pc[1])[0];
        XR = rev ? jzN(S1) + '+' + xb2_n(S0 - S1) + '*e' : jzN(S0) + '+' + xb2_n(S1 - S0) + '*e';
    } else XR = rev ? jzN(a1 + r0) + '+' + xb2_n(a0 - r0 * 3 - a1 - r0) + '*e' : jzN(a0 - r0) + '+' + xb2_n(a1 + r0 * 3 - a0 + r0) + '*e';
    var E0 = single ? (rev ? S1 - r0 : S0 + r0) : (rev ? a1 : a0);
    var RE = 'var e=ios(cl(PO/0.86)),Xr=' + XR + ',rl=' + (rev ? 'Math.max(0,' + jzN(S1) + '-Xr)' : 'Math.max(0,Xr-' + xb2_n(S0) + ')') + ',rr=' + jzN(r0) + '*Math.sqrt(1+rl/(SZ*2)),edge=Xr+' + sg + '*rr;';
    var CA = [], HW = [];
    for (i = 0; i < G.N; i++) { var g = G.g[i]; CA.push(vert ? g.cy : g.cx); HW.push((vert ? g.h : g.w) / 2); }
    var sel = H + RE + 'var CA=' + xb2_arr(CA) + ',HW=' + xb2_arr(HW) + ',c=CA[textIndex-1]||0,hw=HW[textIndex-1]||1,dd=(c-edge)*' + sg + ',near=PO>0?cl(1-(dd-hw)/(rr*2.6)):0,k=1-0.55*Math.pow(near,1.6);';
    var KP = sz;
    jzAnimator(L, 'JZ Out Roll Bunch', [['ADBE Text Position 3D', vert ? [0, KP, 0] : [KP, 0, 0]]], sel + 'var a=' + (-sg) + '*(1-k)*hw/' + jzN(KP) + '*100;[a,a,0]');
    jzAnimator(L, 'JZ Out Roll Squeeze', [['ADBE Text Scale 3D', vert ? [100, 0, 100] : [0, 100, 100]]], sel + '(1-k)*100');
    jzAnimator(L, 'JZ Out Roll Shade', [['ADBE Text Fill Color', jzHex(C.bg)]], sel + '45*near');
    jzAnimator(L, 'JZ Out Hide', [['ADBE Text Opacity', 0]], sel + 'PO>0&&dd+hw<=0?100:0');
    // the flat paper starts at the roll's edge: mask edge moved by expansion
    var BIG = XB2_BIG, EX = H + RE + 'PO>0?' + (rev ? '(edge-' + xb2_n(E0) + ')' : '-(edge-' + xb2_n(E0) + ')') + ':1e4';
    if (vert) xb2_maskRect(L, c0x - BIG, rev ? E0 - BIG : E0, c1x + BIG, rev ? E0 : E0 + BIG, 'JZ Out Roll', EX);
    else xb2_maskRect(L, rev ? E0 - BIG : E0, c0x - BIG, rev ? E0 : E0 + BIG, c1x + BIG, 'JZ Out Roll', EX);
    if (!xb2_main(m, single ? null : G) || (single && xb2_layers(m) > 40)) return;
    // the roll: back, highlight band, shadow band; finally lifted away (shrinks to its middle)
    var S = xb2_shape(m, 'JZ Out Roll'), cm = (c0x + c1x) / 2, band = [[0.72, 1, xb2_mix(C.c0, C.bg, 0.8)], [0.18, 0.38, xb2_mix(C.c0, C.bg, 0.25)], [0, 1, xb2_mix(C.c0, C.bg, 0.55)]], b;
    var LE = H + RE + 'var lf=ic(cl((PO-0.8)/0.18)),e0=' + jzN(c0x - sz * 0.06) + '+' + xb2_n(cm - c0x + sz * 0.06) + '*lf,e1=' + jzN(c1x + sz * 0.06) + '+' + xb2_n(cm - c1x - sz * 0.06) + '*lf,x0=Xr-rr*(1-lf),w=2*rr*(1-lf);';
    for (b = 0; b < 3; b++) {
        var bg = jzGrp(S, 'roll ' + (b + 1)), rc = jzAddRect(bg, 10, 10, 0), s0 = band[b][0], s1 = band[b][1];
        jzSetExpr(rc.property('ADBE Vector Rect Size'), LE + 'var aw=Math.max(0,' + jzN(s1 - s0) + '*w),cw=Math.max(0,e1-e0);' + (vert ? '[cw,aw]' : '[aw,cw]'));
        jzSetExpr(rc.property('ADBE Vector Rect Position'), LE + 'var ap=x0+' + jzN((s0 + s1) / 2) + '*w,cp=(e0+e1)/2;' + (vert ? '[cp,ap]' : '[ap,cp]'));
        jzAddFill(bg, band[b][2]);     // (after the rect is configured: adding the fill invalidates the rect reference)
    }
    jzSetExpr(jzXf(S, 'ADBE Opacity'), H + 'PO>0&&PO<0.998?(1-sm(0.82,0.98,PO))*100:0');
});

/* ---- snakeOut \u2014 \u5217\u306B\u306A\u3063\u3066\u53BB\u308B: the line runs along itself, bends round a curve and leaves, every glyph following the one before */
function xb2_snakeFn(aEnd, R, n) {
    return 'function snk(a2,o){var AE=' + jzN(aEnd) + ',RR=' + jzN(R) + ',NN=' + n + ';if(a2<=AE)return [a2,o,0];var s=a2-AE,arc=RR*Math.PI/2;' +
        'if(s<=arc){var f=s/RR,ca=Math.cos(f),sa=Math.sin(f);return [AE+RR*sa-NN*sa*o,NN*RR*(1-ca)+ca*o,NN*f*57.2958];}return [AE+RR-NN*o,NN*(RR+s-arc),NN*90];}' +
        'function ez(x){return 0.35*x*x+0.65*Math.pow(x,2.4);}';
}
xb2_reg('snakeOut', function (m) {
    var L = m.L, G = xb2_geo(m, true), r = G.rect, F = xb2_frame(m), sz = xb2_sz(m), rev = xb2_bit(m, 231), n = xb2_bit(m, 232) ? 1 : -1, W = m.W, Hh = m.H, i;
    if (xb2_single(m, G)) {        // one glyph per layer: the whole train runs along the frame and turns off it
        var ux = rev ? -1 : 1, aE = rev ? -W * 0.1 : W * 0.9, RS = Math.min(W, Hh) * 0.2, pc = xb2_toComp(L, r.left + r.width / 2, r.top + r.height / 2);
        var SN = xb2_snakeFn(aE, RS, n) + 'var nn=snk(' + jzN(pc[0] * ux) + '+' + jzN(W * 1.2 + RS * 2 + Hh * 0.8) + '*ez(PO),' + jzN((pc[1] - Hh / 2) * ux) + ');';
        m.parts.pos.push(SN + 'd=[d[0]+nn[0]*' + ux + '-' + xb2_n(pc[0]) + ',d[1]+' + jzN(Hh / 2) + '+nn[1]*' + ux + '-' + xb2_n(pc[1]) + '];');
        m.parts.rot.push(SN + 'r+=nn[2];');
        return;
    }
    var vert = G.vert, u = vert ? [0, 1] : [1, 0];
    if (rev) u = [-u[0], -u[1]];
    var v = [-u[1], u[0]], bcx = r.left + r.width / 2, bcy = r.top + r.height / 2, oc = bcx * v[0] + bcy * v[1], A = [], O = [], aMin = 1e9, aMax = -1e9;
    for (i = 0; i < G.N; i++) {
        var g = G.g[i], a = g.cx * u[0] + g.cy * u[1];
        A.push(a); O.push(g.cx * v[0] + g.cy * v[1] - oc);
        if (!g.sp) { aMin = Math.min(aMin, a); aMax = Math.max(aMax, a); }
    }
    if (aMax < aMin) { aMin = 0; aMax = 0; }
    var aEnd = aMax + sz * 0.5, R = sz * 1.3, Dt = (aEnd - aMin) + R * 2 + Math.max(W, Hh) * 0.75 * F.k, K = Dt + R * 2 + sz;
    var sel = xb2_hd(m) + xb2_snakeFn(aEnd, R, n) + 'var A=' + xb2_arr(A) + ',O=' + xb2_arr(O) + ',a=A[textIndex-1]||0,ov=O[textIndex-1]||0,nn=snk(a+' + jzN(Dt) + '*ez(PO),ov),' +
        'dx=' + xb2_n(u[0]) + '*(nn[0]-a)+' + xb2_n(v[0]) + '*(nn[1]-ov),dy=' + xb2_n(u[1]) + '*(nn[0]-a)+' + xb2_n(v[1]) + '*(nn[1]-ov);';
    jzAnimator(L, 'JZ Out Train', [['ADBE Text Position 3D', [K, K, 0]]], sel + '[dx/' + jzN(K) + '*100,dy/' + jzN(K) + '*100,0]');
    jzAnimator(L, 'JZ Out Train Turn', [['ADBE Text Rotation', 90]], sel + 'nn[2]/0.9');
    m.parts.op.push('f*=1-sm(0.9,1,PO);');
});

/* ---- flutterOut \u2014 \u3072\u3089\u3072\u3089\u843D\u3061\u308B: glyphs detach one by one and flutter down like paper, swaying, tilting and flipping over */
xb2_reg('flutterOut', function (m) {
    var L = m.L, G = xb2_geo(m, false), F = xb2_frame(m), sz = xb2_sz(m), C = xb2_col(m);
    var cb = xb2_cbox(L, G.rect), D = Math.max(sz * 2, (m.H - cb.y0) * F.k + sz * 1.2), K = D + sz, back = xb2_mix(C.c0, C.bg, 0.45);
    var sel = xb2_hd(m) + xb2_dirs(F) + xb2_sel(m) + 'var q=wn(PO,hh(ti*3.9+SD+241),0.5),on=q>0&&q<1,ph=hh(ti*2.3+SD+242)*6.2832,w=7+4*hh(ti*5.3+SD+243),am=Math.min(1,q*4),' +
        'sw=Math.sin(q*w+ph)*SZ*0.55*am,Lf=' + jzN(D) + '*(0.25*q+0.75*Math.pow(q,1.6)),fl=Math.cos(q*(9+5*hh(ti*7.7+SD+244))+ph*0.5),' +
        'shv=q<0.08?Math.sin(q*300)*6*(1-q/0.08):0,rt=Math.cos(q*w+ph)*28*am+shv,sx=1+(fl-1)*am;';
    jzAnimator(L, 'JZ Out Flutter', [['ADBE Text Position 3D', [K, K, 0]]], sel + 'on?[(RT0*sw+DN0*Lf)/' + jzN(K) + '*100,(RT1*sw+DN1*Lf)/' + jzN(K) + '*100,0]:[0,0,0]');
    jzAnimator(L, 'JZ Out Flutter Tilt', [['ADBE Text Rotation', 40]], sel + 'on?rt/0.4:0');
    jzAnimator(L, 'JZ Out Flip', [['ADBE Text Scale 3D', [-100, 100, 100]]], sel + 'on?[(1-sx)/2*100,0,0]:[0,0,0]');
    jzAnimator(L, 'JZ Out Flip Back', [['ADBE Text Fill Color', jzHex(back)]], sel + 'on&&fl<0&&am>0.5?100:0');
    jzAnimator(L, 'JZ Out Hide', [['ADBE Text Opacity', 0]], sel + 'q>=1?100:0');
});

/* ---- rollOff \u2014 \u8EE2\u304C\u3063\u3066\u53BB\u308B: each glyph tips over its corner and rolls away like a die, leading edge first */
xb2_reg('rollOff', function (m) {
    var L = m.L, G = xb2_geo(m, true), F = xb2_frame(m), sz = xb2_sz(m), dir = xb2_bit(m, 251) ? 1 : -1, RR = [], i;
    var ED = xb2_edgeDist(m, G, F, dir, sz * 1.3), K = ED.mx + sz * 2;
    for (i = 0; i < G.N; i++) RR.push(Math.max(G.g[i].w, sz * 0.8) / 2);
    var sel = xb2_hd(m) + xb2_dirs(F) + xb2_sel(m) + (dir > 0 ? 'o=1-o;' : '') + 'var DI=' + xb2_arr(ED.DI) + ',RR=' + xb2_arr(RR) + ',q=wn(PO,o,0.45),on=q>0&&q<1,' +
        'dist=DI[ti-1]||DI[0],rd=RR[ti-1]||RR[0],along=dist*Math.pow(q,1.7),th=along/rd,hp=Math.PI/2,ph=((th%hp)+hp)%hp,rise=rd*(Math.SQRT2*Math.cos(ph-Math.PI/4)-1),al=' + dir + '*along;';
    jzAnimator(L, 'JZ Out Roll', [['ADBE Text Position 3D', [K, K, 0]]], sel + 'on?[(RT0*al-DN0*rise)/' + jzN(K) + '*100,(RT1*al-DN1*rise)/' + jzN(K) + '*100,0]:[0,0,0]');
    jzAnimator(L, 'JZ Out Roll Turn', [['ADBE Text Rotation', 3600]], sel + 'on?' + dir + '*th*57.2958/36:0');
    jzAnimator(L, 'JZ Out Hide', [['ADBE Text Opacity', 0]], sel + 'q>=1?100:0');
});

/* ---- fanClose \u2014 \u6247\u3092\u9589\u3058\u308B: the line bends into an arc round a pivot, the ribs close onto one edge and the fan shrinks away */
function xb2_fanFn(R, aLim, rev) {
    return 'var bend=ios(cl(PO/0.3)),cls=io3(cl((PO-0.24)/0.5)),fin=cl((PO-0.72)/0.28),fs=1-0.75*iq(fin),al=1-sm(0.8,0.99,PO),spin=' + (rev ? 1 : -1) + '*0.5*iq(fin);' +
        'function plc(a,w){var alT=(a/' + jzN(R) + ')+(' + jzN(aLim) + '-a/' + jzN(R) + ')*cls+spin,rr=' + jzN(R) + '-w;return [a+(Math.sin(alT)*rr-a)*bend,w+((' + jzN(R) + '-Math.cos(alT)*rr)-w)*bend,alT*bend*57.2958];}';
}
xb2_reg('fanClose', function (m) {
    var L = m.L, G = xb2_geo(m, true), r = G.rect, sz = xb2_sz(m), rev = xb2_bit(m, 261), i;
    if (xb2_single(m, G)) {       // one glyph per layer: the whole frame is the fan
        var W = m.W, Hh = m.H, RS = W * 0.55, aL = (rev ? 1 : -1) * W * 0.45 / RS, pc = xb2_toComp(L, r.left + r.width / 2, r.top + r.height / 2);
        var N = jzCount(m.c.text || ''), lead = Math.round(m.o.mi || 0) === (rev ? N - 1 : 0);
        var FN = xb2_fanFn(RS, aL, rev) + 'var nn=plc(' + jzN(pc[0] - W / 2) + ',' + jzN(pc[1] - Hh / 2) + ');';
        m.parts.pos.push(FN + 'd=[d[0]+' + jzN(W / 2) + '+nn[0]-' + xb2_n(pc[0]) + ',d[1]+' + jzN(Hh / 2) + '+nn[1]-' + xb2_n(pc[1]) + '];');
        m.parts.rot.push(FN + 'r+=nn[2];');
        m.parts.sc.push(FN + 'f=[f[0]*fs,f[1]*fs];');
        m.parts.op.push(FN + 'f*=al' + (lead ? '' : '*(1-sm(0.55,0.95,cls))') + ';');
        return;
    }
    var vert = G.vert, u = vert ? [0, 1] : [1, 0], v = [-u[1], u[0]], bcx = r.left + r.width / 2, bcy = r.top + r.height / 2;
    var ac = bcx * u[0] + bcy * u[1], oc = bcx * v[0] + bcy * v[1], A = [], WV = [], aMin = 1e9, aMax = -1e9;
    for (i = 0; i < G.N; i++) {
        var g = G.g[i], a = g.cx * u[0] + g.cy * u[1] - ac;
        A.push(a); WV.push(g.cx * v[0] + g.cy * v[1] - oc);
        if (!g.sp) { aMin = Math.min(aMin, a); aMax = Math.max(aMax, a); }
    }
    if (aMax < aMin) { aMin = 0; aMax = 0; }
    var half = Math.max(sz, (aMax - aMin) / 2), R = Math.max(sz * 2, half * 1.15), aLim = (rev ? aMax : aMin) / R, K = R * 2 + half * 2 + sz;
    var sel = xb2_hd(m) + xb2_fanFn(R, aLim, rev) + 'var A=' + xb2_arr(A) + ',WV=' + xb2_arr(WV) + ',a=A[textIndex-1]||0,w=WV[textIndex-1]||0,nn=plc(a,w),' +
        'dx=' + xb2_n(u[0]) + '*(nn[0]-a)+' + xb2_n(v[0]) + '*(nn[1]-w),dy=' + xb2_n(u[1]) + '*(nn[0]-a)+' + xb2_n(v[1]) + '*(nn[1]-w),' +
        'lead=Math.abs(a/' + jzN(R) + '-' + xb2_n(aLim) + ')<0.001,aa=al*(lead?1:1-sm(0.55,0.95,cls));';
    jzAnimator(L, 'JZ Out Fan', [['ADBE Text Position 3D', [K, K, 0]]], sel + '[dx/' + jzN(K) + '*100,dy/' + jzN(K) + '*100,0]');
    jzAnimator(L, 'JZ Out Rib', [['ADBE Text Rotation', 180]], sel + 'nn[2]/1.8');
    jzAnimator(L, 'JZ Out Fan Size', [['ADBE Text Scale 3D', [0, 0, 100]]], sel + '[(1-fs)*100,(1-fs)*100,0]');
    jzAnimator(L, 'JZ Out Fan Fade', [['ADBE Text Opacity', 0]], sel + '(1-aa)*100');
});

/* ================================================================ SIGNAL / GRAPHIC */

/* ---- rgbSplitOut \u2014 \u8272\u5206\u89E3: the text splits into its ghost / accent channels which drift apart with a jitter and fade */
xb2_reg('rgbSplitOut', function (m) {
    var L = m.L, G = xb2_geo(m, false), C = xb2_col(m), k;
    var cols = [C.gA, C.gB, C.acc], dirs = [[-1, -0.3], [1, 0.25], [0.15, 0.9]];
    m.parts.op.push('f*=1-sm(0.05,0.35,PO);');
    if (!xb2_main(m, G) || xb2_layers(m) > 50) {       // no copies: a jittering tinted fade
        jzAnimator(L, 'JZ Out Split Tint', [['ADBE Text Fill Color', jzHex(C.acc)]], xb2_hd(m) + '60*sm(0,0.3,PO)');
        m.parts.pos.push('seedRandom(SD+Math.floor(time*12)*3,true);var jx=PO>0?random(-1,1)*' + jzN(xb2_sz(m) * 0.08) + '*(0.3+PO):0;d=[d[0]+jx,d[1]];');
        return;
    }
    var pre = 'var e=ic(PO),sp=SZ*(0.05+1.5*e),ca=sm(0,0.1,PO)*(1-sm(0.55,0.96,PO));';
    for (k = 0; k < 3; k++) {
        var pos = 'seedRandom(SD+Math.floor(time*12)*3+' + (k * 17) + ',true);var jx=random(-1,1)*SZ*0.06*(0.3+e),jy=random(-1,1)*SZ*0.015,p=parent.transform.anchorPoint;' +
            '[p[0]+' + xb2_n(dirs[k][0]) + '*sp+jx,p[1]+' + xb2_n(dirs[k][1] * 0.5) + '*sp+jy]';
        var D = xb2_twin(m, 'JZ Out Split ' + (k + 1), pre, 'ca*' + (k === 2 ? '0.8' : '0.9'), pos);
        jzNoGhost(D);                    // the browser draws the channel copies on the main pass only
        var fe = jzEffect(D, 'ADBE Fill', 'JZ Out Channel');
        jzEP(fe, 3, jzHex(cols[k])); jzEP(fe, 7, 1);
        try { D.blendingMode = C.dark ? BlendingMode.SCREEN : BlendingMode.MULTIPLY; } catch (e0) {}
        if (k) jzSetExpr(jzXf(D, 'ADBE Scale'), xb2_hd(m) + 'var s=100*(1+' + jzN(0.08 * k) + '*ic(PO));[s,s]');
    }
});

/* ---- shockOut \u2014 \u885D\u6483\u6CE2: a small implosion, then a ring bursts outward and every glyph is blown away as the ring passes it */
xb2_reg('shockOut', function (m) {
    var L = m.L, G = xb2_geo(m, true), r = G.rect, F = xb2_frame(m), sz = xb2_sz(m), C = xb2_col(m), H = xb2_hd(m), single = xb2_single(m, G), i;
    var cx = r.left + r.width / 2, cy = r.top + r.height / 2, RM = Math.sqrt(r.width * r.width + r.height * r.height) / 2 + sz * 1.8, DD = [], UX = [], UY = [];
    if (single) { var cc = xb2_toLayer(L, m.W / 2, m.H / 2); cx = cc[0]; cy = cc[1]; RM = Math.sqrt(m.W * m.W + m.H * m.H) * 0.55 * F.k; }
    for (i = 0; i < G.N; i++) {
        var dx = G.g[i].cx - cx, dy = G.g[i].cy - cy, d = Math.sqrt(dx * dx + dy * dy) || 1;
        DD.push(d); UX.push(dx / d); UY.push(dy / d);
    }
    var K = sz * 1.7, RE = 'var rg=' + jzN(RM) + '*oc(cl((PO-0.12)/0.88)),pre=PO<0.16?Math.sin(Math.PI*cl(PO/0.16)):0;';
    var sel = H + RE + xb2_sel(m) + 'var DD=' + xb2_arr(DD) + ',UX=' + xb2_arr(UX) + ',UY=' + xb2_arr(UY) + ',dd=DD[ti-1]||1,ps=rg-dd,mv=0,s=1,a=1,rt=0;' +
        'if(PO>0){if(ps<=0){mv=-pre*SZ*0.08;s=1-0.05*pre;}else{var k=1-Math.exp(-ps/(SZ*0.7));mv=SZ*1.6*k;s=1+0.2*Math.exp(-ps/(SZ*0.25))-0.45*sm(0,SZ*2,ps);' +
        'a=1-sm(SZ*0.15,SZ*1.5,ps);rt=(hh(ti*5.9+SD+281)*2-1)*50*k;}}s=Math.max(0.05,s);var ux=UX[ti-1]||0,uy=UY[ti-1]||0;';
    jzAnimator(L, 'JZ Out Shock', [['ADBE Text Position 3D', [K, K, 0]]], sel + '[ux*mv/' + jzN(K) + '*100,uy*mv/' + jzN(K) + '*100,0]');
    jzAnimator(L, 'JZ Out Shock Size', [['ADBE Text Scale 3D', [200, 200, 100]]], sel + '[(s-1)*100,(s-1)*100,0]');
    jzAnimator(L, 'JZ Out Shock Spin', [['ADBE Text Rotation', 50]], sel + 'rt*2');
    jzAnimator(L, 'JZ Out Shock Fade', [['ADBE Text Opacity', 0]], sel + '(1-a)*100');
    if (!xb2_main(m, single ? null : G) || (single && Math.round(m.o.mi || 0) !== 0)) return;
    var S = xb2_shape(m, 'JZ Out Shock Ring'), RG = H + RE + 'var fa=1-sm(0.3,1,PO);', ring = [[1, 0.09, 1.5, 100], [0.82, 0.025, 1, 60]], k;
    for (k = 0; k < 2; k++) {
        var gg = jzGrp(S, 'ring ' + (k + 1)), el = jzAddEllipse(gg, 10, 10, cx, cy);
        jzSetExpr(el.property('ADBE Vector Ellipse Size'), RG + 'var d=Math.max(0.1,rg*' + jzN(ring[k][0] * 2) + ');[d,d]');
        var st = jzAddStroke(gg, C.acc, 2, ring[k][3]);     // (after the ellipse is configured: adding invalidates its reference)
        jzSetExpr(st.property('ADBE Vector Stroke Width'),RG + 'Math.max(' + jzN(ring[k][2] * m.u) + ',SZ*' + jzN(ring[k][1]) + '*fa)');
        jzSetExpr(jzGX(gg).property('ADBE Vector Group Opacity'), RG + 'PO>0&&rg>0.5?fa*100:0');
    }
});

/* ---- floodOut \u2014 \u6C34\u6CA1: a wavy water line rises through the text; what is under water wobbles, tints and sinks out of sight
   (AE: the dry part is cut by a straight, feathered mask edge that rises under the wavy surface line) */
xb2_reg('floodOut', function (m) {
    var L = m.L, G = xb2_geo(m, false), r = G.rect, F = xb2_frame(m), sz = xb2_sz(m), C = xb2_col(m), H = xb2_hd(m), single = xb2_single(m, G), seed = m.c.seed | 0, BIG = XB2_BIG, j, k;
    // layer-space frame: u along screen right (rt), v along screen down (dn); c = box centre
    var pad = sz * 0.15, rt = F.rt, dn = F.dn, cx = r.left + r.width / 2, cy = r.top + r.height / 2, cu = cx * rt[0] + cy * rt[1], cv = cx * dn[0] + cy * dn[1];
    var PB = [[r.left - pad, r.top - pad], [r.left + r.width + pad, r.top - pad], [r.left - pad, r.top + r.height + pad], [r.left + r.width + pad, r.top + r.height + pad]];
    var u0 = 1e9, u1 = -1e9, v0 = 1e9, v1 = -1e9;
    for (k = 0; k < 4; k++) { var pu = PB[k][0] * rt[0] + PB[k][1] * rt[1], pv = PB[k][0] * dn[0] + PB[k][1] * dn[1]; u0 = Math.min(u0, pu); u1 = Math.max(u1, pu); v0 = Math.min(v0, pv); v1 = Math.max(v1, pv); }
    var U = single ? m.H * 0.12 * F.k : sz, A = U * 0.07, V0, V1;
    if (single) {                  // one glyph per layer: the level crosses the frame from 74% to 24% of its height
        var tr = L.property('ADBE Transform Group'), an = tr.property('ADBE Anchor Point').value, ps = tr.property('ADBE Position').value, av = an[0] * dn[0] + an[1] * dn[1];
        V0 = av + (m.H * 0.74 - ps[1]) * F.k; V1 = av + (m.H * 0.24 - ps[1]) * F.k;
    } else { V0 = v1 + A * 2; V1 = v0 - A * 3; }
    var E = 'var e=ios(cl(PO/0.9)),V=' + jzN(V0) + '+' + xb2_n(V1 - V0) + '*e;', fe = Math.max(2 * m.u, A * 1.6);
    var bp = function (v, off) { var d = v - cv + (off || 0); return [cx + dn[0] * d, cy + dn[1] * d]; };     // point on the level line below the centre
    var half = function (v, sg, off) {                       // half plane above (sg -1) / below (sg 1) the level v
        var b0 = bp(v, off), a1 = [b0[0] - rt[0] * BIG, b0[1] - rt[1] * BIG], a2 = [b0[0] + rt[0] * BIG, b0[1] + rt[1] * BIG];
        return [a1, a2, [a2[0] + sg * dn[0] * BIG, a2[1] + sg * dn[1] * BIG], [a1[0] + sg * dn[0] * BIG, a1[1] + sg * dn[1] * BIG]];
    };
    var main = xb2_main(m, single ? null : G);
    // under water: a tinted, refracted copy below the level (drawn on the main pass only)
    if (main && xb2_layers(m) <= 50) {
        var dy = sz * 0.05, D = xb2_twin(m, 'JZ Out Flood Under', E + 'var uw=(1-sm(0.3,0.95,PO))*0.5;', 'uw', 'var p=parent.transform.anchorPoint;[p[0]+' + xb2_n(dn[0] * dy) + ',p[1]+' + xb2_n(dn[1] * dy) + ']', true);
        jzNoGhost(D);
        xb2_maskPoly(D, half(V0, 1, -dy), 'JZ Out Flood Under', H + E + 'PO>0?' + jzN(V0) + '-V:0', fe);
        var fl = jzEffect(D, 'ADBE Fill', 'JZ Out Water Tint'); jzEP(fl, 3, jzHex(xb2_mix(C.c0, C.acc, 0.55))); jzEP(fl, 7, 1);
        xb2_wave(D, 'JZ Out Refract', 0, sz * 2 * Math.PI / 7, H + 'PO>0?SZ*0.04:0', 'time*343.8');
    }
    // above water: the lyric, cut at the level (mask edge moved by expansion)
    xb2_maskPoly(L, half(V0, -1), 'JZ Out Flood', H + E + 'PO>0?-(' + jzN(V0) + '-V):1e4', fe);
    m.parts.op.push(E + 'f*=V<' + jzN(v0 - A * 2) + '?0:1;');
    if (!main) return;
    // the water line (two sine waves) + bubbles rising to it, drawn upright in a group turned back to the screen axes
    var S = xb2_shape(m, 'JZ Out Flood Line'), LE = H + E + 'var fa=PO>0?1-sm(0.82,0.98,PO):0;', x0 = u0 - cu - sz * 0.2, x1 = u1 - cu + sz * 0.2;
    var wg = jzGrp(S, 'water');
    jzGX(wg).property('ADBE Vector Position').setValue([cx, cy]); jzGX(wg).property('ADBE Vector Rotation').setValue(-F.deg);
    // (each sub-group / item is finished before the next sibling is added: adding one invalidates references to the others)
    var cg = xb2_sub(wg, 'core');
    jzAddPath(cg, [[x0, 0], [x1, 0]], false); jzAddStroke(cg, C.acc, Math.max(1.5 * m.u, U * 0.028));
    jzSetExpr(jzGX(cg).property('ADBE Vector Position'), LE + '[0,V-' + xb2_n(cv) + ']');
    var gg = xb2_sub(wg, 'glow');
    jzAddPath(gg, [[x0, 0], [x1, 0]], false); jzAddStroke(gg, C.acc, Math.max(4 * m.u, U * 0.1), 15);
    jzSetExpr(jzGX(gg).property('ADBE Vector Position'), LE + '[0,V-' + xb2_n(cv) + ']');
    for (j = 0; j < 7; j++) {
        var bgp = xb2_sub(wg, 'bubble ' + (j + 1)), bx = x0 + (x1 - x0) * xb2_r(seed, j, 291), rb = xb2_r(seed, j, 292);
        var BE = LE + 'var ph=(PO*2.2+' + jzN(rb) + ')%1,by=V+' + jzN(A) + '+(1-ph)*SZ*0.9;';
        var be = jzAddEllipse(bgp, 10, 10);
        jzSetExpr(be.property('ADBE Vector Ellipse Size'), BE + 'var d=2*Math.max(1,SZ*0.03*(0.5+ph));[d,d]');
        jzAddStroke(bgp, C.acc, Math.max(1 * m.u, sz * 0.01));
        jzSetExpr(jzGX(bgp).property('ADBE Vector Position'), BE + '[' + jzN(bx) + '+Math.sin(ph*9+' + j + ')*SZ*0.03,by-' + xb2_n(cv) + ']');
        jzSetExpr(jzGX(bgp).property('ADBE Vector Group Opacity'), BE + 'by>' + jzN(v1 + sz * 0.3) + '?0:70*fa*(1-ph*0.5)');
    }
    jzSetExpr(jzXf(S, 'ADBE Opacity'), LE + 'fa*100');
    xb2_wave(S, 'JZ Out Surface 1', 90, U * 2 * Math.PI / 4.2, H + jzN(A), 'time*286.5');
    xb2_wave(S, 'JZ Out Surface 2', 90, U * 2 * Math.PI / 9.5, H + jzN(A * 0.5), '-time*418.3');
});

/* ---- slashOut \u2014 \u4E00\u5200\u4E21\u65AD: one clean sword stroke: a flash along the line, a beat, then the upper half slides off along the cut */
xb2_reg('slashOut', function (m) {
    var L = m.L, G = xb2_geo(m, false), r = G.rect, sz = xb2_sz(m), C = xb2_col(m), H = xb2_hd(m), vert = G.vert;
    var pad = sz * 0.2, bw = r.width + pad * 2, bh = r.height + pad * 2, cx = r.left + r.width / 2, cy = r.top + r.height / 2;
    var tilt = (xb2_bit(m, 301) ? 1 : -1) * (vert ? 16 + 8 * xb2_cr(m, 302) : 7 + 5 * xb2_cr(m, 302)), th = ((vert ? 90 : 0) + tilt) * Math.PI / 180;
    var d = [Math.cos(th), Math.sin(th)], n = [-d[1], d[0]], LL = Math.sqrt(bw * bw + bh * bh) + sz * 4, dn = d[1] > 0 ? d : [-d[0], -d[1]];
    var half = function (sg) {
        var o = [cx + n[0] * sg * LL, cy + n[1] * sg * LL];
        return [[cx - d[0] * LL, cy - d[1] * LL], [cx + d[0] * LL, cy + d[1] * LL], [o[0] + d[0] * LL, o[1] + d[1] * LL], [o[0] - d[0] * LL, o[1] - d[1] * LL]];
    };
    var E = 'var fl=cl(PO/0.12),gap=sm(0.12,0.3,PO),v=cl((PO-0.3)/0.7),iv=iq(v);';
    // the upper half: a masked copy that slides downhill along the cut
    if (xb2_layers(m) <= 56) {
        var D = xb2_twin(m, 'JZ Out Slash Top', E, '1-sm(0.62,0.98,PO)');
        xb2_maskPoly(D, half(-1), 'JZ Out Slash Top');
        xb2_xform(D, 'JZ Out Slash Slide', cx, cy, H + E + 'var sl=SZ*0.06*gap+SZ*2.2*iv;[' + jzN(cx) + '+' + xb2_n(dn[0]) + '*sl-' + xb2_n(n[0]) + '*SZ*0.05*gap,' + jzN(cy) + '+' + xb2_n(dn[1]) + '*sl-' + xb2_n(n[1]) + '*SZ*0.05*gap]');
    }
    // the lower half: the lyric itself, cut along the line (mask active only in the exit), nudged and faded
    xb2_maskPoly(L, half(1), 'JZ Out Slash', H + 'PO>0?0:1e4');
    xb2_xform(L, 'JZ Out Slash Drop', cx, cy, H + E + '[' + jzN(cx) + '-' + xb2_n(dn[0]) + '*SZ*0.35*iv+' + xb2_n(n[0]) + '*SZ*0.03*gap,' + jzN(cy) + '-' + xb2_n(dn[1]) + '*SZ*0.35*iv+' + xb2_n(n[1]) + '*(SZ*0.03*gap+SZ*0.5*iv)]');
    m.parts.op.push('f*=1-sm(0.5,0.95,PO);');
    if (!xb2_main(m, G)) return;
    // the flash of the blade along the cut
    var ext = (vert ? r.height : r.width) / 2 + sz * 0.9, P0 = [cx - d[0] * ext, cy - d[1] * ext], P1 = [cx + d[0] * ext, cy + d[1] * ext], st = xb2_bit(m, 303);
    var S = xb2_shape(m, 'JZ Out Slash Flash'), FE = H + E + 'var fa=PO>0?1-sm(0.12,0.34,PO):0;', k, W2 = [[0.09, 2, 35], [0.03, 1.5, 100]];
    for (k = 0; k < 2; k++) {
        var gg = jzGrp(S, k ? 'core' : 'glow');
        jzAddPath(gg, st ? [P1, P0] : [P0, P1], false);
        jzAddTrimPaths(gg, FE + 'oe(fl)*100', FE + 'sm(0.08,0.3,PO)*100');
        var sk = jzAddStroke(gg, C.acc, 2, W2[k][2]);
        jzSetExpr(sk.property('ADBE Vector Stroke Width'), FE + 'Math.max(' + jzN(W2[k][1] * m.u) + ',SZ*' + jzN(W2[k][0]) + '*fa)');
    }
    jzSetExpr(jzXf(S, 'ADBE Opacity'), FE + 'fa*100');
});

/* ---- mosaicOut \u2014 \u30E2\u30B6\u30A4\u30AF: the text is pixelated into ever larger blocks while it fades */
// (browser: an off-screen low-res copy on the main pass only -> AE: an exit-only copy of the lyric with a Mosaic effect, kept out
//  of the chroma ghosts; text layers are rasterised in comp space, so the block count is set in comp px)
xb2_reg('mosaicOut', function (m) {
    var L = m.L, G = xb2_geo(m, false), F = xb2_frame(m), H = xb2_hd(m) + 'var b=SZ*' + jzN(F.s) + '*(0.01+0.34*Math.pow(PO,1.5)),on=PO>0&&b>=1.6;';
    var T = L;
    if (!xb2_crowded(m, G) && xb2_layers(m) <= 56) {
        T = xb2_twin(m, 'JZ Out Mosaic', '', '1-sm(0.55,0.97,PO)');
        jzNoGhost(T);
        m.parts.op.push('f*=PO>0?0:1;');
    } else m.parts.op.push('f*=1-sm(0.55,0.97,PO);');
    var mo = jzEffect(T, 'ADBE Mosaic', 'JZ Out Mosaic');
    jzEX(mo, 1, H + 'on?Math.max(1,Math.min(4000,Math.round(thisComp.width/b))):4000');
    jzEX(mo, 2, H + 'on?Math.max(1,Math.min(4000,Math.round(thisComp.height/b))):4000');
    jzEP(mo, 3, 0);
});

/* ---- scribbleOut \u2014 \u3050\u3057\u3083\u3050\u3057\u3083\u6D88\u3057: a thick marker scrawls back and forth over each line, then text and scribble fade together */
xb2_reg('scribbleOut', function (m) {
    var L = m.L, G = xb2_geo(m, true), sz = xb2_sz(m), C = xb2_col(m), H = xb2_hd(m), seed = m.c.seed | 0, vert = G.vert, lines = xb2_lines(G), li;
    m.parts.op.push('f*=1-sm(0.45,0.75,PO);');
    if (!lines.length || xb2_layers(m) > 60) return;
    var S = xb2_shape(m, 'JZ Out Scribble');
    for (li = 0; li < lines.length; li++) {
        var a0 = lines[li].u0 - sz * 0.15, a1 = lines[li].u1 + sz * 0.15, mid = lines[li].v, hf = G.fs * 0.5, pts = [], a = a0, k = 0;
        while (a < a1 && k < 200) {              // forward: uneven strokes, the hand drifts up and down
            var dr = Math.sin(a / sz * 1.3 + li) * hf * 0.18, o = dr + ((k & 1) ? 1 : -1) * hf * (0.8 + 0.45 * xb2_r(seed, li, k, 312));
            pts.push(vert ? [mid + o, a] : [a, mid + o]);
            a += sz * (0.14 + 0.2 * xb2_r(seed, li, k, 311)); k++;
        }
        a = a1;
        while (a > a0 && k < 400) {              // back again, flatter and faster, filling the gaps
            var dr2 = Math.sin(a / sz * 2.1 + li * 3) * hf * 0.25, o2 = dr2 + ((k & 1) ? 1 : -1) * hf * (0.45 + 0.4 * xb2_r(seed, li, k, 314));
            pts.push(vert ? [mid + o2, a] : [a, mid + o2]);
            a -= sz * (0.2 + 0.25 * xb2_r(seed, li, k, 313)); k++;
        }
        if (pts.length < 2) continue;
        var g = jzGrp(S, 'scribble ' + (li + 1));
        jzAddPath(g, pts, false);
        jzAddTrimPaths(g, H + 'ios(cl(PO/0.58))*100', null);
        var st = jzAddStroke(g, C.acc, sz * 0.15);
        try { st.property('ADBE Vector Stroke Line Cap').setValue(2); st.property('ADBE Vector Stroke Line Join').setValue(2); } catch (e0) {}
        jzSetExpr(st.property('ADBE Vector Stroke Width'), H + 'Math.max(1,SZ*0.15*(1-0.7*sm(0.62,0.95,PO)))');
    }
    jzSetExpr(jzXf(S, 'ADBE Opacity'), H + 'PO>0?(1-sm(0.66,0.97,PO))*100:0');
});

/* ---- candleOut \u2014 \u5439\u304D\u6D88\u3059: a breath travels along the line; each glyph leans, flickers, goes out and leaves a curl of smoke */
xb2_reg('candleOut', function (m) {
    var L = m.L, G = xb2_geo(m, true), F = xb2_frame(m), sz = xb2_sz(m), C = xb2_col(m), H = xb2_hd(m), dir = xb2_bit(m, 321) ? 1 : -1, i;
    var warm = xb2_mix(C.c0, C.acc, 0.5), UE = (dir > 0 ? '' : 'o=1-o;') + 'var u=(PO-o*0.42)/0.5;';
    var sel = H + xb2_sel(m) + UE + 'var ln=Math.sin(Math.PI*Math.min(1,Math.max(0,u)/0.45));seedRandom(ti*37+SD+Math.floor(time*12)*11,true);var fl=random();';
    jzAnimator(L, 'JZ Out Candle Lean', [['ADBE Text Skew', -dir * 22], ['ADBE Text Position 3D', [dir * sz * 0.06, 0, 0]], ['ADBE Text Scale 3D', [104, 104, 100]]], sel + 'u>0&&u<0.45?ln*100:0');
    jzAnimator(L, 'JZ Out Candle Warm', [['ADBE Text Fill Color', jzHex(warm)]], sel + 'u>0?100:0');
    jzAnimator(L, 'JZ Out Candle Fade', [['ADBE Text Opacity', 0]], sel + 'u<=0?0:(u>=0.45?100:(1-(u>0.3?(1-(u-0.3)/0.15)*(fl<0.5?1:0.6):0.65+0.35*fl))*100)');
    if (!xb2_main(m, G) || !xb2_fewGlyphs(G)) return;
    // a curl of smoke rises from every glyph once it is out
    var S = xb2_shape(m, 'JZ Out Candle Smoke'), lw = Math.max(1.2 * m.u, sz * 0.03);
    for (i = 0; i < G.N; i++) {
        var g = G.g[i]; if (g.sp) continue;
        var pts = [], k;
        for (k = 0; k <= 9; k++) { var f = k / 9, hgt = sz * 1.55 * f, w = Math.sin(f * 5 + 7 + i) * sz * 0.09 * f + dir * sz * 0.4 * f; pts.push([w, -hgt]); }
        var sg = jzGrp(S, 'smoke ' + (i + 1)), E = H + xb2_at(m, i, G.N) + UE + 'var ag=(u-0.36)/0.64;';
        jzAddPath(sg, pts, false);
        jzAddTrimPaths(sg, E + 'cl((0.25+1.3*ag)/1.55)*100', null);
        var st = jzAddStroke(sg, C.sub, lw); try { st.property('ADBE Vector Stroke Line Cap').setValue(2); st.property('ADBE Vector Stroke Line Join').setValue(2); } catch (e0) {}
        var gx = jzGX(sg);             // (fetched after the group's contents are complete)
        gx.property('ADBE Vector Position').setValue([g.cx - F.dn[0] * g.h * 0.4, g.cy - F.dn[1] * g.h * 0.4]);
        gx.property('ADBE Vector Rotation').setValue(-F.deg);
        jzSetExpr(gx.property('ADBE Vector Group Opacity'), E + 'u>=0.36&&u<1?90*(1-ag):0');
    }
});

// ================================================================ effect events part 1 (AE port of the fx entries in src/11p_looks.js)
// fx.build(f, ev): one plan event. f.comp = the MAIN comp (absolute time), ev = { t, dur, amp, type, seed, sc }.
// Make an adjustment layer (jzAdjLayer) or overlay layers (jzEvShape / jzEvSolid) covering [ev.t, ev.t + ev.dur] and drive them with
// expressions built on jzEvHead(ev.t, ev.dur) \u2014 it defines T0, TD and p (0..1 progress through the event, like the browser's k).

/* ---- strobe \u2014 \u30B9\u30C8\u30ED\u30DC: the picture flips to its negative on alternate quarters */
jzReg('fx', 'strobe', { build: function (f, ev) {
    var L = jzAdjLayer(f.comp, 'JZ FX strobe', ev.t, ev.dur);
    var iv = jzEffect(L, 'ADBE Invert', 'JZ Strobe');
    jzEX(iv, 2, jzEvHead(ev.t, ev.dur) + 'Math.floor(p*4)%2?100:0');     // blend with original: 0 = inverted
} });

// ================================================================ fx1 helpers
// exact port of the browser's J.h / J.r / J.rs / J.rr, so every variant choice (direction, corner, grid, strips \u2026) matches the web frame
function fx1_h(a, b, c, d, e) {
    var h = 0x9e3779b9 ^ (a | 0);
    h = jzImul(h ^ (h >>> 16), 0x85ebca6b);
    h = (h + jzImul((b | 0) + 0x632be5ab, 0xc2b2ae35)) | 0;
    h = jzImul(h ^ (h >>> 13), 0xc2b2ae35);
    h = (h + jzImul((c | 0) + 0x5bd1e995, 0x27d4eb2f)) | 0;
    h = jzImul(h ^ (h >>> 15), 0x165667b1);
    h = (h + jzImul((d | 0) + 0x1b873593, 0x85ebca6b)) | 0;
    h = jzImul(h ^ (h >>> 16), 0x27d4eb2f);
    h = (h + jzImul((e | 0) + 0x68e31da4, 0x9e3779b1)) | 0;
    h ^= h >>> 15; h = jzImul(h, 0x2c1b3c6d); h ^= h >>> 12; h = jzImul(h, 0x297a2d39); h ^= h >>> 15;
    return h >>> 0;
}
function fx1_r(a, b, c, d, e) { return fx1_h(a, b, c, d, e) / 4294967296; }
function fx1_rs(a, b, c, d, e) { return fx1_r(a, b, c, d, e) * 2 - 1; }
function fx1_rr(lo, hi, a, b, c, d, e) { return lo + (hi - lo) * fx1_r(a, b, c, d, e); }
function fx1_S(ev) { return fx1_h(Math.round(ev.t * 1000), 7331); }                 // evS(ev)
function fx1_amp(ev) { return jzClamp(ev.amp == null ? 1 : ev.amp, 0.3, 1.6); }     // ampOf(ev)
function fx1_dark(c) { return jzLum(c || '#000000') < 0.45; }                        // isDark
function fx1_best(list, against) {
    var b = list[0], bv = -1;
    for (var i = 0; i < list.length; i++) { if (!list[i]) continue; var v = jzContrast(list[i], against); if (v > bv) { bv = v; b = list[i]; } }
    return b;
}
function fx1_ink(sc) {                                                                 // inkOf(sc)
    var l = [sc.ink, sc.fg];
    for (var i = 0; i < l.length; i++) if (l[i] && jzContrast(l[i], sc.bg) >= 1.6) return l[i];
    return sc.fg;
}
// extra easings for the expressions (JZ_FNS has cl oe oc ic iq \u2026)
function fx1_E() { return 'function ioc(x){x=cl(x);return x<0.5?4*x*x*x:1-Math.pow(-2*x+2,3)/2;}function bell(x){return Math.sin(Math.PI*cl(x));}\n'; }
function fx1_head(ev, extra) { return jzEvHead(ev.t, ev.dur, extra) + fx1_E(); }

// the browser re-randomises glitches on a 24 fps clock (I.step = floor(t*24)): list the steps inside the event
function fx1_steps(ev) {
    var k0 = Math.floor(ev.t * 24 + 1e-6), k1 = Math.floor((ev.t + ev.dur) * 24 - 1e-6), o, j;
    if (k1 < k0) k1 = k0;
    o = { k0: k0, n: k1 - k0 + 1, ts: [] };
    for (j = 0; j < o.n; j++) o.ts.push(j ? (k0 + j) / 24 - 1e-4 : Math.min(ev.t, k0 / 24));
    return o;
}
// expression snippet: si = index of the current 24 fps step inside the event (off = layer-comp time offset to absolute time)
function fx1_si(S, off) { return 'var si=Math.max(0,Math.min(' + (S.n - 1) + ',Math.floor((time+' + jzN(off || 0) + ')*24+0.0001)-' + S.k0 + '));'; }
function fx1_arr(name, a) { var s = []; for (var i = 0; i < a.length; i++) s.push(jzN(a[i])); return 'var ' + name + '=[' + s.join(',') + '];'; }
// hold keyframes (one value per step)
function fx1_hold(prop, ts, vs) {
    if (!prop) return;
    try {
        if (vs.length === 1) { prop.setValue(vs[0]); return; }
        var i;
        for (i = 0; i < ts.length; i++) prop.setValueAtTime(ts[i], vs[i]);
        for (i = 1; i <= prop.numKeys; i++) prop.setInterpolationTypeAtKey(i, KeyframeInterpolationType.HOLD, KeyframeInterpolationType.HOLD);
    } catch (e) { jzWarn('fx1 keys: ' + e.toString()); }
}
function fx1_rectShape(x0, y0, x1, y1) { var sh = new Shape(); sh.vertices = [[x1, y0], [x1, y1], [x0, y1], [x0, y0]]; sh.closed = true; return sh; }
function fx1_sub(g, name) { var s = jzVecs(g).addProperty('ADBE Vector Group'); if (name) s.name = name; return s; }
function fx1_root(L) { return L.property('ADBE Root Vectors Group'); }
// shape group holding one rectangle whose box comes from an expression body defining x, y (top-left), w, h
function fx1_box(vecs, name, hex, op, hd, body) {
    var g = vecs.addProperty('ADBE Vector Group'); g.name = name;
    var r = jzAddRect(g, 10, 10);
    jzSetExpr(r.property('ADBE Vector Rect Size'), hd + body + '[Math.max(0,w),Math.max(0,h)]');
    jzSetExpr(r.property('ADBE Vector Rect Position'), hd + body + '[x+w/2,y+h/2]');
    jzAddFill(g, hex, op);
    return g;
}
// group mapping a "design space" (the move runs along +x over LL px, M across) onto the frame: mirrored when dir < 0, transposed when vert
function fx1_orient(L, LL, dir, vert) {
    var g = jzGrp(L, 'orient'), t = jzGX(g);
    if (vert) { t.property('ADBE Vector Rotation').setValue(90); t.property('ADBE Vector Scale').setValue([dir * 100, -100]); t.property('ADBE Vector Position').setValue([0, dir > 0 ? 0 : LL]); }
    else { t.property('ADBE Vector Scale').setValue([dir * 100, 100]); t.property('ADBE Vector Position').setValue([dir > 0 ? 0 : LL, 0]); }
    return g;
}
// adjustment layer applying a Transform inside one rectangle, re-keyed (hold) on every 24 fps step.
// P[j] per step = { x, y, w, h (mask), ax, ay (source point), px, py (where it lands), sw (scale width %) } or null (off at that step)
function fx1_strip(f, ev, name, S, P, op) {
    var j, ref = null;
    for (j = 0; j < P.length; j++) if (P[j]) { ref = P[j]; break; }
    if (!ref) return null;
    var L = jzAdjLayer(f.comp, name, ev.t, ev.dur);
    var m = L.property('ADBE Mask Parade').addProperty('ADBE Mask Atom');
    var tr = jzEffect(L, 'ADBE Geometry2', 'JZ Strip');
    jzEP(tr, 3, 0);                                               // uniform scale off
    var sh = [], an = [], po = [], sw = [], ops = [];
    for (j = 0; j < S.n; j++) {
        var q = P[j] || ref;
        sh.push(fx1_rectShape(q.x, q.y, q.x + q.w, q.y + q.h)); an.push([q.ax, q.ay]); po.push([q.px, q.py]);
        sw.push(Math.min(10000, q.sw || 100)); ops.push(P[j] ? op : 0);
    }
    fx1_hold(m.property('ADBE Mask Shape'), S.ts, sh);
    fx1_hold(tr.property(1), S.ts, an); fx1_hold(tr.property(2), S.ts, po); fx1_hold(tr.property(5), S.ts, sw);
    fx1_hold(jzXf(L, 'ADBE Opacity'), S.ts, ops);
    return L;
}
// the cut wrappers (precomp layers holding 'content') visible during [t0, t1]
function fx1_layer(comp, name) { for (var i = 1; i <= comp.numLayers; i++) if (comp.layer(i).name === name) return comp.layer(i); return null; }
function fx1_wraps(f, t0, t1) {
    var out = [], C = f.comp;
    // the builder keeps an index of the cut wrappers (f.wraps): no scan of the whole main comp per event, which made
    // long songs slower and slower (every event walked hundreds of layers, and the copies it added made it worse)
    if (f.wraps) {
        for (var w = 0; w < f.wraps.length; w++) {
            var W0 = f.wraps[w];
            if (W0.cut.start > t1 + 1 || W0.cut.end < t0 - 1 || !W0.content) continue;
            if (W0.layer.inPoint > t1 || W0.layer.outPoint <= t0) continue;
            out.push({ L: W0.layer, comp: W0.stage || W0.comp, content: W0.content });
        }
        return out;
    }
    for (var i = 1; i <= C.numLayers; i++) {
        var L = C.layer(i), src = null;
        try { src = L.source; } catch (e) { src = null; }
        if (!src || typeof src.numLayers !== 'number') continue;
        if (L.inPoint > t1 || L.outPoint <= t0) continue;
        var cl = fx1_layer(src, 'content');
        if (cl) out.push({ L: L, comp: src, content: cl });
    }
    return out;
}

// ================================================================ covering wipes (overlay graphics)

/* ---- panelWipe \u2014 \u30D1\u30CD\u30EB\u30EF\u30A4\u30D7: a slanted accent panel sweeps across (with a thin second-colour edge) and sweeps away */
function fx1_slant(o, hex, hd, sl, M) {
    var g = fx1_sub(o, 'panel');
    var r = jzAddRect(g, 10, M);
    jzSetExpr(r.property('ADBE Vector Rect Size'), hd + 'var w=u1-u0<=0.0005?0:(u1-u0)*(LL+SL);[w,M]');
    jzAddFill(g, hex);
    var tg = jzGX(g);
    tg.property('ADBE Vector Skew').setValue(Math.atan(sl / M) * 180 / Math.PI);     // top edge leads by SL
    jzSetExpr(tg.property('ADBE Vector Position'), hd + '[(u0+u1)/2*(LL+SL)-SL/2,M/2]');
}
jzReg('fx', 'panelWipe', { build: function (f, ev) {
    var W = f.W, H = f.H, sc = ev.sc, s = fx1_S(ev), dir = fx1_r(s, 1) < 0.5 ? 1 : -1, vert = fx1_r(s, 2) < 0.28;
    var LL = vert ? H : W, M = vert ? W : H, sl = M * 0.3;
    var L = jzEvShape(f.comp, 'JZ FX panelWipe', ev.t, ev.dur);
    var o = fx1_orient(L, LL, dir, vert);
    var hd = fx1_head(ev, ',LL=' + jzN(LL) + ',M=' + jzN(M) + ',SL=' + jzN(sl)) + 'var b=0.4,a0,a1;if(p<b){a0=0;a1=oc(p/b);}else{a0=ic((p-b)/(1-b));a1=1;}';
    fx1_slant(o, sc.accent, hd + 'var u0=a0,u1=a1;', sl, M);                                           // on top
    fx1_slant(o, fx1_best([sc.ink, sc.fg, sc.bg], sc.accent), hd + 'var u0,u1;if(p<b){u0=a1;u1=Math.min(1,a1+0.06);}else{u0=Math.max(0,a0-0.06);u1=a0;}', sl, M);
} });

/* ---- irisTrans \u2014 \u30A2\u30A4\u30EA\u30B9: a dark iris closes on a point near the centre (accent rim) and opens again */
jzReg('fx', 'irisTrans', { build: function (f, ev) {
    var W = f.W, H = f.H, sc = ev.sc, s = fx1_S(ev);
    var cx = W / 2 + fx1_rs(s, 1) * W * 0.08, cy = H / 2 + fx1_rs(s, 2) * H * 0.06;
    var dx = Math.max(cx, W - cx), dy = Math.max(cy, H - cy), R = Math.sqrt(dx * dx + dy * dy);
    var ink = sc.ink || sc.fg, col = fx1_dark(sc.bg) ? '#000000' : (fx1_dark(ink) ? ink : '#111111');
    var L = jzEvShape(f.comp, 'JZ FX irisTrans', ev.t, ev.dur);
    var hd = fx1_head(ev, ',R=' + jzN(R)) + 'var c=p<0.5?ic(p/0.5):1-oc((p-0.5)/0.5),r=R*(1-c);';
    var gr = jzGrp(L, 'rim'), el = jzAddEllipse(gr, 10, 10, cx, cy);
    jzSetExpr(el.property('ADBE Vector Ellipse Size'), hd + 'r>1&&c>0.02?[2*r,2*r]:[0,0]');
    jzAddStroke(gr, sc.accent, Math.max(2, H * 0.008));
    var gc = jzGrp(L, 'iris');
    jzAddRect(gc, W + 8, H + 8, 0, W / 2, H / 2);
    var hole = jzAddEllipse(gc, 10, 10, cx, cy);
    jzSetExpr(hole.property('ADBE Vector Ellipse Size'), hd + 'r>0.5?[2*r,2*r]:[0,0]');
    var fl = jzAddFill(gc, col); fl.property('ADBE Vector Fill Rule').setValue(2);            // even-odd: the circle is a hole
} });

/* ---- doors \u2014 \u6249: two ink panels close from the sides (accent edges) and open again */
jzReg('fx', 'doors', { build: function (f, ev) {
    var W = f.W, H = f.H, sc = ev.sc, s = fx1_S(ev), vert = fx1_r(s, 3) < 0.3;
    var LL = vert ? H : W, M = vert ? W : H, lw = Math.max(2, Math.min(W, H) * 0.008), ink = fx1_ink(sc);
    var L = jzEvShape(f.comp, 'JZ FX doors', ev.t, ev.dur);
    var hd = fx1_head(ev, ',LL=' + jzN(LL) + ',M=' + jzN(M) + ',LW=' + jzN(lw)) + 'var c=p<0.5?iq(p/0.5):1-oc((p-0.5)/0.5),half=LL/2*c+1;';
    jzSetExpr(jzXf(L, 'ADBE Opacity'), hd + 'c<=0.001?0:100');
    var V = jzVecs(fx1_orient(L, LL, 1, vert));
    fx1_box(V, 'line 1', sc.accent, 35, hd, 'var x=half-LW*4,y=0,w=LW*0.5,h=M;');
    fx1_box(V, 'line 2', sc.accent, 35, hd, 'var x=LL-half+LW*3.5,y=0,w=LW*0.5,h=M;');
    fx1_box(V, 'edge 1', sc.accent, 100, hd, 'var x=half-LW,y=0,w=LW,h=M;');
    fx1_box(V, 'edge 2', sc.accent, 100, hd, 'var x=LL-half,y=0,w=LW,h=M;');
    fx1_box(V, 'door 1', ink, 100, hd, 'var x=0,y=0,w=half,h=M;');
    fx1_box(V, 'door 2', ink, 100, hd, 'var x=LL-half,y=0,w=half,h=M;');
} });

/* ---- blindsTrans \u2014 \u30D6\u30E9\u30A4\u30F3\u30C9: slats close one after another (staggered), then open */
jzReg('fx', 'blindsTrans', { build: function (f, ev) {
    var W = f.W, H = f.H, sc = ev.sc, s = fx1_S(ev), n = 7 + (fx1_h(s, 4) % 6), vert = fx1_r(s, 5) < 0.35;
    var col = fx1_r(s, 6) < 0.5 ? sc.accent : fx1_ink(sc), LL = vert ? W : H, M = vert ? H : W, slat = LL / n;
    var L = jzEvShape(f.comp, 'JZ FX blindsTrans', ev.t, ev.dur);
    var hd = fx1_head(ev, ',N=' + n + ',SL=' + jzN(slat) + ',M=' + jzN(M)) +
        'var c=p<0.5?ic(p/0.5):1-oc((p-0.5)/0.5);function hs(i){var ci=cl(c*1.35-(i/N)*0.35),h=SL*ci+(ci>=1?1:0);return c<=0.001||h<=0.2?0:h;}';
    var g = jzGrp(L, 'slats');
    for (var i = 0; i < n; i++) {
        var r = jzAddRect(g, 10, 10);
        if (vert) {
            jzSetExpr(r.property('ADBE Vector Rect Size'), hd + '[hs(' + i + '),M]');
            jzSetExpr(r.property('ADBE Vector Rect Position'), hd + '[' + jzN(i * slat) + '+hs(' + i + ')/2,M/2]');
        } else {
            jzSetExpr(r.property('ADBE Vector Rect Size'), hd + '[M,hs(' + i + ')]');
            jzSetExpr(r.property('ADBE Vector Rect Position'), hd + '[M/2,' + jzN(i * slat) + '+hs(' + i + ')/2]');
        }
    }
    jzAddFill(g, col);
} });

// ================================================================ glitches on the picture (adjustment layers)

/* ---- rgbSplit \u2014 RGB\u5206\u96E2: red / cyan copies of the lyric knocked sideways (screen on dark, multiply on light).
   The browser tints |frame \u2212 bg| (= the ink); here: tinted duplicates of the cut's ghost / content layer inside its wrapper,
   so they follow the camera exactly. */
jzReg('fx', 'rgbSplit', { build: function (f, ev) {
    var W = f.W, H = f.H, a = fx1_amp(ev), s = fx1_S(ev), dk = fx1_dark(ev.sc.bg), S = fx1_steps(ev), j, i, k;
    var R5 = [], R6 = [];
    for (j = 0; j < S.n; j++) { var st = S.k0 + j + s; R5.push(fx1_r(st, 5)); R6.push(fx1_rs(st, 6)); }
    var ws = fx1_wraps(f, ev.t, ev.t + ev.dur);
    for (i = 0; i < ws.length; i++) {
        var w = ws[i], off = w.L.startTime, base = fx1_layer(w.comp, 'ghost B') || w.content;
        for (k = 0; k < 2; k++) {
            var D = base.duplicate();
            D.name = 'JZ FX rgbSplit ' + (k ? 'cyan' : 'red');
            D.moveToBeginning();
            D.startTime = 0;
            D.inPoint = Math.max(0, ev.t - off); D.outPoint = Math.min(w.comp.duration, Math.max(ev.t + ev.dur - off, ev.t - off + 1 / 24));
            var fe = jzEffect(D, 'ADBE Fill', 'JZ rgbSplit Colour');
            jzEP(fe, 3, jzHex(k ? '#1EE6FF' : '#FF2A2A')); jzEP(fe, 7, 1);
            D.blendingMode = dk ? BlendingMode.SCREEN : BlendingMode.MULTIPLY;
            jzXf(D, 'ADBE Opacity').setValue(90);
            jzSetExpr(jzXf(D, 'ADBE Position'), jzEvHead(ev.t - off, ev.dur, ',W=' + W + ',H=' + H + ',A=' + jzN(a) + ',SG=' + (k ? 1 : -1)) +
                fx1_arr('R5', R5) + fx1_arr('R6', R6) + fx1_si(S, off) +
                'var d=Math.max(2,W*(0.006+0.01*R5[si])*A*(1-0.6*p)),dy=R6[si]*H*0.004*A;[value[0]+SG*d,value[1]+SG*dy]');
        }
    }
} });

/* ---- smear \u2014 \u6A2A\u30B9\u30DF\u30A2: a few horizontal bands stretch one pixel column into long streaks (new bands every 1/24 s) */
jzReg('fx', 'smear', { build: function (f, ev) {
    var W = f.W, H = f.H, a = fx1_amp(ev), s = fx1_S(ev), S = fx1_steps(ev), sw = Math.max(1, Math.round(W * 0.003));
    var steps = [], nMax = 0, j, i;
    for (j = 0; j < S.n; j++) {
        var st = (S.k0 + j) * 13 + s, n = 7 + (fx1_h(st, 2) % 6), list = [];
        for (i = 0; i < n; i++) {
            var h = Math.max(2, Math.round(H * fx1_rr(0.008, 0.06, st, i, 2))), y = Math.round(jzClamp(H * (0.22 + 0.56 * fx1_r(st, i, 1)), 0, H - h));
            var sx = Math.round(W * fx1_rr(0.25, 0.75, st, i, 3)), len = W * fx1_rr(0.12, 0.45, st, i, 4) * a, dir = fx1_r(st, i, 5) < 0.5 ? 1 : -1;
            var x0 = dir > 0 ? sx : sx - len;
            list.push({ x: x0, y: y, w: len, h: h, ax: sx, ay: y + h / 2, px: x0, py: y + h / 2, sw: len / sw * 100 });
        }
        steps.push(list); if (n > nMax) nMax = n;
    }
    for (i = 0; i < nMax; i++) {
        var P = [];
        for (j = 0; j < S.n; j++) P.push(steps[j][i] || null);
        fx1_strip(f, ev, 'JZ FX smear ' + jzPad(i + 1, 2), S, P, 92);
    }
} });

/* ---- vhsRoll \u2014 VHS\u30ED\u30FC\u30EB: the picture rolls down and back (wrapping), a black tracking bar rides the seam */
jzReg('fx', 'vhsRoll', { build: function (f, ev) {
    var W = f.W, H = f.H, a = fx1_amp(ev), s = fx1_S(ev), S = fx1_steps(ev), j, i;
    var DX = [];
    for (j = 0; j < S.n; j++) DX.push(Math.round(fx1_rs((S.k0 + j) * 7 + s, 1) * W * 0.004 * a));
    var hd = fx1_head(ev, ',W=' + W + ',H=' + H + ',A=' + jzN(a)) + 'var oy=Math.round(H*0.2*A*bell(p));';
    // the roll itself: Offset wraps the frame vertically
    var L = jzAdjLayer(f.comp, 'JZ FX vhsRoll', ev.t, ev.dur);
    var of = jzEffect(L, 'ADBE Offset', 'JZ VHS Roll');
    jzEX(of, 1, hd + fx1_arr('DX', DX) + fx1_si(S) + 'oy<1?[W/2,H/2]:[W/2+DX[si],H/2+oy]');
    // torn lines just under the seam
    var sh = Math.max(2, Math.round(H * 0.008));
    for (i = 0; i < 5; i++) {
        var SX = [];
        for (j = 0; j < S.n; j++) SX.push(fx1_rs((S.k0 + j) * 7 + s, i, 2) * W * 0.03 * a);
        var T = jzAdjLayer(f.comp, 'JZ FX vhsRoll tear ' + (i + 1), ev.t, ev.dur);
        jzMaskRect(T, 0, i * sh * 1.6, W, i * sh * 1.6 + sh);
        jzSetExpr(jzXf(T, 'ADBE Position'), hd + '[value[0],value[1]+oy]');
        jzSetExpr(jzXf(T, 'ADBE Opacity'), hd + 'oy<1||oy+' + jzN(i * sh * 1.6 + sh) + '>H?0:100');
        var tr = jzEffect(T, 'ADBE Geometry2', 'JZ Tear'); jzEP(tr, 1, [W / 2, H / 2]); jzEP(tr, 2, [W / 2, H / 2]);
        jzEX(tr, 2, hd + fx1_arr('SX', SX) + fx1_si(S) + '[value[0]+SX[si],value[1]]');
    }
    // tracking bar + highlight above the seam
    var bh = Math.max(3, H * 0.028), hl = Math.max(1, H * 0.003);
    var B = jzEvShape(f.comp, 'JZ FX vhsRoll bar', ev.t, ev.dur);
    jzSetExpr(jzXf(B, 'ADBE Opacity'), hd + 'oy<1?0:100');
    fx1_box(fx1_root(B), 'highlight', '#FFFFFF', 75, hd, 'var x=0,y=oy-' + jzN(bh + hl) + ',w=W,h=' + jzN(hl) + ';');
    fx1_box(fx1_root(B), 'bar', '#000000', 78, hd, 'var x=0,y=oy-' + jzN(bh) + ',w=W,h=' + jzN(bh) + ';');
} });

/* ---- trackingNoise \u2014 \u30C8\u30E9\u30C3\u30AD\u30F3\u30B0\u30CE\u30A4\u30BA: two drifting bands of torn rows, streaky noise and hairlines */
jzReg('fx', 'trackingNoise', { build: function (f, ev) {
    var W = f.W, H = f.H, a = fx1_amp(ev), s = fx1_S(ev), S = fx1_steps(ev), dk = fx1_dark(ev.sc.bg), j, i, b;
    var bands = [
        { yc: 'H*' + jzN(0.25 + 0.5 * fx1_r(s, 1)) + '+(p-0.5)*H*0.12', bh: H * (0.05 + 0.06 * fx1_r(s, 2)) * a, rows: 6 },
        { yc: 'H*' + jzN(0.1 + 0.8 * fx1_r(s, 8)) + '-p*H*0.08', bh: H * 0.018 * a, rows: 2 }
    ];
    var Lh = jzEvShape(f.comp, 'JZ FX trackingNoise lines', ev.t, ev.dur);
    for (b = 0; b < bands.length; b++) {
        var B = bands[b], hh = Math.max(2, Math.round(B.bh));
        var hd = fx1_head(ev, ',W=' + W + ',H=' + H) + 'var y0=Math.max(0,Math.min(H-2,Math.round(' + B.yc + '-' + jzN(B.bh / 2) + ')));';
        // torn rows: each row slides sideways by its own amount, new amounts every step
        for (i = 0; i < B.rows; i++) {
            var ry = Math.floor(i * hh / B.rows), rh = Math.max(1, Math.floor(hh / B.rows)), DX = [];
            for (j = 0; j < S.n; j++) DX.push(fx1_rs((S.k0 + j) * 5 + s, i, 9) * W * 0.035 * a);
            var R = jzAdjLayer(f.comp, 'JZ FX trackingNoise row ' + (b + 1) + '.' + (i + 1), ev.t, ev.dur);
            jzMaskRect(R, 0, ry, W, ry + rh);
            jzSetExpr(jzXf(R, 'ADBE Position'), hd + '[value[0],value[1]+y0]');
            var tr = jzEffect(R, 'ADBE Geometry2', 'JZ Row'); jzEP(tr, 1, [W / 2, H / 2]); jzEP(tr, 2, [W / 2, H / 2]);
            jzEX(tr, 2, hd + fx1_arr('DX', DX) + fx1_si(S) + '[value[0]+DX[si],value[1]]');
        }
        // streaky noise over the band
        var N = f.comp.layers.addSolid([0.3, 0.3, 0.3], 'JZ FX trackingNoise noise ' + (b + 1), W, hh, 1, f.comp.duration);
        N.inPoint = Math.max(0, ev.t); N.outPoint = Math.min(f.comp.duration, ev.t + Math.max(ev.dur, 1 / 24));
        var nz = jzEffect(N, 'ADBE Noise', 'JZ Noise'); jzEP(nz, 1, 100); jzEP(nz, 2, 0); jzEP(nz, 3, 1);
        var db = jzEffect(N, 'ADBE Motion Blur', 'JZ Streak'); jzEP(db, 1, 90); jzEP(db, 2, W * 0.06);
        var bc = jzEffect(N, 'ADBE Brightness & Contrast 2', 'JZ Noise Contrast'); jzEP(bc, 1, -10); jzEP(bc, 2, 100);
        jzSetExpr(jzXf(N, 'ADBE Position'), hd + '[W/2,y0+' + jzN(hh / 2) + ']');
        N.blendingMode = dk ? BlendingMode.SCREEN : BlendingMode.MULTIPLY;
        jzXf(N, 'ADBE Opacity').setValue(75);
        // hairlines
        var g = jzGrp(Lh, 'band ' + (b + 1));
        for (i = 0; i < 7; i++) {
            var X = [], Y = [], WD = [];
            for (j = 0; j < S.n; j++) { var st = (S.k0 + j) * 5 + s; X.push(fx1_r(st, i, 5) * W); Y.push(fx1_r(st, i, 6)); WD.push(W * fx1_rr(0.05, 0.4, st, i, 7)); }
            var r = jzAddRect(g, 10, Math.max(1, H * 0.0025));
            var hx = hd + fx1_arr('X', X) + fx1_arr('Y', Y) + fx1_arr('WD', WD) + fx1_si(S);
            jzSetExpr(r.property('ADBE Vector Rect Size'), hx + '[WD[si],' + jzN(Math.max(1, H * 0.0025)) + ']');
            jzSetExpr(r.property('ADBE Vector Rect Position'), hx + '[X[si]+WD[si]/2,y0+Y[si]*' + hh + '+' + jzN(Math.max(1, H * 0.0025) / 2) + ']');
        }
        jzAddFill(g, dk ? '#FFFFFF' : '#000000', dk ? 80 : 70);
    }
    Lh.moveToBeginning();
} });

/* ---- mirrorFlash \u2014 \u30DF\u30E9\u30FC: for two frames one half (or quarter) of the picture is mirrored onto the rest */
jzReg('fx', 'mirrorFlash', { build: function (f, ev) {
    var W = f.W, H = f.H, m = fx1_h(fx1_S(ev), 9) % 4;
    var L = jzAdjLayer(f.comp, 'JZ FX mirrorFlash', ev.t, ev.dur);
    var mi = jzEffect(L, 'ADBE Mirror', 'JZ Mirror');
    jzEP(mi, 1, [W / 2, H / 2]); jzEP(mi, 2, m === 1 ? 180 : (m === 2 ? 90 : 0));      // 0: left \u2192 right, 180: right \u2192 left, 90: top \u2192 bottom
    if (m === 3) { var m2 = jzEffect(L, 'ADBE Mirror', 'JZ Mirror 2'); jzEP(m2, 1, [W / 2, H / 2]); jzEP(m2, 2, 90); }
} });

/* ---- posterize \u2014 \u30DD\u30B9\u30BF\u30EA\u30BC: a punch of contrast + saturation that fades out */
jzReg('fx', 'posterize', { build: function (f, ev) {
    var L = jzAdjLayer(f.comp, 'JZ FX posterize', ev.t, ev.dur);
    var hd = fx1_head(ev, ',A=' + jzN(fx1_amp(ev))) + 'var a=A*Math.pow(1-p,1.2);a=a<0.02?0:a;';
    var bc = jzEffect(L, 'ADBE Brightness & Contrast 2', 'JZ Posterize Contrast');
    jzEX(bc, 2, hd + 'Math.min(100,180*a)');
    var hs = jzEffect(L, 'ADBE Color Balance (HLS)', 'JZ Posterize Saturation');
    jzEX(hs, 3, hd + 'Math.min(100,260*a)');
} });

/* ---- hueShift \u2014 \u8272\u76F8\u30B7\u30D5\u30C8: the picture jumps round the colour wheel with a ghost-colour wash, fading back */
jzReg('fx', 'hueShift', { build: function (f, ev) {
    var sc = ev.sc, s = fx1_S(ev), deg = Math.round(90 + 180 * fx1_r(s, 3)), dk = fx1_dark(sc.bg);
    var hd = fx1_head(ev, ',AM=' + jzN(Math.min(1, fx1_amp(ev)))) + 'var a=Math.pow(1-p,0.8)*AM;';
    var L = jzAdjLayer(f.comp, 'JZ FX hueShift', ev.t, ev.dur);
    var hs = jzEffect(L, 'ADBE Color Balance (HLS)', 'JZ Hue Shift'); jzEP(hs, 1, deg); jzEP(hs, 3, 60);
    jzSetExpr(jzXf(L, 'ADBE Opacity'), hd + '100*a');
    var C = jzEvSolid(f.comp, 'JZ FX hueShift wash', (fx1_r(s, 4) < 0.5 ? sc.ghostA : sc.ghostB) || sc.accent, ev.t, ev.dur);
    C.blendingMode = dk ? BlendingMode.MULTIPLY : BlendingMode.SCREEN;
    jzSetExpr(jzXf(C, 'ADBE Opacity'), hd + '50*a');
} });

/* ---- tileShift \u2014 \u30BF\u30A4\u30EB\u305A\u3089\u3057: the frame cut into a grid; tiles jump off their places (some swap), background in the gaps */
jzReg('fx', 'tileShift', { build: function (f, ev) {
    var W = f.W, H = f.H, sc = ev.sc, a = fx1_amp(ev), s = fx1_S(ev), S = fx1_steps(ev), j, i, x, y, k;
    var steps = [], nMax = 0;
    for (j = 0; j < S.n; j++) {
        var s0 = (S.k0 + j) * 11 + s, gx = 3 + (fx1_h(s0, 1) % 4), gy = 2 + (fx1_h(s0, 2) % 3), list = [];
        for (y = 0; y < gy; y++) for (x = 0; x < gx; x++) {
            var x0 = Math.round(x * W / gx), x1 = Math.round((x + 1) * W / gx), y0 = Math.round(y * H / gy), y1 = Math.round((y + 1) * H / gy), tw = x1 - x0, th = y1 - y0;
            var moved = fx1_r(s0, x, y, 3) < 0.6, dx = moved ? fx1_rs(s0, x, y, 4) * tw * 0.2 * a : 0, dy = moved ? fx1_rs(s0, x, y, 5) * th * 0.15 * a : 0;
            var si = x, sj = y;
            if (fx1_r(s0, x, y, 6) < 0.12) { si = fx1_h(s0, x, y, 7) % gx; sj = fx1_h(s0, x, y, 8) % gy; }
            var sx0 = Math.round(si * W / gx), sy0 = Math.round(sj * H / gy);
            list.push({ x: x0 + dx, y: y0 + dy, w: tw, h: th, ax: sx0, ay: sy0, px: x0 + dx, py: y0 + dy, sw: 100, id: (sx0 !== x0 || sy0 !== y0 || dx !== 0 || dy !== 0) });
        }
        steps.push(list); if (list.length > nMax) nMax = list.length;
    }
    for (k = 0; k < nMax; k++) {
        var P = [], any = false;
        for (j = 0; j < S.n; j++) { var q = steps[j][k] || null; if (q && q.id) any = true; P.push(q && q.id ? q : null); }
        if (any) fx1_strip(f, ev, 'JZ FX tileShift ' + jzPad(k + 1, 2), S, P, 100);
    }
    // background where no tile lands: a bg solid with every tile cut out (re-keyed per step)
    var G = jzEvSolid(f.comp, 'JZ FX tileShift gaps', sc.bg, ev.t, ev.dur);
    for (k = 0; k < nMax; k++) {
        var mk = G.property('ADBE Mask Parade').addProperty('ADBE Mask Atom'), shs = [];
        mk.maskMode = MaskMode.SUBTRACT;
        for (j = 0; j < S.n; j++) { var t = steps[j][k]; shs.push(t ? fx1_rectShape(t.x, t.y, t.x + t.w, t.y + t.h) : fx1_rectShape(0, 0, 0, 0)); }
        fx1_hold(mk.property('ADBE Mask Shape'), S.ts, shs);
    }
} });

/* ---- filmBurn \u2014 \u30D5\u30A3\u30EB\u30E0\u713C\u3051: a warm light leak blooms from one edge and fades (flickering) */
jzReg('fx', 'filmBurn', { build: function (f, ev) {
    var W = f.W, H = f.H, sc = ev.sc, s = fx1_S(ev), S = fx1_steps(ev), j;
    var cn = fx1_h(s, 5) % 4, cx = cn & 1 ? W * 1.02 : -W * 0.02, cy = cn & 2 ? H * (0.7 + 0.3 * fx1_r(s, 6)) : H * 0.3 * fx1_r(s, 6);
    var FL = [];
    for (j = 0; j < S.n; j++) FL.push(0.85 + 0.15 * fx1_r(S.k0 + j, 3));
    var hd = fx1_head(ev, ',D=' + jzN(Math.sqrt(W * W + H * H)) + ',CX=' + jzN(cx) + ',CY=' + jzN(cy) + ',AM=' + jzN(jzClamp(fx1_amp(ev), 0.5, 1.2))) +
        fx1_arr('FL', FL) + fx1_si(S) + 'var b=2/7,a=(p<b?oc(p/b):1-ioc((p-b)/(1-b)))*AM*FL[si],R=D*(0.55+0.5*p);';
    var mk = function (name, c0, c1, reach, mode) {
        var L = jzEvSolid(f.comp, 'JZ FX filmBurn ' + name, '#000000', ev.t, ev.dur);
        var g = jzEffect(L, 'ADBE Ramp', 'JZ Burn');
        jzEP(g, 1, [cx, cy]); jzEP(g, 2, c0); jzEP(g, 4, c1); jzEP(g, 5, 2);
        jzEX(g, 3, hd + '[CX+R*' + jzN(reach) + ',CY]');
        L.blendingMode = mode;
        jzSetExpr(jzXf(L, 'ADBE Opacity'), hd + 'Math.min(100,100*a)');
        return L;
    };
    if (!fx1_dark(sc.bg)) mk('tint', [1, 0.76, 0.62], [1, 1, 1], 1, BlendingMode.MULTIPLY);          // light paper: the orange also darkens
    mk('glow', [0.95, 0.46, 0.16], [0, 0, 0], 1.15, BlendingMode.SCREEN);
    mk('core', [0.6, 0.56, 0.44], [0, 0, 0], 0.4, BlendingMode.SCREEN);
} });

/* ---- whipBlur \u2014 \u30DB\u30A4\u30C3\u30D7\u30D6\u30E9\u30FC: a whip-pan smear (directional blur) swells and settles */
jzReg('fx', 'whipBlur', { build: function (f, ev) {
    var vert = fx1_r(fx1_S(ev), 2) < 0.25;
    var hd = fx1_head(ev, ',W=' + f.W + ',A=' + jzN(fx1_amp(ev))) + 'var amt=p<0.4?ic(p/0.4):1-oc((p-0.4)/0.6),d=W*0.045*A*amt;';
    var L = jzAdjLayer(f.comp, 'JZ FX whipBlur', ev.t, ev.dur);
    var db = jzEffect(L, 'ADBE Motion Blur', 'JZ Whip'); jzEP(db, 1, vert ? 0 : 90);
    jzEX(db, 2, hd + 'd<1?0:d');
    var gb = jzEffect(L, 'ADBE Gaussian Blur 2', 'JZ Whip Soft');                // the browser draws the smear at half resolution
    jzEX(gb, 1, hd + 'd<1?0:' + jzN(3 * f.u) + '*amt');
} });

/* ---- blackFrame / whiteFrame \u2014 \u9ED2\u30B3\u30DE / \u767D\u30B3\u30DE: one or two frames of solid black / white */
function fx1_koma(col) {
    return { build: function (f, ev) {
        var one = fx1_r(fx1_S(ev), 9) < 0.5;
        jzEvSolid(f.comp, 'JZ FX ' + (col === '#000000' ? 'blackFrame' : 'whiteFrame'), col, ev.t, one ? ev.dur / 2 : ev.dur);
    } };
}
jzReg('fx', 'blackFrame', fx1_koma('#000000'));
jzReg('fx', 'whiteFrame', fx1_koma('#FFFFFF'));

/* ---- gridRepeat \u2014 \u753B\u9762\u5206\u5272: the whole frame repeated 2\u00D72 / 3\u00D73 with background gutters (switches grid at 60%) */
// built from copies: tile (0,0) = the frame scaled down, the rest of row 0 copies tile (0,0), the other rows copy row 0
// (every copy reads a region that is already final, so no precomp is needed)
function fx1_grid(f, ev, n, t0, dur, bg) {
    var W = f.W, H = f.H, tw = W / n, th = H / n, i, L, tr;
    L = jzAdjLayer(f.comp, 'JZ FX gridRepeat ' + n + 'x' + n + ' tile', t0, dur);
    jzMaskRect(L, 0, 0, tw, th);
    tr = jzEffect(L, 'ADBE Geometry2', 'JZ Grid Scale'); jzEP(tr, 1, [0, 0]); jzEP(tr, 2, [0, 0]); jzEP(tr, 4, 100 / n);
    for (i = 1; i < n; i++) {
        L = jzAdjLayer(f.comp, 'JZ FX gridRepeat ' + n + 'x' + n + ' col ' + i, t0, dur);
        jzMaskRect(L, i * tw, 0, (i + 1) * tw, th);
        tr = jzEffect(L, 'ADBE Geometry2', 'JZ Grid Copy'); jzEP(tr, 1, [0, 0]); jzEP(tr, 2, [i * tw, 0]);
    }
    for (i = 1; i < n; i++) {
        L = jzAdjLayer(f.comp, 'JZ FX gridRepeat ' + n + 'x' + n + ' row ' + i, t0, dur);
        jzMaskRect(L, 0, i * th, W, (i + 1) * th);
        tr = jzEffect(L, 'ADBE Geometry2', 'JZ Grid Copy'); jzEP(tr, 1, [0, 0]); jzEP(tr, 2, [0, i * th]);
    }
    var g = Math.max(2, Math.round(H * 0.006)), S = jzEvShape(f.comp, 'JZ FX gridRepeat ' + n + 'x' + n + ' gutters', t0, dur), G = jzGrp(S, 'gutters');
    for (i = 1; i < n; i++) { jzAddRect(G, g, H, 0, i * tw, H / 2); jzAddRect(G, W, g, 0, W / 2, i * th); }
    jzAddFill(G, bg);
}
jzReg('fx', 'gridRepeat', { build: function (f, ev) {
    var fl = fx1_r(fx1_S(ev), 1) < 0.5, n1 = fl ? 2 : 3, n2 = fl ? 3 : 2;
    fx1_grid(f, ev, n1, ev.t, ev.dur * 0.6, ev.sc.bg);
    fx1_grid(f, ev, n2, ev.t + ev.dur * 0.6, ev.dur * 0.4, ev.sc.bg);
} });

/* ---- waveWarp \u2014 \u6CE2\u3086\u304C\u307F: the rows sway sideways in a travelling sine wave that swells and settles */
jzReg('fx', 'waveWarp', { build: function (f, ev) {
    var W = f.W, H = f.H, s = fx1_S(ev), fr = 2 + fx1_r(s, 1) * 2, ph0 = fx1_r(s, 2) * 6;
    var hd = fx1_head(ev, ',W=' + W + ',A=' + jzN(fx1_amp(ev))) + 'var amp=W*0.034*A*bell(p);';
    var L = jzAdjLayer(f.comp, 'JZ FX waveWarp', ev.t, ev.dur);
    var ww = jzEffect(L, 'ADBE Wave Warp', 'JZ Wave');
    jzEP(ww, 1, 1); jzEP(ww, 3, H / fr); jzEP(ww, 4, 0); jzEP(ww, 5, 0); jzEP(ww, 6, 1);          // sine, wave length = H / cycles, travels up, still, no pinning
    jzEX(ww, 2, hd + 'amp<0.5?0:amp');
    jzEX(ww, 7, hd + '-(p*8+' + jzN(ph0) + ')*180/Math.PI');
} });

/* ---- pixelDrift \u2014 \u30D4\u30AF\u30BB\u30EB\u305A\u308C: many thin segments slide sideways or smear out (new set every 1/24 s) */
jzReg('fx', 'pixelDrift', { build: function (f, ev) {
    var W = f.W, H = f.H, a = fx1_amp(ev), s = fx1_S(ev), S = fx1_steps(ev), MAXL = 20;
    var steps = [], nMax = 0, j, i;
    for (j = 0; j < S.n; j++) {
        var s0 = (S.k0 + j) * 7 + s, m = 16 + (fx1_h(s0, 1) % 14), list = [];
        for (i = 0; i < m; i++) {
            var h = Math.max(1, Math.round(H * fx1_rr(0.003, 0.022, s0, i, 2))), y = Math.round(jzClamp(H * (0.2 + 0.6 * fx1_r(s0, i, 1)), 0, H - h));
            var len = Math.round(W * fx1_rr(0.05, 0.3, s0, i, 4)), x0 = Math.round(fx1_r(s0, i, 3) * (W - len)), dx = fx1_rs(s0, i, 5) * W * 0.07 * a;
            if (fx1_r(s0, i, 6) < 0.35) { var xs = dx > 0 ? x0 : x0 - len; list.push({ x: xs, y: y, w: len, h: h, ax: x0, ay: y, px: xs, py: y, sw: len * 100 }); }
            else list.push({ x: x0 + dx, y: y, w: len, h: h, ax: x0, ay: y, px: x0 + dx, py: y, sw: 100 });
        }
        steps.push(list); if (m > nMax) nMax = m;
    }
    for (i = 0; i < Math.min(nMax, MAXL); i++) {
        var P = [];
        for (j = 0; j < S.n; j++) P.push(steps[j][i] || null);
        fx1_strip(f, ev, 'JZ FX pixelDrift ' + jzPad(i + 1, 2), S, P, 100);
    }
} });

/* ---- zoomPunch \u2014 \u30BA\u30FC\u30E0\u30D1\u30F3\u30C1: a quick punch-in on the frame that eases back */
jzReg('fx', 'zoomPunch', { build: function (f, ev) {
    var zk = (0.06 + 0.04 * fx1_r(fx1_S(ev), 1)) * jzClamp(fx1_amp(ev), 0.5, 1.3);
    var L = jzAdjLayer(f.comp, 'JZ FX zoomPunch', ev.t, ev.dur);
    var tr = jzEffect(L, 'ADBE Geometry2', 'JZ Zoom Punch'); jzEP(tr, 1, [f.W / 2, f.H / 2]); jzEP(tr, 2, [f.W / 2, f.H / 2]);
    jzEX(tr, 4, fx1_head(ev, ',ZK=' + jzN(zk)) + 'var amt=p<0.25?oe(p/0.25):1-ioc((p-0.25)/0.75);100*(1+ZK*amt)');
} });

/* ---- lightSweep \u2014 \u5149\u306E\u7B4B: a soft slanted light band (plus a thin echo) sweeps across the frame */
jzReg('fx', 'lightSweep', { build: function (f, ev) {
    var W = f.W, H = f.H, sc = ev.sc, s = fx1_S(ev), dk = fx1_dark(sc.bg);
    var ang = (20 + 20 * fx1_r(s, 1)) * (fx1_r(s, 2) < 0.5 ? 1 : -1), dir = fx1_r(s, 3) < 0.5 ? 1 : -1;
    var bw = W * 0.11, HH = Math.sqrt(W * W + H * H), col = dk ? sc.fg : jzMixHex(sc.accent, '#FFFFFF', 0.35);
    var hd = fx1_head(ev, ',W=' + W + ',H=' + H + ',AM=' + jzN(Math.min(1, fx1_amp(ev)))) +
        'var P=ioc(p),xc=' + (dir > 0 ? 'W*(-0.3+1.6*P)' : 'W*(1.3-1.6*P)') + ',a=AM*Math.sqrt(bell(p));';
    var strips = [[0, bw, 60], [-bw * 0.95 * dir, bw * 0.25, 45]];
    for (var i = 0; i < strips.length; i++) {
        var L = jzEvShape(f.comp, 'JZ FX lightSweep ' + (i ? 'echo' : 'band'), ev.t, ev.dur);
        var g = jzGrp(L, 'light');
        jzAddRect(g, strips[i][1] * 0.5, HH * 2, 0, strips[i][0], 0);
        jzAddFill(g, col);
        var gb = jzEffect(L, 'ADBE Gaussian Blur 2', 'JZ Soft'); jzEP(gb, 1, strips[i][1] * 0.45);
        jzSetExpr(jzXf(L, 'ADBE Position'), hd + '[xc,H/2]');
        jzXf(L, 'ADBE Rotate Z').setValue(ang);
        jzSetExpr(jzXf(L, 'ADBE Opacity'), hd + strips[i][2] + '*a');
        L.blendingMode = dk ? BlendingMode.SCREEN : BlendingMode.MULTIPLY;
    }
} });

/* ---- crtOff \u2014 \u30D6\u30E9\u30A6\u30F3\u7BA1\u30AA\u30D5: the picture collapses to a bright line / dot on black (TV switched off) and pops back on */
jzReg('fx', 'crtOff', { build: function (f, ev) {
    var W = f.W, H = f.H;
    var hd = fx1_head(ev, ',W=' + W + ',H=' + H) +
        'var sx,sy,br,q;if(p<0.5){q=p/0.5;sy=Math.max(0.004,1-ic(Math.min(1,q*1.15)));sx=q>0.8?Math.max(0.01,1-ic((q-0.8)/0.2)*0.99):1;br=iq(q);}' +
        'else{q=(p-0.5)/0.5;sy=Math.max(0.004,oe(cl(q*1.4-0.15)));sx=q<0.15?Math.max(0.01,oc(q/0.15)):1;br=1-oc(q);}var w=W*sx,h=Math.max(1,H*sy),gh=H*0.05*br+1;';
    var L = jzAdjLayer(f.comp, 'JZ FX crtOff', ev.t, ev.dur);
    var tr = jzEffect(L, 'ADBE Geometry2', 'JZ CRT Squash');
    jzEP(tr, 1, [W / 2, H / 2]); jzEP(tr, 2, [W / 2, H / 2]); jzEP(tr, 3, 0);
    jzEX(tr, 4, hd + 'sy*100'); jzEX(tr, 5, hd + 'sx*100');
    // black around the squashed picture (even-odd: outer frame minus the picture's box)
    var K = jzEvShape(f.comp, 'JZ FX crtOff black', ev.t, ev.dur), gk = jzGrp(K, 'black');
    jzAddRect(gk, W + 8, H + 8, 0, W / 2, H / 2);
    var hole = jzAddRect(gk, W, H, 0, W / 2, H / 2);
    jzSetExpr(hole.property('ADBE Vector Rect Size'), hd + '[w,h]');
    jzAddFill(gk, '#000000').property('ADBE Vector Fill Rule').setValue(2);
    // the picture brightens as it collapses
    var Wb = jzEvShape(f.comp, 'JZ FX crtOff white', ev.t, ev.dur), gw = jzGrp(Wb, 'white');
    var rw = jzAddRect(gw, W, H, 0, W / 2, H / 2);
    jzSetExpr(rw.property('ADBE Vector Rect Size'), hd + '[w,h]');
    jzAddFill(gw, '#FFFFFF');
    Wb.blendingMode = BlendingMode.SCREEN;
    jzSetExpr(jzXf(Wb, 'ADBE Opacity'), hd + '85*br');
    // soft glow line across the centre
    var Gl = jzEvShape(f.comp, 'JZ FX crtOff glow', ev.t, ev.dur), gg = jzGrp(Gl, 'glow');
    var rg = jzAddRect(gg, W, 10, 0, W / 2, H / 2);
    jzSetExpr(rg.property('ADBE Vector Rect Size'), hd + '[w*1.1,gh]');
    jzAddFill(gg, '#FFFFFF');
    var gb = jzEffect(Gl, 'ADBE Gaussian Blur 2', 'JZ Glow Soft'); jzEP(gb, 2, 3);
    jzEX(gb, 1, hd + 'gh*0.8');
    Gl.blendingMode = BlendingMode.SCREEN;
    jzSetExpr(jzXf(Gl, 'ADBE Opacity'), hd + '60*br');
} });

/* ---- splitSlide \u2014 \u4E0A\u4E0B\u30B9\u30E9\u30A4\u30C9: the frame splits in two; the halves slide apart (accent seam), background in the gaps */
jzReg('fx', 'splitSlide', { build: function (f, ev) {
    var W = f.W, H = f.H, sc = ev.sc, s = fx1_S(ev), vert = fx1_r(s, 1) < 0.3, sp = 0.5 + fx1_rs(s, 2) * 0.08;
    var lw = Math.max(2, Math.round(Math.min(W, H) * 0.004)), cut = vert ? Math.round(W * sp) : Math.round(H * sp);
    var hd = fx1_head(ev, ',W=' + W + ',H=' + H + ',C=' + cut + ',A=' + jzN(fx1_amp(ev))) +
        'var amt=p<1/3?oc(p*3):1-ioc((p-1/3)/(2/3));amt=amt<=0.002?0:amt;var d=' + (vert ? 'H*0.1' : 'W*0.09') + '*A*amt;';
    for (var k = 0; k < 2; k++) {
        var L = jzAdjLayer(f.comp, 'JZ FX splitSlide ' + (k ? 'B' : 'A'), ev.t, ev.dur), sg = k ? '+' : '-';
        if (vert) jzMaskRect(L, k ? cut : 0, 0, k ? W : cut, H); else jzMaskRect(L, 0, k ? cut : 0, W, k ? H : cut);
        var tr = jzEffect(L, 'ADBE Geometry2', 'JZ Split Slide'); jzEP(tr, 1, [W / 2, H / 2]); jzEP(tr, 2, [W / 2, H / 2]);
        jzEX(tr, 2, hd + (vert ? '[value[0],value[1]' + sg + 'd]' : '[value[0]' + sg + 'd,value[1]]'));
    }
    var G = jzEvShape(f.comp, 'JZ FX splitSlide seam', ev.t, ev.dur), V = fx1_root(G);
    var seam = fx1_box(V, 'seam', sc.accent, 100, hd, vert ? 'var x=C-' + jzN(lw / 2) + ',y=0,w=' + lw + ',h=amt>0?H:0;' : 'var x=0,y=C-' + jzN(lw / 2) + ',w=amt>0?W:0,h=' + lw + ';');
    jzSetExpr(jzVecs(seam).property(2).property('ADBE Vector Fill Opacity'), hd + '100*amt');
    if (vert) {
        fx1_box(V, 'gap A', sc.bg, 100, hd, 'var x=0,y=H-d,w=C,h=d;');
        fx1_box(V, 'gap B', sc.bg, 100, hd, 'var x=C,y=0,w=W-C,h=d;');
    } else {
        fx1_box(V, 'gap A', sc.bg, 100, hd, 'var x=W-d,y=0,w=d,h=C;');
        fx1_box(V, 'gap B', sc.bg, 100, hd, 'var x=0,y=C,w=d,h=H-C;');
    }
} });

// ================================================================ effect events part 2 (AE port of the fx entries in src/11p_fxB.js)
// fx.build(f, ev): f.comp = the MAIN comp (absolute time), ev = { t, dur, amp, type, seed, sc }.
// Adjustment layers (jzAdjLayer) / overlays (jzEvShape, jzEvSolid) over [ev.t, ev.t + ev.dur], driven by jzEvHead \u2192 p (= browser k).
// Effects that need extra copies of the picture (the browser's `S` snapshot drawn again: radialChroma, echoFrames, perspectiveTilt)
// duplicate the cut wrappers visible during the event (fx2_copies). Glitch entries that re-roll their randomness on the browser's
// 24 fps clock (I.step) build one layer per clock step (fx2_steps) with the browser's own hash, so the patterns match.

// ---------------------------------------------------------------- helpers
// J.h of the browser (same uint32 for the same keys) so AE picks the same variants / positions as the web preview
function fx2_h(a, b, c, d, e) {
    var h = 0x9e3779b9 ^ (a | 0);
    h = jzImul(h ^ (h >>> 16), 0x85ebca6b);
    h = (h + jzImul((b | 0) + 0x632be5ab, 0xc2b2ae35)) | 0;
    h = jzImul(h ^ (h >>> 13), 0xc2b2ae35);
    h = (h + jzImul((c | 0) + 0x5bd1e995, 0x27d4eb2f)) | 0;
    h = jzImul(h ^ (h >>> 15), 0x165667b1);
    h = (h + jzImul((d | 0) + 0x1b873593, 0x85ebca6b)) | 0;
    h = jzImul(h ^ (h >>> 16), 0x27d4eb2f);
    h = (h + jzImul((e | 0) + 0x68e31da4, 0x9e3779b1)) | 0;
    h ^= h >>> 15; h = jzImul(h, 0x2c1b3c6d); h ^= h >>> 12; h = jzImul(h, 0x297a2d39); h ^= h >>> 15;
    return h >>> 0;
}
function fx2_r(a, b, c, d, e) { return fx2_h(a, b, c, d, e) / 4294967296; }
function fx2_rs(a, b, c, d, e) { return fx2_r(a, b, c, d, e) * 2 - 1; }
function fx2_rr(lo, hi, a, b, c, d, e) { return lo + (hi - lo) * fx2_r(a, b, c, d, e); }
function fx2_noise(x, seed) { var i = Math.floor(x), f = x - i, u = f * f * (3 - 2 * f); return jzLerp(fx2_rs(seed, i), fx2_rs(seed, i + 1), u); }
function fx2_s(ev) { return fx2_h(Math.round(ev.t * 1000), 9127); }                 // evS(ev)
function fx2_amp(ev, lo, hi) { var a = jzClamp(ev.amp == null ? 1 : ev.amp, 0.3, 1.6); return jzClamp(a, lo == null ? 0.3 : lo, hi == null ? 1.6 : hi); }
function fx2_hyp(x, y) { return Math.sqrt(x * x + y * y); }
function fx2_t1(ev) { return ev.t + Math.max(ev.dur, 1 / 24); }
function fx2_dk(sc) { return jzLum(sc.bg) < 0.45; }
function fx2_ext(list, dark) { var b = null, i; for (i = 0; i < list.length; i++) { if (!list[i]) continue; if (b === null || (dark ? jzLum(list[i]) < jzLum(b) : jzLum(list[i]) > jzLum(b))) b = list[i]; } return b || (dark ? '#111111' : '#FFFFFF'); }
function fx2_ink(sc) { return fx2_dk(sc) ? fx2_ext([sc.fg, sc.ink, '#FFFFFF'], false) : fx2_ext([sc.fg, sc.ink, '#111111'], true); }
function fx2_hueD(a, b) { var d = Math.abs(a - b) % 360; return d > 180 ? 360 - d : d; }
// the most colourful scheme colour (cool blue for monochrome schemes)
function fx2_vivid(sc, fb) {
    var best = null, bv = 0.18, L = [sc.accent, sc.accent2, sc.ghostA, sc.ghostB, sc.fg], i, hs, v;
    for (i = 0; i < L.length; i++) { if (!L[i]) continue; hs = jzToHsl(L[i]); v = hs[1] * (1 - Math.abs(hs[2] - 0.55) * 1.1); if (v > bv) { bv = v; best = L[i]; } }
    return best || fb || '#4FB8FF';
}
// two hues for a duotone
function fx2_huePair(sc) {
    var src = [sc.accent, sc.accent2, sc.ghostA, sc.ghostB, sc.fg, sc.bg], cs = [], i, j, x, hs;
    for (i = 0; i < src.length; i++) if (src[i]) { hs = jzToHsl(src[i]); cs.push({ h: hs[0], v: hs[1] * (1 - Math.abs(hs[2] - 0.5) * 1.2) }); }
    for (i = 1; i < cs.length; i++) { x = cs[i]; j = i - 1; while (j >= 0 && cs[j].v < x.v) { cs[j + 1] = cs[j]; j--; } cs[j + 1] = x; }
    if (!cs.length || cs[0].v < 0.15) return [330, 195];
    for (i = 0; i < cs.length; i++) if (cs[i].v > 0.15 && fx2_hueD(cs[i].h, cs[0].h) > 50) return [cs[0].h, cs[i].h];
    return [cs[0].h, cs[0].h + 170];
}
// expression helpers on top of jzEvHead: bell, in-out cubic, out quad, attack-hold-release, ST = the browser's 24 fps step
var fx2_FNS = 'function bell(x){return Math.sin(Math.PI*cl(x));}' +
    'function ioc(x){x=cl(x);return x<0.5?4*x*x*x:1-Math.pow(-2*x+2,3)/2;}' +
    'function oq(x){x=cl(x);return 1-(1-x)*(1-x);}' +
    'function ahr(k,a,b){return k<a?oc(k/a):(k>b?1-ic((k-b)/Math.max(0.001,1-b)):1);}' +
    'var ST=Math.floor(time*24+0.000001);\n';
function fx2_head(ev, extra) { return jzEvHead(ev.t, ev.dur, extra) + fx2_FNS; }
function fx2_span(L, t0, t1) { try { L.inPoint = t0; L.outPoint = Math.max(t1, t0 + 0.001); } catch (e) { jzWarn('fx2 span: ' + e.toString()); } return L; }
// one entry per 24 fps clock step inside the event (the browser re-rolls glitch randomness on I.step = floor(t*24))
function fx2_steps(ev, cap) {
    var t0 = ev.t, t1 = fx2_t1(ev), s = Math.floor(t0 * 24 + 0.000001), out = [];
    for (; s / 24 < t1 - 0.0001 && out.length < (cap || 12); s++) out.push({ st: s, t0: Math.max(t0, s / 24), t1: Math.min(t1, (s + 1) / 24) });
    if (out.length) out[out.length - 1].t1 = t1;
    return out;
}
// masks (layer space = comp space for comp-sized solids / adjustment layers / wrappers)
function fx2_maskPts(L, verts, ins, outs, o) {
    o = o || {};
    var m = L.property('ADBE Mask Parade').addProperty('ADBE Mask Atom'), sh = new Shape();
    sh.vertices = verts; if (ins) { sh.inTangents = ins; sh.outTangents = outs; } sh.closed = true;
    m.property('ADBE Mask Shape').setValue(sh);
    if (o.mode) m.maskMode = o.mode;
    if (o.feather) m.property('ADBE Mask Feather').setValue([o.feather, o.feather]);
    if (o.op != null) m.property('ADBE Mask Opacity').setValue(o.op);
    if (o.inv) m.inverted = true;
    return m;
}
function fx2_ellPts(cx, cy, rx, ry) {
    var kx = 0.5523 * rx, ky = 0.5523 * ry;
    return { v: [[cx, cy - ry], [cx + rx, cy], [cx, cy + ry], [cx - rx, cy]], i: [[-kx, 0], [0, -ky], [kx, 0], [0, ky]], o: [[kx, 0], [0, ky], [-kx, 0], [0, -ky]] };
}
function fx2_maskEll(L, cx, cy, rx, ry, o) { var e = fx2_ellPts(cx, cy, rx, ry); return fx2_maskPts(L, e.v, e.i, e.o, o); }
function fx2_maskRect(L, x0, y0, x1, y1, o) { return fx2_maskPts(L, [[x1, y0], [x1, y1], [x0, y1], [x0, y0]], null, null, o); }
// shape helpers
function fx2_path(g, verts, ins, outs, closed) {
    var p = jzVecs(g).addProperty('ADBE Vector Shape - Group'), sh = new Shape();
    sh.vertices = verts; if (ins) { sh.inTangents = ins; sh.outTangents = outs; } sh.closed = !!closed;
    p.property('ADBE Vector Shape').setValue(sh);
    return p;
}
function fx2_eo(fill) { try { fill.property('ADBE Vector Fill Rule').setValue(2); } catch (e) {} return fill; }
function fx2_gx(g, mn, ex) { jzSetExpr(jzGX(g).property(mn), ex); }
function fx2_sub(g, name) { var q = jzVecs(g).addProperty('ADBE Vector Group'); if (name) q.name = name; return q; }
function fx2_solid(f, name, hex, w, h, t0, t1) {
    var L = f.comp.layers.addSolid(jzHex(hex), name, Math.max(4, Math.round(w)), Math.max(4, Math.round(h)), 1, f.comp.duration);
    return fx2_span(L, t0, t1);
}
// Transform effect (ADBE Geometry2) with anchor = position = pivot
function fx2_xform(L, name, pivot) {
    var tr = jzEffect(L, 'ADBE Geometry2', name);
    if (pivot) { jzEP(tr, 1, pivot); jzEP(tr, 2, pivot); }
    return tr;
}
function fx2_isWrap(L) {
    try { return !!(L && L.source && typeof L.source.numLayers === 'number' && /^\d\d\d /.test(String(L.name))); } catch (e) { return false; }
}
// copies of the picture: duplicates of the cut wrappers visible during the event, moved to the top of the main comp and trimmed to it.
// A matted wrapper takes a copy of its matte along (kept directly above the copy). fn(layer, isMatte) is applied to each.
function fx2_copies(f, ev, tag, fn) {
    var C = f.comp, t0 = ev.t, t1 = fx2_t1(ev), src = [], out = [], i, L, D, M, M0;
    if (f.wraps) {                      // indexed by the builder (see fx1_wraps)
        for (i = f.wraps.length - 1; i >= 0; i--) { var W0 = f.wraps[i]; if (W0.cut.start > t1 + 1 || W0.cut.end < t0 - 1) continue; L = W0.layer; if (L.inPoint < t1 - 0.0001 && L.outPoint > t0 + 0.0001) src.push(L); }
    } else for (i = C.numLayers; i >= 1; i--) { L = C.layer(i); if (fx2_isWrap(L) && L.inPoint < t1 - 0.0001 && L.outPoint > t0 + 0.0001) src.push(L); }
    for (i = 0; i < src.length; i++) {
        L = src[i]; M0 = null;
        try { if (L.trackMatteType !== TrackMatteType.NO_TRACK_MATTE && L.index > 1) M0 = C.layer(L.index - 1); } catch (e0) {}
        D = L.duplicate(); D.moveToBeginning(); D.name = 'JZ FX ' + tag + ' copy';
        fx2_span(D, Math.max(D.inPoint, t0), Math.min(D.outPoint, t1));
        M = null;
        if (M0) { M = M0.duplicate(); M.moveToBeginning(); M.name = 'JZ FX ' + tag + ' matte'; fx2_span(M, Math.max(M.inPoint, t0), Math.min(M.outPoint, t1)); }
        else { try { if (D.trackMatteType !== TrackMatteType.NO_TRACK_MATTE) D.trackMatteType = TrackMatteType.NO_TRACK_MATTE; } catch (e1) {} }
        if (fn) { fn(D, false); if (M) fn(M, true); }
        out.push({ L: D, M: M });
    }
    return out;
}
// a mask on a picture copy that may already carry transition masks (then it must intersect them)
function fx2_clipMask(L, verts) {
    var n = 0; try { n = L.property('ADBE Mask Parade').numProperties; } catch (e) { n = 0; }
    return fx2_maskPts(L, verts, null, null, n > 0 ? { mode: MaskMode.INTERSECT } : null);
}
function fx2_cropMask(L, x0, y0, x1, y1) { return fx2_clipMask(L, [[x1, y0], [x1, y1], [x0, y1], [x0, y0]]); }

// ================================================================ lens / optics
/* ---- radialChroma \u2014 \u653E\u5C04\u8272\u53CE\u5DEE: red / cyan tinted copies of the picture scaled out from the centre */
jzReg('fx', 'radialChroma', { build: function (f, ev) {
    var s = fx2_s(ev), dk = fx2_dk(ev.sc);
    var hd = fx2_head(ev, ',D0=' + jzN((0.03 + 0.016 * fx2_r(s, 1)) * fx2_amp(ev))) + 'var d=D0*(0.4+0.6*oq(p))*(p>0.8?1-(p-0.8)*2.5:1);\n';
    var parts = [['#FF3020', 1, 95], ['#18E0FF', -0.7, 95], ['#FF3020', 2.2, 45]], j;
    for (j = 0; j < parts.length; j++) {
        fx2_copies(f, ev, 'radialChroma', function (L, isM) {
            if (!isM) {
                var tn = jzEffect(L, 'ADBE Tint', 'JZ Chroma Tint');
                jzEP(tn, 1, jzHex(dk ? '#000000' : parts[j][0])); jzEP(tn, 2, jzHex(dk ? parts[j][0] : '#FFFFFF')); jzEP(tn, 3, 100);
                L.blendingMode = dk ? BlendingMode.SCREEN : BlendingMode.MULTIPLY;
            }
            var tr = fx2_xform(L, 'JZ Chroma Scale', [f.W / 2, f.H / 2]);
            jzEX(tr, 4, hd + '100*(1+' + jzN(parts[j][1]) + '*d)');
            if (!isM) jzEP(tr, 9, parts[j][2]);
        });
    }
} });

/* ---- bloomFlash \u2014 \u30D6\u30EB\u30FC\u30E0: a soft glow blooms out of the frame and fades */
jzReg('fx', 'bloomFlash', { build: function (f, ev) {
    var dk = fx2_dk(ev.sc), A = fx2_amp(ev, 0.5, 1.3);
    var hd = fx2_head(ev, ',AC=' + jzN(A)) + 'var a=AC*(p<0.12?oc(p/0.12):Math.pow(Math.max(0,1-(p-0.12)/0.88),1.5));\n';
    var L = jzAdjLayer(f.comp, 'JZ FX bloomFlash', ev.t, ev.dur);
    var g1 = jzEffect(L, 'ADBE Glo2', 'JZ Bloom Near');
    jzEP(g1, 2, dk ? 30 : 8); jzEP(g1, 3, 18 * f.u); jzEX(g1, 4, hd + 'a*' + (dk ? '1.1' : '0.6'));
    var g2 = jzEffect(L, 'ADBE Glo2', 'JZ Bloom Far');
    jzEP(g2, 2, dk ? 30 : 8); jzEP(g2, 3, 90 * f.u); jzEX(g2, 4, hd + 'a*' + (dk ? '1.2' : '0.5'));
    if (dk) {   // + a faint fg-coloured lift
        var F = jzEvSolid(f.comp, 'JZ FX bloomFlash lift', ev.sc.fg, ev.t, ev.dur);
        F.blendingMode = BlendingMode.SCREEN;
        jzSetExpr(jzXf(F, 'ADBE Opacity'), hd + '10*a');
    }
} });

/* ---- bulge \u2014 \u9B5A\u773C: fisheye bulge (or pinch) that swells in and relaxes */
jzReg('fx', 'bulge', { build: function (f, ev) {
    var s = fx2_s(ev), pinch = fx2_r(s, 1) < 0.25, W = f.W, H = f.H;
    var base = (pinch ? -0.26 : 0.27) * fx2_amp(ev, 0.5, 1.35);
    var L = jzAdjLayer(f.comp, 'JZ FX bulge', ev.t, ev.dur);
    var bg = jzEffect(L, 'ADBE Bulge', 'JZ Bulge');
    jzEP(bg, 1, W * 0.56); jzEP(bg, 2, H * 0.56); jzEP(bg, 3, [W * (0.5 + fx2_rs(s, 2) * 0.05), H * (0.5 + fx2_rs(s, 3) * 0.04)]);
    jzEX(bg, 4, fx2_head(ev, ',B0=' + jzN(base)) + 'var amt=p<0.3?oc(p/0.3):1-ioc((p-0.3)/0.7);var A=Math.max(-0.36,Math.min(0.38,B0*amt));A/0.27*0.55');
} });

// ================================================================ glitch
/* ---- pixelSort \u2014 \u30D4\u30AF\u30BB\u30EB\u30BD\u30FC\u30C8: runs of columns melt downwards (or upwards) \u2014 masked vertical stretches */
jzReg('fx', 'pixelSort', { build: function (f, ev) {
    var W = f.W, H = f.H, a = fx2_amp(ev), s = fx2_s(ev), st = Math.floor(ev.t * 24 + 0.000001) * 19 + s, up = fx2_r(s, 1) < 0.3;
    var xa = W * fx2_rr(0.02, 0.35, st, 1), xb = Math.min(W, xa + W * fx2_rr(0.4, 0.7, st, 2));
    var x = Math.round(xa), i = 0, cols = [], wr, on;
    while (x < xb && i < 200) {
        wr = Math.max(1, Math.round(W * fx2_rr(0.002, 0.009, st, i, 3)));
        on = fx2_noise(x / W * 9, st + 5) > -0.25 && fx2_r(st, i, 4) < 0.85;
        if (on) cols.push([x, Math.min(xb, x + wr)]);
        x += wr; i++;
    }
    // the columns' window / stretch vary smoothly along x: 5 bands, one adjustment layer each (its columns as masks)
    var K = 5, bw = (xb - xa) / K, k, j;
    for (k = 0; k < K; k++) {
        var b0 = xa + k * bw, b1 = b0 + bw, runs = [], cur = null, cm;
        for (j = 0; j < cols.length; j++) {
            cm = (cols[j][0] + cols[j][1]) / 2;
            if (cm < b0 || cm >= b1) continue;
            if (cur && Math.abs(cur[1] - cols[j][0]) < 0.5) cur[1] = cols[j][1]; else { cur = [cols[j][0], cols[j][1]]; runs.push(cur); }
        }
        if (!runs.length) continue;
        var xc = (b0 + b1) / 2, n1 = fx2_noise(xc / W * 7, st + 9) * 0.5 + 0.5, n2 = fx2_noise(xc / W * 23, st + 13) * 0.5 + 0.5;
        var win = H * (0.08 + 0.12 * n1), str = 1 + (0.5 + 2 * n2) * a, y0, ya, yb;
        if (!up) { y0 = H * (0.36 + 0.12 * n2); ya = y0; yb = y0 + Math.min(H - y0, win * str); }
        else { y0 = H * (0.64 - 0.12 * n2); ya = y0 - Math.min(y0, win * str); yb = y0; }
        var L = jzAdjLayer(f.comp, 'JZ FX pixelSort', ev.t, ev.dur);
        for (j = 0; j < runs.length; j++) fx2_maskRect(L, runs[j][0], ya, runs[j][1], yb);
        var tr = fx2_xform(L, 'JZ Pixel Sort', [xc, y0]);
        jzEP(tr, 3, 0); jzEP(tr, 5, 100);
        jzEX(tr, 4, 'posterizeTime(24);seedRandom(' + ((s + k * 31) % 99991) + '+Math.floor(time*24)*7,true);' + jzN(str * 100) + '*random(0.8,1.25)');
        // the browser re-rolls the melt every 24 fps step: after the first step the whole melt jumps to a new place
        jzSetExpr(jzXf(L, 'ADBE Position'), 'var S0=' + Math.floor(ev.t * 24 + 0.000001) + ',st=Math.floor(time*24+0.000001);seedRandom(' + (s % 99991) + '+st*19,true);' +
            'var jx=random(-0.18,0.18)*thisComp.width,jy=random(-0.05,0.05)*thisComp.height;st<=S0?value:[value[0]+jx,value[1]+jy]');
    }
} });

/* ---- interlace \u2014 \u30A4\u30F3\u30BF\u30FC\u30EC\u30FC\u30B9: alternate row bands knocked sideways against the rest (square Wave Warp) */
jzReg('fx', 'interlace', { build: function (f, ev) {
    var H = f.H, a = fx2_amp(ev), s = fx2_s(ev), Lr = Math.max(1, Math.round(H / 200));
    var hd = fx2_head(ev, ',A=' + jzN(a) + ',SD=' + (s % 9973)) + 'seedRandom(SD+ST*7,true);var sg=random()<0.5?1:-1;var dx=sg*(0.012+0.02*random())*thisComp.width*A*(1-0.45*p);\n';
    var L = jzAdjLayer(f.comp, 'JZ FX interlace', ev.t, ev.dur);
    var ww = jzEffect(L, 'ADBE Wave Warp', 'JZ Interlace Rows');
    jzEP(ww, 1, 2); jzEX(ww, 2, hd + '0.675*Math.abs(dx)'); jzEP(ww, 3, 2 * Lr); jzEP(ww, 4, 0); jzEP(ww, 5, 0); jzEP(ww, 6, 1);
    jzEX(ww, 7, hd + '(ST%2)*180+(dx<0?180:0)');
    var tr = fx2_xform(L, 'JZ Interlace Shift', null);
    jzEX(tr, 2, hd + '[value[0]+0.325*dx,value[1]]');
    // the browser paints the background first: bg-coloured edges where the rows leave the frame
    var S = jzEvShape(f.comp, 'JZ FX interlace edges', ev.t, ev.dur), side, g, r;
    for (side = 0; side < 2; side++) {
        // (the rect is configured before the fill is added \u2014 adding the fill invalidates r in AE)
        g = jzGrp(S, side ? 'right' : 'left'); r = jzAddRect(g, 10, H + 4, 0, 0, 0);
        jzSetExpr(r.property('ADBE Vector Rect Size'), hd + '[Math.max(' + (side ? '-dx,0.35*dx' : 'dx,-0.35*dx') + ')+3,thisComp.height+4]');
        jzSetExpr(r.property('ADBE Vector Rect Position'), hd + 'var w=Math.max(' + (side ? '-dx,0.35*dx' : 'dx,-0.35*dx') + ')+3;[' + (side ? 'thisComp.width-w/2' : 'w/2') + ',thisComp.height/2]');
        jzAddFill(g, ev.sc.bg);
    }
} });

/* ---- macroBlock \u2014 \u30D6\u30ED\u30C3\u30AF\u30CE\u30A4\u30BA: grid-aligned blocks smear down, quantise or slip (re-rolled every 24 fps step) */
jzReg('fx', 'macroBlock', { build: function (f, ev) {
    var W = f.W, H = f.H, a = fx2_amp(ev), s = fx2_s(ev), B = Math.max(6, Math.round(Math.min(W, H) / 18));
    var nx = Math.ceil(W / B), ny = Math.ceil(H / B), steps = fx2_steps(ev, 4), k, c, i, j, q;
    for (k = 0; k < steps.length; k++) {
        var st = steps[k].st * 23 + s, sm = [], qu = [], sl = [], df = [], slip = 0, smTop = H;
        var nC = 2 + (fx2_h(st, 1) % 3);
        for (c = 0; c < nC; c++) {
            var gw = Math.min(nx, 3 + (fx2_h(st, c, 2) % 7)), gh = 1 + (fx2_h(st, c, 3) % 3);
            var i0 = Math.floor(fx2_r(st, c, 4) * (nx - gw + 1)), j0 = Math.round(ny * (0.38 + 0.24 * fx2_r(st, c, 5)) - gh / 2);
            for (j = 0; j < gh; j++) for (i = 0; i < gw; i++) {
                var id = i * 16 + j;
                if (fx2_r(st, c, id, 6) < 0.12) continue;
                var bx = (i0 + i) * B, by = jzClamp(j0 + j, 0, ny - 1) * B, bw = Math.min(B, W - bx), bh = Math.min(B, H - by), m = fx2_r(st, c, id, 7);
                if (bw < 1 || bh < 1) continue;
                if (m < 0.42) { sm.push([bx, by, bx + bw, by + Math.min(H - by, B * (1 + (fx2_h(st, c, id, 9) % 3)))]); smTop = Math.min(smTop, by); }
                else if (m < 0.7 || m >= 0.9) qu.push([bx, by, bx + bw, by + bh, m >= 0.9]);
                else { sl.push([bx, by, bx + bw, by + bh]); slip += (fx2_r(st, c, id, 10) < 0.5 ? -1 : 1) * (1 + (fx2_h(st, c, id, 12) % 2)); }
                if (m >= 0.42 && fx2_r(st, c, id, 8) < 0.06 * a) df.push([bx, by, bw, bh, fx2_r(st, c, id, 11) < 0.5 ? ev.sc.ghostA : ev.sc.ghostB]);
            }
        }
        var t0 = steps[k].t0, t1 = steps[k].t1, L, e;
        if (sm.length) {     // datamosh bleed: the blocks' top rows smeared down
            L = fx2_span(jzAdjLayer(f.comp, 'JZ FX macroBlock smear', t0, t1 - t0), t0, t1);
            for (q = 0; q < sm.length; q++) fx2_maskRect(L, sm[q][0], sm[q][1], sm[q][2], sm[q][3]);
            e = fx2_xform(L, 'JZ Block Smear', [W / 2, smTop + 0.5]); jzEP(e, 3, 0); jzEP(e, 5, 100); jzEP(e, 4, Math.min(9000, 100 * B * 1.5));
        }
        if (qu.length) {     // quantised to 3x3 flat cells (the "flat" blocks too)
            L = fx2_span(jzAdjLayer(f.comp, 'JZ FX macroBlock quant', t0, t1 - t0), t0, t1);
            for (q = 0; q < qu.length; q++) fx2_maskRect(L, qu[q][0], qu[q][1], qu[q][2], qu[q][3]);
            e = jzEffect(L, 'ADBE Mosaic', 'JZ Block Cells'); jzEP(e, 1, Math.max(1, Math.round(3 * W / B))); jzEP(e, 2, Math.max(1, Math.round(3 * H / B))); jzEP(e, 3, 1);
        }
        if (sl.length) {     // slipped: copied from a neighbouring block
            L = fx2_span(jzAdjLayer(f.comp, 'JZ FX macroBlock slip', t0, t1 - t0), t0, t1);
            for (q = 0; q < sl.length; q++) fx2_maskRect(L, sl[q][0], sl[q][1], sl[q][2], sl[q][3]);
            e = jzEffect(L, 'ADBE Offset', 'JZ Block Slip'); jzEP(e, 1, [W / 2 - (slip < 0 ? -1 : 1) * B * (Math.abs(slip) > sl.length * 1.4 ? 2 : 1), H / 2]);
        }
        if (df.length) {     // a few blocks flashed in the ghost colours (difference)
            L = fx2_span(jzEvShape(f.comp, 'JZ FX macroBlock tint', t0, t1 - t0), t0, t1);
            L.blendingMode = BlendingMode.DIFFERENCE; jzXf(L, 'ADBE Opacity').setValue(30);
            for (q = 0; q < df.length; q++) { var g = jzGrp(L); jzAddRect(g, df[q][2], df[q][3], 0, df[q][0] + df[q][2] / 2, df[q][1] + df[q][3] / 2); jzAddFill(g, df[q][4] || '#FF3080'); }
        }
    }
} });

// ================================================================ print / stylise
/* ---- halftone \u2014 \u7DB2\u70B9: the picture seen through a 45\u00B0 screen on a paper-tone ground (square dots: Venetian Blinds x2 as a matte) */
jzReg('fx', 'halftone', { build: function (f, ev) {
    var c = Math.max(5, Math.round(Math.min(f.W, f.H) / 44));
    var G = jzEvSolid(f.comp, 'JZ FX halftone ground', jzMixHex(ev.sc.bg, ev.sc.fg, 0.16), ev.t, ev.dur);
    var M = jzEvSolid(f.comp, 'JZ FX halftone screen', '#FFFFFF', ev.t, ev.dur);
    // browser dot radius r = lerp(0.72c, 0.42c, amt) \u2192 square dot of the same area \u2192 blinds completion
    var ex = fx2_head(ev) + 'var r=0.72-0.3*ahr(p,0.3,0.72);100*(1-Math.min(1,1.7725*r))';
    var d, vb, dirs = [45, 135];
    for (d = 0; d < 2; d++) { vb = jzEffect(M, 'ADBE Venetian Blinds', 'JZ Screen ' + dirs[d]); jzEX(vb, 1, ex); jzEP(vb, 2, dirs[d]); jzEP(vb, 3, c); }
    G.trackMatteType = TrackMatteType.ALPHA_INVERTED;
} });

/* ---- duotone \u2014 \u30C0\u30D6\u30EB\u30C8\u30FC\u30F3: the frame mapped onto two scheme hues (Tint) */
jzReg('fx', 'duotone', { build: function (f, ev) {
    var s = fx2_s(ev), hp = fx2_huePair(ev.sc), h1 = hp[0], h2 = hp[1], t;
    if (fx2_r(s, 1) < 0.4) { t = h1; h1 = h2; h2 = t; }
    var L = jzAdjLayer(f.comp, 'JZ FX duotone', ev.t, ev.dur);
    var tn = jzEffect(L, 'ADBE Tint', 'JZ Duotone');
    jzEP(tn, 1, jzHex(jzHsl(h1, 0.8, 0.15))); jzEP(tn, 2, jzHex(jzHsl(h2, 1, 0.76)));
    jzEX(tn, 3, fx2_head(ev, ',AC=' + jzN(fx2_amp(ev, 0.6, 1))) + '100*AC*ahr(p,0.12,0.7)');
} });

/* ---- ditherBit \u2014 1bit\u30C7\u30A3\u30B6: coarse pixels, noise dither, hard threshold, mapped to the scheme's darkest / lightest colour */
jzReg('fx', 'ditherBit', { build: function (f, ev) {
    var sc = ev.sc, q = Math.max(2, Math.round(f.H / 200));
    var hd = fx2_head(ev, ',Q=' + q) + 'var c=Q*(p<0.34?2:1);\n';
    var L = jzAdjLayer(f.comp, 'JZ FX ditherBit', ev.t, ev.dur), m;
    m = jzEffect(L, 'ADBE Mosaic', 'JZ Dither Cells'); jzEX(m, 1, hd + 'Math.round(thisComp.width/c)'); jzEX(m, 2, hd + 'Math.round(thisComp.height/c)'); jzEP(m, 3, 1);
    var nz = jzEffect(L, 'ADBE Noise', 'JZ Dither Noise'); jzEP(nz, 1, 40); jzEP(nz, 2, 0);
    m = jzEffect(L, 'ADBE Mosaic', 'JZ Dither Cells 2'); jzEX(m, 1, hd + 'Math.round(thisComp.width/c)'); jzEX(m, 2, hd + 'Math.round(thisComp.height/c)'); jzEP(m, 3, 1);
    var th = jzEffect(L, 'ADBE Threshold2', 'JZ 1bit'); jzEP(th, 1, 128);
    var tn = jzEffect(L, 'ADBE Tint', 'JZ 1bit Colours');
    var c0 = fx2_ext([sc.bg, sc.fg, sc.ink], true), c1 = fx2_ext([sc.bg, sc.fg, sc.ink], false);
    jzEP(tn, 1, jzHex(c0)); jzEP(tn, 2, jzHex(c1)); jzEP(tn, 3, 100);
    // the ordered (Bayer) dither turns the flat background into a sparse dot lattice (density = its brightness): a dot screen on top
    var lb = jzLum(sc.bg), dark = lb < 0.5, fr = jzClamp(dark ? lb : 1 - lb, 0.02, 0.3);
    var D = jzEvSolid(f.comp, 'JZ FX ditherBit dots', dark ? c1 : c0, ev.t, ev.dur), d, vb;
    for (d = 0; d < 2; d++) { vb = jzEffect(D, 'ADBE Venetian Blinds', 'JZ Dither Dots ' + (d + 1)); jzEP(vb, 1, 100 * (1 - Math.min(0.5, Math.sqrt(fr)))); jzEP(vb, 2, d * 90); jzEX(vb, 3, hd + '4*c'); }
} });

// ================================================================ frame motion
/* ---- rotateSnap \u2014 \u50BE\u304D\u30B9\u30CA\u30C3\u30D7: the frame snaps to a tilt and springs back level (zoomed to cover) */
jzReg('fx', 'rotateSnap', { build: function (f, ev) {
    var s = fx2_s(ev), dir = fx2_r(s, 1) < 0.5 ? 1 : -1, TH = dir * (3 + 2 * fx2_r(s, 2)) * fx2_amp(ev, 0.5, 1.3);
    var hd = fx2_head(ev, ',TH=' + jzN(TH)) + 'var e=p<0.12?1:Math.exp(-4.5*(p-0.12))*Math.cos((p-0.12)*Math.PI*2.4);var th=TH*e;' +
        'var q=Math.abs(th)*Math.PI/180,c=Math.cos(q),sn=Math.sin(q),W=thisComp.width,H=thisComp.height;var z=Math.max((W*c+H*sn)/W,(W*sn+H*c)/H)*(1+0.025*Math.abs(e));\n';
    var L = jzAdjLayer(f.comp, 'JZ FX rotateSnap', ev.t, ev.dur);
    var tr = fx2_xform(L, 'JZ Rotate Snap', null);
    jzEX(tr, 4, hd + 'z*100'); jzEX(tr, 8, hd + 'th');
} });

/* ---- echoFrames \u2014 \u6B8B\u50CF\u30A8\u30B3\u30FC: stepped after-images of the frame (lighten on dark schemes / darken on light ones) */
jzReg('fx', 'echoFrames', { build: function (f, ev) {
    var s = fx2_s(ev), dk = fx2_dk(ev.sc), a = fx2_amp(ev, 0.5, 1.3), PI = Math.PI;
    var ang = fx2_r(s, 1) < 0.65 ? (fx2_r(s, 2) < 0.5 ? 0 : PI) : (fx2_r(s, 3) < 0.5 ? 0.25 : 0.75) * PI + (fx2_r(s, 2) < 0.5 ? 0 : PI);
    var hd = fx2_head(ev, ',CA=' + jzN(Math.cos(ang)) + ',SA=' + jzN(Math.sin(ang)) + ',MM=' + jzN(Math.min(f.W, f.H) * a)) +
        'var d=MM*(0.02+0.035*oc(p)),fade=p<0.1?1:1-iq((p-0.1)/0.9);\n';
    var i;
    for (i = 4; i >= 1; i--) {
        fx2_copies(f, ev, 'echoFrames', function (L, isM) {
            var tr = fx2_xform(L, 'JZ Echo Offset', null);
            jzEX(tr, 2, hd + '[value[0]+CA*d*' + i + ',value[1]+SA*d*' + i + ']');
            if (!isM) { jzEX(tr, 9, hd + '100*Math.max(0,fade*' + jzN(0.64 - i * 0.12) + ')'); L.blendingMode = dk ? BlendingMode.LIGHTEN : BlendingMode.DARKEN; }
        });
    }
} });

/* ---- kaleido \u2014 \u4E07\u83EF\u93E1: n mirrored wedges around the centre \u2014 picture copies, each clipped to a source wedge, turned and flipped into place */
jzReg('fx', 'kaleido', { build: function (f, ev) {
    var W = f.W, H = f.H, s = fx2_s(ev), n = fx2_r(s, 1) < 0.5 ? 6 : 8, th = 360 / n, PI = Math.PI, i;
    var src = fx2_r(s, 4) < 0.5 ? 0 : 180, sg = fx2_r(s, 3) < 0.5 ? 1 : -1, cx = W / 2, cy = H / 2, R = fx2_hyp(W, H) * 1.3;
    var hd = fx2_head(ev, ',R0=' + jzN(fx2_r(s, 2) * 360) + ',SG=' + sg) + 'var rot=R0+p*0.5*SG*180/Math.PI,z=1.05+0.12*p;\n';
    var ha = (th / 2 + 0.35) * PI / 180, a0 = src * PI / 180;
    var wedge = [[cx, cy], [cx + Math.cos(a0 - ha) * R, cy + Math.sin(a0 - ha) * R], [cx + Math.cos(a0) * R * 1.2, cy + Math.sin(a0) * R * 1.2], [cx + Math.cos(a0 + ha) * R, cy + Math.sin(a0 + ha) * R]];
    for (i = 0; i < n; i++) {
        var odd = i % 2 === 1, rotOff = i * th + (src && !odd ? 180 : 0);
        var sx = odd && src ? '-z' : 'z', sy = odd && !src ? '-z' : 'z';
        fx2_copies(f, ev, 'kaleido', function (L, isM) {
            fx2_clipMask(L, wedge);
            var tr = fx2_xform(L, 'JZ Kaleido Wedge', [cx, cy]);
            jzEP(tr, 3, 0);
            jzEX(tr, 4, hd + '100*' + sy); jzEX(tr, 5, hd + '100*' + sx); jzEX(tr, 8, hd + 'rot+' + jzN(rotOff));
            if (!isM) jzEX(tr, 9, hd + '100*(p>0.75?1-(p-0.75)/0.25*0.5:1)');
        });
    }
} });

// ================================================================ inversion / light
/* ---- bandInvert \u2014 \u5E2F\u53CD\u8EE2: a few bands of the frame inverted (difference), re-rolled every 24 fps step */
jzReg('fx', 'bandInvert', { build: function (f, ev) {
    var W = f.W, H = f.H, s = fx2_s(ev), vert = fx2_r(s, 2) < 0.22, a = fx2_amp(ev, 0.6, 1.3), Lx = vert ? W : H, Mx = vert ? H : W;
    var steps = fx2_steps(ev, 12), k, i;
    for (k = 0; k < steps.length; k++) {
        var st = steps[k].st * 13 + s, n = 2 + (fx2_h(st, 1) % 4);
        var S = fx2_span(jzEvShape(f.comp, 'JZ FX bandInvert', steps[k].t0, steps[k].t1 - steps[k].t0), steps[k].t0, steps[k].t1);
        S.blendingMode = BlendingMode.DIFFERENCE;
        var g = jzGrp(S, 'bands');
        for (i = 0; i < n; i++) {
            var h = Math.max(2, Lx * fx2_rr(0.012, 0.12, st, i, 3) * a), y = Math.round(Lx * fx2_rr(0.12, 0.88, st, i, 4) - h / 2);
            var part = fx2_r(st, i, 5) < 0.35, x0 = part ? Mx * fx2_rr(0, 0.5, st, i, 6) : 0, w = part ? Mx * fx2_rr(0.25, 0.6, st, i, 7) : Mx;
            if (vert) jzAddRect(g, h, w, 0, y + h / 2, x0 + w / 2); else jzAddRect(g, w, h, 0, x0 + w / 2, y + h / 2);
        }
        jzAddFill(g, '#FFFFFF');
    }
} });

/* ---- lightRays \u2014 \u5149\u8292: god rays from a point above the lyric (masked wedges on a radial Gradient Ramp), slowly turning */
jzReg('fx', 'lightRays', { build: function (f, ev) {
    var W = f.W, H = f.H, sc = ev.sc, s = fx2_s(ev), dk = fx2_dk(sc), M = Math.min(W, H), D = fx2_hyp(W, H), PI = Math.PI;
    var cx = W * (0.5 + fx2_rs(s, 1) * 0.12), cy = H * (0.42 + fx2_rs(s, 2) * 0.1);
    var n = 12 + (fx2_h(s, 3) % 8), rot = fx2_r(s, 4) * 2 * PI, sg = fx2_r(s, 7) < 0.5 ? 1 : -1;
    var col = dk ? jzMixHex(sc.fg, '#FFFFFF', 0.3) : jzMixHex(fx2_vivid(sc), '#FFFFFF', 0.35);
    var SZ = Math.ceil(D * 2.2), C0 = SZ / 2, R = D * 1.05, i;
    var L = fx2_solid(f, 'JZ FX lightRays', col, SZ, SZ, ev.t, fx2_t1(ev));
    jzXf(L, 'ADBE Position').setValue([cx, cy]);
    for (i = 0; i < n; i++) {
        var an = rot + i / n * 2 * PI + fx2_rs(s, i, 5) * 0.12, w = 2 * PI / n * fx2_rr(0.14, 0.45, s, i, 6);
        fx2_maskPts(L, [[C0, C0], [C0 + Math.cos(an - w / 2) * R, C0 + Math.sin(an - w / 2) * R], [C0 + Math.cos(an + w / 2) * R, C0 + Math.sin(an + w / 2) * R]]);
    }
    fx2_maskEll(L, C0, C0, M * 0.2, M * 0.2, { feather: M * 0.2, op: 80 });     // the bright core
    var hd = fx2_head(ev, ',A0=' + jzN(fx2_amp(ev, 0.5, 1.2)) + ',DG=' + jzN(D)) + 'var Rp=DG*(0.4+0.6*oc(p));\n';
    var rp = jzEffect(L, 'ADBE Ramp', 'JZ Ray Falloff');
    jzEP(rp, 1, [C0, C0]); jzEP(rp, 2, jzHex(col)); jzEX(rp, 3, hd + '[' + jzN(C0) + '+0.65*Rp,' + jzN(C0) + ']'); jzEP(rp, 4, jzHex(dk ? '#000000' : '#FFFFFF')); jzEP(rp, 5, 2);
    L.blendingMode = dk ? BlendingMode.SCREEN : BlendingMode.MULTIPLY;
    jzSetExpr(jzXf(L, 'ADBE Opacity'), hd + 'seedRandom(ST*5+3,true);100*Math.min(1,A0*Math.pow(bell(p),0.6)*(0.9+0.1*random())*0.8)');
    jzSetExpr(jzXf(L, 'ADBE Rotate Z'), hd + 'p*' + jzN(0.22 * sg * 180 / PI));
} });

/* ---- anamorphic \u2014 \u30A2\u30CA\u30E2\u30D5\u30EC\u30A2: a horizontal lens streak with core and ghosts drifting across (feathered elliptical masks) */
jzReg('fx', 'anamorphic', { build: function (f, ev) {
    var W = f.W, H = f.H, sc = ev.sc, s = fx2_s(ev), dk = fx2_dk(sc), M = Math.min(W, H), col = fx2_vivid(sc), dir = fx2_r(s, 3) < 0.5 ? 1 : -1;
    var y = H * (0.5 + fx2_rs(s, 1) * 0.1), x = W * (0.5 + fx2_rs(s, 2) * 0.22), t1 = fx2_t1(ev);
    var hd = fx2_head(ev, ',A0=' + jzN(fx2_amp(ev, 0.5, 1.2)) + ',DX=' + jzN(W * 0.14 * dir)) + 'seedRandom(ST*7+5,true);var a=A0*Math.pow(bell(p),0.5)*(0.88+0.12*random());var dx=(p-0.5)*DX;\n';
    var mode = dk ? BlendingMode.SCREEN : BlendingMode.MULTIPLY;
    function mk(name, hex, opK, moveK) {
        var L = jzEvSolid(f.comp, 'JZ FX anamorphic ' + name, hex, ev.t, ev.dur);
        L.blendingMode = mode;
        jzSetExpr(jzXf(L, 'ADBE Opacity'), hd + '100*Math.min(1,a*' + jzN(opK) + ')');
        jzSetExpr(jzXf(L, 'ADBE Position'), hd + '[value[0]+dx*' + jzN(moveK) + ',value[1]]');
        return L;
    }
    var A = mk('streak', dk ? col : jzMixHex(col, '#FFFFFF', 0.2), dk ? 0.6 : 0.75, 1);
    fx2_maskEll(A, x, y, W * 0.78, M * 0.045, { feather: M * 0.05, op: 40 });
    fx2_maskEll(A, x, y, W * 0.36, M * 0.03, { feather: M * 0.04, op: 100 });
    var B = mk('core', dk ? '#FFFFFF' : col, dk ? 0.95 : 1, 1);
    fx2_maskEll(B, x, y, W * 0.6, Math.max(1.5, M * 0.006), { feather: Math.max(2, M * 0.008), op: 100 });
    fx2_maskEll(B, x, y, M * (dk ? 0.07 : 0.05), M * (dk ? 0.07 : 0.05), { feather: M * 0.06, op: dk ? 85 : 55 });
    if (dk) {      // lens ghosts along the line through the frame centre (they drift the other way)
        var C = mk('ghosts', col, 0.16, -1.1), fs = [0.55, 1.1, 1.7], rs = [0.03, 0.06, 0.02], j;
        for (j = 0; j < 3; j++) fx2_maskEll(C, W / 2 + (W / 2 - x) * fs[j], H / 2 + (H / 2 - y) * fs[j], M * rs[j], M * rs[j], { feather: M * rs[j] * 0.4 });
    }
} });

/* ---- heartbeat \u2014 \u9F13\u52D5: a coloured vignette closes in twice with a slight push */
jzReg('fx', 'heartbeat', { build: function (f, ev) {
    var W = f.W, H = f.H, sc = ev.sc, dk = fx2_dk(sc), M = Math.min(W, H), R = fx2_hyp(W, H) / 2;
    var hd = fx2_head(ev, ',AC=' + jzN(fx2_amp(ev, 0.5, 1.2))) +
        'function pulse(x,c,w){var u=(x-c)/w;return u<0||u>1?0:(u<0.22?oc(u/0.22):1-ioc((u-0.22)/0.78));}var P=Math.max(pulse(p,0,0.36),0.8*pulse(p,0.44,0.56))*AC;\n';
    var L = jzAdjLayer(f.comp, 'JZ FX heartbeat', ev.t, ev.dur);
    var tr = fx2_xform(L, 'JZ Heartbeat Push', null); jzEX(tr, 4, hd + '100*(1+0.03*P)');
    // vignette: inverted feathered circle; alpha ramps 0 \u2192 1 from r_in = M*(0.52-0.2P) to the corners
    var col = jzMixHex(fx2_vivid(sc), '#000000', dk ? 0.1 : 0.25), pk = dk ? 0.55 : 0.8, rin = M * 0.52, rc = (rin + R) / 2, SZ = Math.ceil(R * 2.6);
    var V = fx2_solid(f, 'JZ FX heartbeat vignette', col, SZ, SZ, ev.t, fx2_t1(ev));
    jzXf(V, 'ADBE Position').setValue([W / 2, H / 2]);
    fx2_maskEll(V, SZ / 2, SZ / 2, rc, rc, { feather: (R - rin) * 0.9, inv: true });
    jzSetExpr(jzXf(V, 'ADBE Scale'), hd + 'var k=((' + jzN(M) + '*(0.52-0.2*P))+' + jzN(R) + ')/2/' + jzN(rc) + ';[value[0]*k,value[1]*k]');
    jzSetExpr(jzXf(V, 'ADBE Opacity'), hd + 'Math.min(100,115*' + jzN(pk) + '*P)');
    V.blendingMode = dk ? BlendingMode.SCREEN : BlendingMode.MULTIPLY;
} });

// ================================================================ film / TV
/* ---- tvStatic \u2014 \u7802\u5D50: TV snow over the frame with a rolling dark band and scan lines */
jzReg('fx', 'tvStatic', { build: function (f, ev) {
    var W = f.W, H = f.H, s = fx2_s(ev), sz = Math.max(1, Math.round(H / 540));
    var hd = fx2_head(ev, ',AC=' + jzN(fx2_amp(ev, 0.7, 1.1)) + ',SD=' + (s % 9973)) + 'var cov=(p<0.4?0.5+0.5*oq(p/0.4):1-oq((p-0.4)/0.6))*AC;\n';
    var N = jzEvSolid(f.comp, 'JZ FX tvStatic', '#7A7A7A', ev.t, ev.dur);
    var nz = jzEffect(N, 'ADBE Noise', 'JZ Snow'); jzEP(nz, 1, 100); jzEP(nz, 2, 0);
    var mo = jzEffect(N, 'ADBE Mosaic', 'JZ Snow Grain'); jzEP(mo, 1, Math.round(W / (2 * sz))); jzEP(mo, 2, Math.round(H / sz)); jzEP(mo, 3, 1);
    jzSetExpr(jzXf(N, 'ADBE Opacity'), hd + '100*Math.min(1,cov)');
    var S = jzEvShape(f.comp, 'JZ FX tvStatic bars', ev.t, ev.dur), g, i;
    jzSetExpr(jzXf(S, 'ADBE Opacity'), hd + '100*Math.min(1,cov)');
    for (i = 0; i < 4; i++) {
        g = jzGrp(S, 'line ' + (i + 1)); jzAddRect(g, W, Math.max(1, H * 0.003), 0, 0, 0); jzAddFill(g, '#FFFFFF', 50);
        fx2_gx(g, 'ADBE Vector Position', hd + 'seedRandom(SD+ST*3+' + (i * 17 + 4) + ',true);[' + jzN(W / 2) + ',random()*' + jzN(H) + ']');
    }
    g = jzGrp(S, 'band'); jzAddRect(g, W, H * 0.16, 0, 0, 0); jzAddFill(g, '#000000', 30);
    fx2_gx(g, 'ADBE Vector Position', hd + 'seedRandom(SD+ST*3,true);[' + jzN(W / 2) + ',(random()*1.2-0.1)*' + jzN(H) + '+' + jzN(H * 0.08) + ']');
} });

/* ---- dustScratches \u2014 \u30D5\u30A3\u30EB\u30E0\u50B7: film dust, hairs and vertical scratches, a new set every 24 fps step */
jzReg('fx', 'dustScratches', { build: function (f, ev) {
    var W = f.W, H = f.H, sc = ev.sc, s = fx2_s(ev), dk = fx2_dk(sc), M = Math.min(W, H), lw = Math.max(1, M * 0.0017), LT = '#FFFDF5', DKc = '#181008';
    var aEx = fx2_head(ev, ',AC=' + jzN(fx2_amp(ev, 0.6, 1.2))) + '100*Math.min(1,ahr(p,0.08,0.7)*AC)';
    var nS = 1 + (fx2_h(s, 3) % 3), steps = fx2_steps(ev, 12), k, i, g, st;
    for (k = 0; k < steps.length; k++) {
        st = steps[k].st * 31 + s;
        var S = fx2_span(jzEvShape(f.comp, 'JZ FX dustScratches', steps[k].t0, steps[k].t1 - steps[k].t0), steps[k].t0, steps[k].t1);
        jzSetExpr(jzXf(S, 'ADBE Opacity'), aEx);
        var nD = 7 + (fx2_h(st, 14) % 9);
        for (i = 0; i < nD; i++) {       // dust specks and hairs
            var x = fx2_r(st, i, 15) * W, y = fx2_r(st, i, 16) * H, r = M * fx2_rr(0.0018, 0.007, st, i, 17);
            var c = fx2_r(st, i, 18) < (dk ? 0.7 : 0.25) ? LT : DKc, op = 100 * fx2_rr(0.45, 0.9, st, i, 19);
            g = jzGrp(S, 'dust ' + (i + 1));
            if (fx2_r(st, i, 20) < 0.72) {
                jzAddEllipse(g, 2 * r, 2 * r * fx2_rr(0.45, 1, st, i, 21), 0, 0); jzAddFill(g, c, op);
                jzGX(g).property('ADBE Vector Position').setValue([x, y]); jzGX(g).property('ADBE Vector Rotation').setValue(fx2_r(st, i, 22) * 360);
            } else {
                var Lh = M * fx2_rr(0.02, 0.06, st, i, 23), an = fx2_r(st, i, 24) * 2 * Math.PI;
                var c1 = [Math.cos(an) * Lh * 0.4 + fx2_rs(st, i, 25) * Lh * 0.4, Math.sin(an) * Lh * 0.4], c2 = [Math.cos(an + 0.8) * Lh * 0.8, Math.sin(an + 0.8) * Lh * 0.8], e = [Math.cos(an + 0.3) * Lh, Math.sin(an + 0.3) * Lh];
                fx2_path(g, [[x, y], [x + e[0], y + e[1]]], [[0, 0], [c2[0] - e[0], c2[1] - e[1]]], [[c1[0], c1[1]], [0, 0]], false);
                jzAddStroke(g, c, lw * 0.8, op);
            }
        }
        for (i = 0; i < nS; i++) {       // scratches
            if (fx2_r(st, i, 4) < 0.18) continue;
            var sx = W * (0.1 + 0.8 * fx2_r(s, i, 5)) + fx2_rs(st, i, 6) * M * 0.012;
            var y0 = fx2_r(s, i, 7) < 0.5 ? -2 : H * 0.5 * fx2_r(st, i, 8), y1 = fx2_r(s, i, 9) < 0.5 ? H + 2 : y0 + H * fx2_rr(0.3, 0.7, st, i, 10);
            var q = [sx + fx2_rs(st, i, 12) * M * 0.008, (y0 + y1) / 2], ex = sx + fx2_rs(s, i, 13) * M * 0.006;
            g = jzGrp(S, 'scratch ' + (i + 1));
            fx2_path(g, [[sx, y0], [ex, y1]], [[0, 0], [(q[0] - ex) * 2 / 3, (q[1] - y1) * 2 / 3]], [[(q[0] - sx) * 2 / 3, (q[1] - y0) * 2 / 3], [0, 0]], false);
            var sk = jzAddStroke(g, dk ? LT : DKc, 1.4 * lw * fx2_rr(0.7, 1.7, s, i, 11), 70);
            try { sk.property('ADBE Vector Stroke Line Cap').setValue(2); } catch (e1) {}
        }
        g = jzGrp(S, 'flicker'); jzAddRect(g, W + 4, H + 4, 0, W / 2, H / 2); jzAddFill(g, fx2_r(st, 1) < 0.5 ? LT : DKc, 100 * 0.06 * fx2_r(st, 2));
    }
} });

// ================================================================ film strip / 3D / water
/* ---- filmAdvance \u2014 \u30D5\u30A3\u30EB\u30E0\u9001\u308A: the picture shrinks into a film gate with sprocket holes and the strip is pulled on by one frame */
jzReg('fx', 'filmAdvance', { build: function (f, ev) {
    var W = f.W, H = f.H, s = fx2_s(ev), dir = fx2_r(s, 1) < 0.7 ? 1 : -1, gap = Math.max(2, H * 0.03);
    var hd = fx2_head(ev, ',DIR=' + dir + ',GP=' + jzN(gap)) + 'var env=p<0.22?ioc(p/0.22):(p>0.78?1-ioc((p-0.78)/0.22):1);' +
        'var W=thisComp.width,H=thisComp.height,z=1-0.17*env,fw=W*z,fh=H*z,x0=(W-fw)/2,top=(H-fh)/2,q=ioc(cl((p-0.2)/0.6)),off=q*fh*DIR,hp=fh/4;\n';
    // the strip: Offset wraps the frame (one full frame per pull), then the Transform shrinks it into the gate
    var L = jzAdjLayer(f.comp, 'JZ FX filmAdvance', ev.t, ev.dur);
    var of = jzEffect(L, 'ADBE Offset', 'JZ Film Pull'); jzEX(of, 1, hd + '[W/2,H/2-q*H*DIR]');
    var tr = fx2_xform(L, 'JZ Film Gate', null); jzEX(tr, 4, hd + '100*z');
    // film base + frame lines + sprocket holes
    var S = jzEvShape(f.comp, 'JZ FX filmAdvance strip', ev.t, ev.dur), g, r, side, rep;
    for (side = 0; side < 2; side++) {
        g = jzGrp(S, side ? 'holes right' : 'holes left');
        r = jzAddRect(g, 10, 10, 0, 0, 0);
        jzSetExpr(r.property('ADBE Vector Rect Size'), hd + '[x0*0.42,hp*0.46]');
        jzSetExpr(r.property('ADBE Vector Rect Roundness'), hd + 'x0*0.084');
        var fl = jzAddFill(g, '#ECE6DA'); jzSetExpr(fl.property('ADBE Vector Fill Opacity'), hd + '(x0>3?90:0)*env');
        rep = jzVecs(g).addProperty('ADBE Vector Filter - Repeater');
        rep.property('ADBE Vector Repeater Copies').setValue(Math.ceil(4 / 0.8) + 3);
        jzSetExpr(rep.property('ADBE Vector Repeater Transform').property('ADBE Vector Repeater Position'), hd + '[0,hp]');
        fx2_gx(g, 'ADBE Vector Position', hd + 'var yc=top-off,y=((yc%hp)+hp)%hp-hp;[' + (side ? 'W-x0*0.29-x0*0.21' : 'x0*0.29+x0*0.21') + ',y+hp/2]');
    }
    g = jzGrp(S, 'frame lines');
    for (side = 0; side < 2; side++) {
        r = jzAddRect(g, 10, 10, 0, 0, 0);
        jzSetExpr(r.property('ADBE Vector Rect Size'), hd + '[fw+2,GP*env]');
        jzSetExpr(r.property('ADBE Vector Rect Position'), hd + 'var sy=top+(((-off)%fh)+fh)%fh;[W/2,sy' + (side ? '+fh' : '') + ']');
    }
    jzAddFill(g, '#0D0B09');
    g = jzGrp(S, 'film base');
    jzAddRect(g, W + 8, H + 8, 0, W / 2, H / 2);
    r = jzAddRect(g, W, H, 0, W / 2, H / 2); jzSetExpr(r.property('ADBE Vector Rect Size'), hd + '[fw,fh]');
    fx2_eo(jzAddFill(g, '#0D0B09'));
} });

/* ---- perspectiveTilt \u2014 \u30D1\u30FC\u30B9\u63FA\u308C: the frame swings in 3D around its vertical (or horizontal) axis \u2014 perspective strips of picture copies */
jzReg('fx', 'perspectiveTilt', { build: function (f, ev) {
    var W = f.W, H = f.H, sc = ev.sc, s = fx2_s(ev), cols = fx2_r(s, 1) < (W >= H ? 0.7 : 0.35), dir = fx2_r(s, 2) < 0.5 ? 1 : -1;
    var PH = dir * (30 + 10 * fx2_r(s, 3)) * fx2_amp(ev, 0.5, 1.2), Lx = cols ? W : H, Mx = cols ? H : W, N = 10, i;
    jzEvSolid(f.comp, 'JZ FX perspectiveTilt ground', fx2_dk(sc) ? sc.bg : jzMixHex(sc.bg, sc.fg, 0.1), ev.t, ev.dur);
    var hd = fx2_head(ev, ',PH=' + jzN(PH) + ',HL=' + jzN(Lx / 2) + ',DD=' + jzN(1.15 * Math.max(W, H)) + ',MX=' + jzN(Mx)) +
        'var e=p<0.25?oc(p/0.25):Math.cos((p-0.25)/0.75*Math.PI*1.5)*Math.exp(-2.6*(p-0.25)/0.75);var ph=PH*e*Math.PI/180,co=Math.cos(ph),si=Math.sin(ph);' +
        'function pp(u){return DD/(DD+u*HL*si);}function dd(u){return HL+u*HL*co*pp(u);}\n';
    for (i = 0; i < N; i++) {
        var s0 = i * Lx / N, s1 = (i + 1) * Lx / N;
        var sx = hd + 'var d0=dd(' + jzN(-1 + 2 * i / N) + '),d1=dd(' + jzN(-1 + 2 * (i + 1) / N) + '),h=MX*(pp(' + jzN(-1 + 2 * i / N) + ')+pp(' + jzN(-1 + 2 * (i + 1) / N) + '))/2;\n';
        fx2_copies(f, ev, 'perspectiveTilt', function (L, isM) {
            if (cols) fx2_cropMask(L, s0 - 1, -20, s1 + 1, H + 20); else fx2_cropMask(L, -20, s0 - 1, W + 20, s1 + 1);
            var tr = fx2_xform(L, 'JZ Perspective Strip', cols ? [(s0 + s1) / 2, H / 2] : [W / 2, (s0 + s1) / 2]);
            jzEP(tr, 3, 0);
            jzEX(tr, 2, sx + (cols ? '[(d0+d1)/2,' + jzN(H / 2) + ']' : '[' + jzN(W / 2) + ',(d0+d1)/2]'));
            jzEX(tr, cols ? 5 : 4, sx + '(d1-d0)/' + jzN(s1 - s0) + '*100');
            jzEX(tr, cols ? 4 : 5, sx + 'h/MX*100');
        });
    }
} });

/* ---- ripple \u2014 \u6CE2\u7D0B: a water ripple spreads from a drop point (Ripple locked to the wave front) + crest highlights */
jzReg('fx', 'ripple', { build: function (f, ev) {
    var W = f.W, H = f.H, sc = ev.sc, s = fx2_s(ev), dk = fx2_dk(sc), M = Math.min(W, H);
    var cx = W * (0.5 + fx2_rs(s, 1) * 0.1), cy = H * (0.5 + fx2_rs(s, 2) * 0.08), lam = M * 0.1;
    var Rmax = fx2_hyp(Math.max(cx, W - cx), Math.max(cy, H - cy));
    var hd = fx2_head(ev, ',RM=' + jzN(Rmax) + ',LAM=' + jzN(lam) + ',A0=' + jzN(M * 0.022 * fx2_amp(ev, 0.5, 1.3)) + ',HD=' + jzN(fx2_hyp(W, H) / 2)) +
        'var front=(0.05+0.95*oq(p))*RM*1.05,A=A0*(1-0.7*p);\n';
    var L = jzAdjLayer(f.comp, 'JZ FX ripple', ev.t, ev.dur);
    var rp = jzEffect(L, 'ADBE Ripple', 'JZ Ripple');
    jzEX(rp, 1, hd + 'Math.min(100,(front+LAM*0.3)/HD*100)'); jzEP(rp, 2, [cx, cy]); jzEP(rp, 4, 0); jzEP(rp, 5, lam);
    jzEX(rp, 6, hd + '2*A'); jzEX(rp, 7, hd + 'front/LAM*360');
    var S = jzEvShape(f.comp, 'JZ FX ripple crests', ev.t, ev.dur), j, g, el, sk;
    S.blendingMode = dk ? BlendingMode.SCREEN : BlendingMode.MULTIPLY;
    for (j = 2; j >= 0; j--) {
        g = jzGrp(S, 'crest ' + (j + 1)); el = jzAddEllipse(g, 10, 10, 0, 0);
        jzGX(g).property('ADBE Vector Position').setValue([cx, cy]);
        jzSetExpr(el.property('ADBE Vector Ellipse Size'), hd + 'var rc=Math.max(0,front-LAM*' + jzN(j + 0.25) + ');[2*rc,2*rc]');
        sk = jzAddStroke(g, dk ? '#FFFFFF' : jzMixHex(sc.fg, sc.bg, 0.4), Math.max(1, lam / 5 * 0.9), 100);
        jzSetExpr(sk.property('ADBE Vector Stroke Opacity'), hd + 'var rc=front-LAM*' + jzN(j + 0.25) + ';rc>2?100*' + jzN(0.16 * Math.exp(-j * 0.9)) + '*(1-0.6*p)*' + (dk ? '1.6' : '1.2') + ':0');
    }
} });

// ================================================================ manga / graphic overlays
/* ---- focusLines \u2014 \u96C6\u4E2D\u7DDA: thin wedges converging on the centre, redrawn every 24 fps step like hand-drawn animation */
jzReg('fx', 'focusLines', { build: function (f, ev) {
    var W = f.W, H = f.H, sc = ev.sc, s = fx2_s(ev), M = Math.min(W, H), PI = Math.PI;
    var cx = W / 2 + fx2_rs(s, 1) * W * 0.03, cy = H / 2 + fx2_rs(s, 2) * H * 0.03, R = fx2_hyp(W, H) * 0.75, n = 90 + (fx2_h(s, 3) % 50);
    var aEx = fx2_head(ev) + '85*ahr(p,0.1,0.72)', steps = fx2_steps(ev, 12), k, i, ink = fx2_ink(sc);
    for (k = 0; k < steps.length; k++) {
        var km = jzClamp(((steps[k].t0 + steps[k].t1) / 2 - ev.t) / Math.max(ev.dur, 0.001), 0, 1);
        var a = km < 0.1 ? 1 - Math.pow(1 - km / 0.1, 3) : (km > 0.72 ? 1 - Math.pow((km - 0.72) / 0.28, 3) : 1);
        var st = steps[k].st * 5 + s, rx = W * (0.4 + 0.06 * (1 - a)), ry = H * (0.34 + 0.06 * (1 - a));
        var S = fx2_span(jzEvShape(f.comp, 'JZ FX focusLines', steps[k].t0, steps[k].t1 - steps[k].t0), steps[k].t0, steps[k].t1);
        jzSetExpr(jzXf(S, 'ADBE Opacity'), aEx);
        var g = jzGrp(S, 'lines');
        for (i = 0; i < n; i++) {
            var an = (i + fx2_r(st, i, 4) * 0.8) / n * 2 * PI, w = M * fx2_rr(0.002, 0.011, st, i, 5), tip = fx2_rr(1.0, 1.45, st, i, 6);
            var tx = cx + Math.cos(an) * rx * tip, ty = cy + Math.sin(an) * ry * tip, nx = -Math.sin(an), ny = Math.cos(an), ox = cx + Math.cos(an) * R, oy = cy + Math.sin(an) * R;
            fx2_path(g, [[tx, ty], [ox + nx * w, oy + ny * w], [ox - nx * w, oy - ny * w]], null, null, true);
        }
        jzAddFill(g, ink);
    }
} });

/* ---- speedLines \u2014 \u6D41\u7DDA: tapered streaks racing across the frame, clear of the centre band */
jzReg('fx', 'speedLines', { build: function (f, ev) {
    var W = f.W, H = f.H, sc = ev.sc, s = fx2_s(ev), vert = W < H ? fx2_r(s, 1) < 0.5 : fx2_r(s, 1) < 0.15, dir = fx2_r(s, 2) < 0.5 ? 1 : -1;
    var Lx = vert ? H : W, Mx = vert ? W : H, n = 22 + (fx2_h(s, 3) % 12), c1 = fx2_ink(sc), c2 = fx2_vivid(sc), i;
    var hd = fx2_head(ev, ',LX=' + jzN(Lx)) + 'var a=ahr(p,0.12,0.7);\n';
    var S = jzEvShape(f.comp, 'JZ FX speedLines', ev.t, ev.dur);
    for (i = 0; i < n; i++) {
        var q = fx2_r(s, i, 4);
        if (Math.abs(q - 0.5) < 0.12 && fx2_r(s, i, 9) < 0.8) q = q < 0.5 ? 0.38 - fx2_r(s, i, 10) * 0.33 : 0.62 + fx2_r(s, i, 10) * 0.33;
        var pp = q * Mx, len = Lx * fx2_rr(0.15, 0.55, s, i, 5), th = Math.max(1, Mx * fx2_rr(0.002, 0.008, s, i, 6)), sp = fx2_rr(1.3, 2.6, s, i, 7);
        var g = jzGrp(S, 'streak ' + (i + 1)), tl = -len * dir;
        if (!vert) fx2_path(g, [[tl, 0], [0, -th / 2], [0, th / 2]], null, null, true); else fx2_path(g, [[0, tl], [-th / 2, 0], [th / 2, 0]], null, null, true);
        jzAddFill(g, fx2_r(s, i, 12) < 0.22 ? c2 : c1);
        var he = hd + 'var fr=(' + jzN(fx2_r(s, i, 8)) + '+p*' + jzN(sp) + ')%1;var hx=' + jzN(-len * 0.2) + '+fr*(LX+' + jzN(len * 1.2) + ');' + (dir > 0 ? '' : 'hx=LX-hx;');
        fx2_gx(g, 'ADBE Vector Position', he + (vert ? '[' + jzN(pp) + ',hx]' : '[hx,' + jzN(pp) + ']'));
        fx2_gx(g, 'ADBE Vector Group Opacity', hd + '100*a*' + jzN(fx2_rr(0.4, 0.9, s, i, 11)));
    }
} });

/* ---- starGlint \u2014 \u30AD\u30E9\u30C3: 8-point glints popping in sequence around the lyric band */
jzReg('fx', 'starGlint', { build: function (f, ev) {
    var W = f.W, H = f.H, sc = ev.sc, s = fx2_s(ev), dk = fx2_dk(sc), M = Math.min(W, H), col = fx2_vivid(sc), n = 2 + (fx2_h(s, 1) % 3), port = H > W;
    var ac = fx2_amp(ev, 0.6, 1.3), i, j, PI = Math.PI;
    var G = jzEvShape(f.comp, 'JZ FX starGlint glow', ev.t, ev.dur), S = jzEvShape(f.comp, 'JZ FX starGlint', ev.t, ev.dur);
    G.blendingMode = dk ? BlendingMode.SCREEN : BlendingMode.NORMAL;
    var gb = jzEffect(G, 'ADBE Gaussian Blur 2', 'JZ Glint Glow'); jzEP(gb, 1, M * 0.05);
    for (i = 0; i < n; i++) {
        var t0 = i * 0.16 + fx2_r(s, i, 4) * 0.06, R = M * (0.1 + 0.07 * fx2_r(s, i, 5)) * ac;
        var x = W * (0.5 + fx2_rs(s, i, 2) * (port ? 0.3 : 0.34)), y = H * (0.5 + fx2_rs(s, i, 3) * (port ? 0.14 : 0.1));
        var hd = fx2_head(ev) + 'var u=(p-' + jzN(t0) + ')/0.55;var g=(u>0&&u<1)?Math.pow(Math.sin(Math.PI*u),0.8):0;\n';
        var pts = [];
        for (j = 0; j < 16; j++) { var an = j * PI / 8, rr = j % 2 ? R * 0.07 : (j % 4 === 0 ? R : R * 0.42); pts.push([Math.cos(an) * rr, Math.sin(an) * rr]); }
        var g = jzGrp(S, 'glint ' + (i + 1));
        jzAddEllipse(g, R * 0.16, R * 0.16, 0, 0); jzAddFill(g, '#FFFFFF');
        var gs = fx2_sub(g, 'star'); fx2_path(gs, pts, null, null, true); jzAddFill(gs, dk ? '#FFFFFF' : col);
        jzGX(g).property('ADBE Vector Position').setValue([x, y]);
        fx2_gx(g, 'ADBE Vector Scale', hd + '[100*g,100*g]');
        fx2_gx(g, 'ADBE Vector Rotation', hd + jzN(fx2_rs(s, i, 6) * 12) + '+u*30');
        var gg = jzGrp(G, 'glow ' + (i + 1));
        jzAddEllipse(gg, R * 0.8, R * 0.8, 0, 0); jzAddFill(gg, col, 70);
        jzGX(gg).property('ADBE Vector Position').setValue([x, y]);
        fx2_gx(gg, 'ADBE Vector Scale', hd + '[100*g,100*g]');
    }
} });

/* ---- colorBars \u2014 \u30AB\u30E9\u30FC\u30D0\u30FC: thin scheme-coloured bars sweeping across the frame at different speeds */
jzReg('fx', 'colorBars', { build: function (f, ev) {
    var W = f.W, H = f.H, sc = ev.sc, s = fx2_s(ev), vert = fx2_r(s, 1) < (W >= H ? 0.35 : 0.15), dir = fx2_r(s, 2) < 0.5 ? 1 : -1;
    var src = [sc.accent, sc.accent2, sc.ghostA, sc.ghostB, sc.fg], cols = [], i;
    for (i = 0; i < src.length; i++) if (src[i] && jzContrast(src[i], sc.bg) > 1.35) cols.push(src[i]);
    if (!cols.length) cols = [fx2_ink(sc)];
    var Lx = vert ? W : H, Mx = vert ? H : W, n = 4 + (fx2_h(s, 3) % 4);
    var S = jzEvShape(f.comp, 'JZ FX colorBars', ev.t, ev.dur);
    for (i = 0; i < n; i++) {
        var th = Math.max(2, Lx * fx2_rr(0.008, 0.045, s, i, 4)), d = fx2_r(s, i, 5) * 0.4, sp = fx2_rr(0.9, 1.5, s, i, 6);
        var part = fx2_r(s, i, 7) < 0.4, m0 = part ? Mx * fx2_rr(0, 0.5, s, i, 8) : 0, mw = part ? Mx * fx2_rr(0.3, 0.6, s, i, 9) : Mx;
        var g = jzGrp(S, 'bar ' + (i + 1));
        if (vert) jzAddRect(g, th, mw, 0, 0, 0); else jzAddRect(g, mw, th, 0, 0, 0);
        jzAddFill(g, cols[(i + (fx2_h(s, 10) % cols.length)) % cols.length], 92);
        var hd = fx2_head(ev) + 'var u=cl((p-' + jzN(d) + ')/' + jzN(1 - d) + '*' + jzN(sp) + ');var ps=' + jzN(-th) + '+' + jzN(Lx + 2 * th) + '*u;' + (dir < 0 ? 'ps=' + jzN(Lx) + '-ps;' : '') + '\n';
        fx2_gx(g, 'ADBE Vector Position', hd + (vert ? '[ps,' + jzN(m0 + mw / 2) + ']' : '[' + jzN(m0 + mw / 2) + ',ps]'));
        fx2_gx(g, 'ADBE Vector Group Opacity', hd + '(u<=0||u>=1)?0:100');
    }
} });

/* ---- zoomStutter \u2014 \u30BA\u30FC\u30E0\u9023\u6253: three hard zoom steps, each with its own slight focus shift */
jzReg('fx', 'zoomStutter', { build: function (f, ev) {
    var W = f.W, H = f.H, s = fx2_s(ev), a = fx2_amp(ev, 0.5, 1.3), z0 = (0.035 + 0.012 * fx2_r(s, 1)) * a, j, cx = [], cy = [];
    for (j = 0; j < 3; j++) { cx.push(jzN(W * (0.5 + fx2_rs(s, 2, j) * 0.04))); cy.push(jzN(H * (0.5 + fx2_rs(s, 3, j) * 0.04))); }
    var hd = fx2_head(ev) + 'var j=Math.min(2,Math.floor(p*3)),CX=[' + cx.join(',') + '],CY=[' + cy.join(',') + '];\n';
    var L = jzAdjLayer(f.comp, 'JZ FX zoomStutter', ev.t, ev.dur);
    var tr = fx2_xform(L, 'JZ Zoom Stutter', null);
    jzEX(tr, 1, hd + '[CX[j],CY[j]]'); jzEX(tr, 2, hd + '[CX[j],CY[j]]'); jzEX(tr, 4, hd + '100*(1+(j+1)*' + jzN(z0) + ')');
} });

// ================================================================ graphic inversions / stylise
/* ---- negativeRing \u2014 \u53CD\u8EE2\u30EA\u30F3\u30B0: an inverted ring (circle or diamond) bursts out from the centre, a thinner echo ring follows */
jzReg('fx', 'negativeRing', { build: function (f, ev) {
    var W = f.W, H = f.H, s = fx2_s(ev), M = Math.min(W, H), dia = fx2_r(s, 1) < 0.35, a = fx2_amp(ev, 0.6, 1.3);
    var cx = W / 2 + fx2_rs(s, 2) * W * 0.05, cy = H / 2 + fx2_rs(s, 3) * H * 0.05, Rm = fx2_hyp(W, H) * (dia ? 0.75 : 0.56);
    var S = jzEvShape(f.comp, 'JZ FX negativeRing', ev.t, ev.dur), rings = [['p*1.05+0.04', M * 0.16 * a], ['(p-0.22)*1.35', M * 0.05 * a]], k, j;
    S.blendingMode = BlendingMode.DIFFERENCE;
    for (k = rings.length - 1; k >= 0; k--) {
        var hd = fx2_head(ev) + 'var q=' + rings[k][0] + ',on=q>0&&q<1,r=on?oc(q)*' + jzN(Rm) + ':0,r0=on?Math.max(0,r-' + jzN(rings[k][1]) + '*(1-0.55*q)):0;\n';
        var g = jzGrp(S, 'ring ' + (k + 1));
        for (j = 0; j < 2; j++) {
            var rx = j ? 'r0' : 'r';
            if (dia) {
                var st = jzVecs(g).addProperty('ADBE Vector Shape - Star');
                st.property('ADBE Vector Star Type').setValue(2); st.property('ADBE Vector Star Points').setValue(4);
                jzSetExpr(st.property('ADBE Vector Star Outer Radius'), hd + 'Math.max(0.01,' + rx + ')');
            } else {
                var el = jzAddEllipse(g, 10, 10, 0, 0);
                jzSetExpr(el.property('ADBE Vector Ellipse Size'), hd + '[2*' + rx + ',2*' + rx + ']');
            }
        }
        fx2_eo(jzAddFill(g, '#FFFFFF'));
        jzGX(g).property('ADBE Vector Position').setValue([cx, cy]);
        fx2_gx(g, 'ADBE Vector Group Opacity', hd + 'on?100:0');
    }
} });

/* ---- edgeDetect \u2014 \u8F2A\u90ED\u62BD\u51FA: neon outlines on a darkened ground (dark schemes) / an ink line drawing on the paper (light ones) */
jzReg('fx', 'edgeDetect', { build: function (f, ev) {
    var sc = ev.sc, dk = fx2_dk(sc);
    var L = jzAdjLayer(f.comp, 'JZ FX edgeDetect', ev.t, ev.dur);
    var gb = jzEffect(L, 'ADBE Gaussian Blur 2', 'JZ Edge Denoise'); jzEP(gb, 1, 2.5 * f.u); jzEP(gb, 3, 1);
    var fe = jzEffect(L, 'ADBE Find Edges', 'JZ Edges'); jzEP(fe, 1, dk ? 1 : 0);
    var tn = jzEffect(L, 'ADBE Tint', 'JZ Edge Colours');
    if (dk) { jzEP(tn, 1, jzHex(jzMixHex(sc.bg, '#000000', 0.6))); jzEP(tn, 2, jzHex(jzMixHex(fx2_vivid(sc), '#FFFFFF', 0.45))); }
    else { jzEP(tn, 1, jzHex(fx2_ext([sc.fg, sc.ink, '#111111'], true))); jzEP(tn, 2, jzHex(sc.bg)); }
    jzEP(tn, 3, 100);
    jzSetExpr(jzXf(L, 'ADBE Opacity'), fx2_head(ev) + '100*ahr(p,0.1,0.72)');
} });

/* ---- shatter \u2014 \u30AC\u30E9\u30B9\u5272\u308C: cracks on the frame, then the shards slip apart and knit back (rings x alternating sectors) */
jzReg('fx', 'shatter', { build: function (f, ev) {
    var W = f.W, H = f.H, sc = ev.sc, s = fx2_s(ev), M = Math.min(W, H), dk = fx2_dk(sc), a = fx2_amp(ev, 0.6, 1.3), PI = Math.PI;
    var px = W * (0.5 + fx2_rs(s, 1) * 0.15), py = H * (0.5 + fx2_rs(s, 2) * 0.12), n = 9 + (fx2_h(s, 3) % 4), i, j, par;
    var radii = [0, M * fx2_rr(0.1, 0.17, s, 4), M * fx2_rr(0.32, 0.45, s, 5), fx2_hyp(W, H) * 1.1], ang = [];
    for (i = 0; i < n; i++) ang.push((i + fx2_rs(s, i, 6) * 0.35) / n * 2 * PI);
    function V(i2, j2) {
        if (!j2) return [px, py];
        var q = ang[i2 % n] + fx2_rs(s, i2 % n, j2, 7) * 0.12, r = radii[j2] * (j2 < 3 ? 1 + fx2_rs(s, i2 % n, j2, 8) * 0.18 : 1);
        return [px + Math.cos(q) * r, py + Math.sin(q) * r];
    }
    function shard(i2, j2) { return j2 ? [V(i2, j2), V(i2 + 1, j2), V(i2 + 1, j2 + 1), V(i2, j2 + 1)] : [V(i2, 0), V(i2, 1), V(i2 + 1, 1)]; }
    var hd = fx2_head(ev) + 'var u=(p-0.125)/0.875,sep=u<=0?0:(u<0.28?oc(u/0.28):1-ioc((u-0.28)/0.72)),crack=u<=0?1:Math.max(0,1-u*1.4);\n';
    // content of each ring slips outwards (+ a small twist, alternating per sector) inside the static shard outlines
    var dist = [radii[1] * 0.6, (radii[1] + radii[2]) / 2, radii[2] * 1.5], rot = [2.5, 1.5, 0.8];
    for (j = 0; j < 3; j++) for (par = 0; par < 2; par++) {
        var L = jzAdjLayer(f.comp, 'JZ FX shatter ring ' + (j + 1), ev.t, ev.dur), cnt = 0;
        for (i = par; i < n; i += 2) { fx2_maskPts(L, shard(i, j)); cnt++; }
        if (!cnt) { L.remove(); continue; }
        var dj = M * (dk ? 0.04 : 0.028) * a * (0.75 + 0.3 * j);
        var tr = fx2_xform(L, 'JZ Shard Slip', [px, py]);
        jzEX(tr, 4, hd + '100*(1+sep*' + jzN(dj / dist[j]) + ')');
        jzEX(tr, 8, hd + 'sep*' + jzN(rot[j] * (par ? 1 : -1) * (fx2_rs(s, j, 10) < 0 ? -1 : 1)));
    }
    // crack lines
    var S = jzEvShape(f.comp, 'JZ FX shatter cracks', ev.t, ev.dur), g = jzGrp(S, 'cracks');
    for (i = 0; i < n; i++) for (j = 0; j < 3; j++) fx2_path(g, shard(i, j), null, null, true);
    // (each stroke is fully configured before the next one is added \u2014 adding gk invalidates sk in AE)
    var sk = jzAddStroke(g, dk ? '#FFFFFF' : fx2_ext([sc.fg, sc.ink], true), Math.max(1.5, M * 0.0025), 100);
    jzSetExpr(sk.property('ADBE Vector Stroke Opacity'), hd + (dk ? '75' : '60') + '*Math.max(crack,sep*0.6)');
    try { sk.property('ADBE Vector Stroke Line Join').setValue(2); } catch (e1) {}
    // the dark gaps that open between the drifting shards (the browser's background showing through)
    var gk = jzAddStroke(g, dk ? '#000000' : jzMixHex(sc.bg, '#000000', 0.55), 1, 100);
    jzSetExpr(gk.property('ADBE Vector Stroke Width'), hd + 'Math.max(0.1,sep*' + jzN(M * (dk ? 0.04 : 0.028) * a * 0.7) + ')');
    jzSetExpr(gk.property('ADBE Vector Stroke Opacity'), hd + 'sep>0.01?100:0');
    try { gk.property('ADBE Vector Stroke Line Join').setValue(2); } catch (e2) {}
    // impact flash
    var F = jzEvSolid(f.comp, 'JZ FX shatter flash', '#FFFFFF', ev.t, ev.dur);
    fx2_maskEll(F, px, py, M * 0.14, M * 0.14, { feather: M * 0.2 });
    F.blendingMode = dk ? BlendingMode.SCREEN : BlendingMode.NORMAL;
    jzSetExpr(jzXf(F, 'ADBE Opacity'), hd + 'p<0.1875?85*(1-p/0.1875):0');
} });

/* ---- defocus \u2014 \u30D4\u30F3\u307C\u3051: the picture drops out of focus (peak at the cut) and snaps back */
jzReg('fx', 'defocus', { build: function (f, ev) {
    var hd = fx2_head(ev, ',AC=' + jzN(fx2_amp(ev, 0.6, 1.2))) + 'var amt=(p<0.5?ioc(p/0.5):1-ioc((p-0.5)/0.5))*AC;\n';
    var L = jzAdjLayer(f.comp, 'JZ FX defocus', ev.t, ev.dur);
    var gb = jzEffect(L, 'ADBE Gaussian Blur 2', 'JZ Defocus'); jzEX(gb, 1, hd + 'amt*thisComp.height/27'); jzEP(gb, 3, 1);
    var tr = fx2_xform(L, 'JZ Defocus Breathe', null); jzEX(tr, 4, hd + '100*(1+0.025*amt)');
    jzSetExpr(jzXf(L, 'ADBE Opacity'), hd + '100*Math.min(1,amt*1.8)');
} });

/* ---- snapshot \u2014 \u30B7\u30E3\u30C3\u30BF\u30FC: white flash, the frame becomes a tilted instant photo on a dimmed backdrop, then zooms back */
jzReg('fx', 'snapshot', { build: function (f, ev) {
    var W = f.W, H = f.H, sc = ev.sc, s = fx2_s(ev), M = Math.min(W, H), D = fx2_hyp(W, H);
    var RT = (fx2_r(s, 1) < 0.5 ? -1 : 1) * (2 + 2.5 * fx2_r(s, 2));
    var hd = fx2_head(ev, ',RT=' + jzN(RT) + ',MM=' + jzN(M)) + 'var env=p<0.16?ob(p/0.16,1.4):(p>0.8?1-ioc((p-0.8)/0.2):1);' +
        'var W=thisComp.width,H=thisComp.height,z=1-0.15*env,rot=RT*env,bw=MM*0.024*cl(env),fw=W*z,fh=H*z,bb=bw*3.2;\n';
    var L = jzAdjLayer(f.comp, 'JZ FX snapshot', ev.t, ev.dur);
    var tr = fx2_xform(L, 'JZ Snapshot Photo', null); jzEX(tr, 4, hd + '100*z'); jzEX(tr, 8, hd + 'rot');
    // print border, drop shadow and dimmed backdrop around the photo (even-odd fills with the photo cut out)
    var S = jzEvShape(f.comp, 'JZ FX snapshot print', ev.t, ev.dur), g, r, fl;
    jzXf(S, 'ADBE Position').setValue([W / 2, H / 2]);
    jzSetExpr(jzXf(S, 'ADBE Rotate Z'), hd + 'rot');
    var OUT = '[fw+2*bw,fh+bw+bb]', OUTP = '[0,(bb-bw)/2]';
    g = jzGrp(S, 'border');
    r = jzAddRect(g, 10, 10, 0, 0, 0); jzSetExpr(r.property('ADBE Vector Rect Size'), hd + OUT); jzSetExpr(r.property('ADBE Vector Rect Position'), hd + OUTP);
    r = jzAddRect(g, 10, 10, 0, 0, 0); jzSetExpr(r.property('ADBE Vector Rect Size'), hd + '[fw,fh]');
    fx2_eo(jzAddFill(g, '#F7F5F0'));
    g = jzGrp(S, 'shadow');
    r = jzAddRect(g, 10, 10, 0, 0, 0); jzSetExpr(r.property('ADBE Vector Rect Size'), hd + OUT); jzSetExpr(r.property('ADBE Vector Rect Position'), hd + '[MM*0.012,(bb-bw)/2+MM*0.02]');
    r = jzAddRect(g, 10, 10, 0, 0, 0); jzSetExpr(r.property('ADBE Vector Rect Size'), hd + OUT); jzSetExpr(r.property('ADBE Vector Rect Position'), hd + OUTP);
    fl = fx2_eo(jzAddFill(g, '#000000')); jzSetExpr(fl.property('ADBE Vector Fill Opacity'), hd + '30*cl(env)');
    g = jzGrp(S, 'backdrop');
    jzAddRect(g, D * 2.2, D * 2.2, 0, 0, 0);
    r = jzAddRect(g, 10, 10, 0, 0, 0); jzSetExpr(r.property('ADBE Vector Rect Size'), hd + OUT); jzSetExpr(r.property('ADBE Vector Rect Position'), hd + OUTP);
    fl = fx2_eo(jzAddFill(g, jzMixHex(sc.bg, '#000000', fx2_dk(sc) ? 0.5 : 0.45))); jzSetExpr(fl.property('ADBE Vector Fill Opacity'), hd + '100*cl(env*1.2)');
    // shutter flash
    var F = jzEvSolid(f.comp, 'JZ FX snapshot flash', '#FFFFFF', ev.t, ev.dur);
    jzSetExpr(jzXf(F, 'ADBE Opacity'), hd + 'p<0.22?90*(1-p/0.22):0');
} });

/* ---- squash \u2014 \u4F38\u7E2E: elastic squash & stretch of the whole frame (damped spring) */
jzReg('fx', 'squash', { build: function (f, ev) {
    var W = f.W, H = f.H, s = fx2_s(ev), hor = fx2_r(s, 1) < 0.6;
    var hd = fx2_head(ev, ',AC=' + jzN(fx2_amp(ev, 0.5, 1.3))) + 'var e=Math.cos(p*Math.PI*2.5)*Math.exp(-1.8*p)*(1-Math.pow(p,6)),a=0.17*AC*e;' +
        'var sx=' + (hor ? '1+a' : '1-a*0.6') + ',sy=' + (hor ? '1-a*0.6' : '1+a') + ',W=thisComp.width,H=thisComp.height,gx=Math.max(0,(W-W*sx)/2)+1,gy=Math.max(0,(H-H*sy)/2)+1;\n';
    var L = jzAdjLayer(f.comp, 'JZ FX squash', ev.t, ev.dur);
    var tr = fx2_xform(L, 'JZ Squash', null); jzEP(tr, 3, 0); jzEX(tr, 4, hd + '100*sy'); jzEX(tr, 5, hd + '100*sx');
    // the browser paints the background first: bg-coloured margins where the squashed frame leaves the edges
    var S = jzEvShape(f.comp, 'JZ FX squash margins', ev.t, ev.dur), g = jzGrp(S, 'margins'), k, r;
    var geo = [['[gx,H+4]', '[gx/2,H/2]'], ['[gx,H+4]', '[W-gx/2,H/2]'], ['[W+4,gy]', '[W/2,gy/2]'], ['[W+4,gy]', '[W/2,H-gy/2]']];
    for (k = 0; k < 4; k++) { r = jzAddRect(g, 10, 10, 0, 0, 0); jzSetExpr(r.property('ADBE Vector Rect Size'), hd + geo[k][0]); jzSetExpr(r.property('ADBE Vector Rect Position'), hd + geo[k][1]); }
    jzAddFill(g, ev.sc.bg);
} });

/* ---- scanBar \u2014 \u30B9\u30AD\u30E3\u30F3: a glowing bar sweeps the frame; what it has not reached is dimmed, rows just behind it jitter */
jzReg('fx', 'scanBar', { build: function (f, ev) {
    var W = f.W, H = f.H, sc = ev.sc, s = fx2_s(ev), dk = fx2_dk(sc), down = fx2_r(s, 1) < 0.7, bh = H * 0.05, band = Math.max(3, Math.round(H * 0.035));
    var col = dk ? jzMixHex(fx2_vivid(sc), '#FFFFFF', 0.4) : fx2_vivid(sc), t1 = fx2_t1(ev);
    var hd = fx2_head(ev, ',BH=' + jzN(bh) + ',HH=' + jzN(H) + ',BD=' + band + ',SD=' + (s % 9973)) + 'var yy=-BH+p*(HH+2*BH),y=' + (down ? 'yy' : 'HH-yy') + ';\n';
    // the jittering rows behind the bar: a thin adjustment strip that follows it
    // (full-frame adjustment with a band mask, moved along: its effect stays limited to the band)
    var J = jzAdjLayer(f.comp, 'JZ FX scanBar jitter', ev.t, ev.dur);
    fx2_maskRect(J, -4, H / 2 - band / 2, W + 4, H / 2 + band / 2);
    jzSetExpr(jzXf(J, 'ADBE Position'), hd + '[' + jzN(W / 2) + ',Math.max(BD/2,Math.min(HH-BD/2,' + (down ? 'y-BD/2' : 'y+BD/2') + '))]');
    var ww = jzEffect(J, 'ADBE Wave Warp', 'JZ Scan Jitter');
    jzEP(ww, 1, 2); jzEP(ww, 3, Math.max(2, 2 * band / 3)); jzEP(ww, 4, 0); jzEP(ww, 5, 0); jzEP(ww, 6, 1);
    jzEX(ww, 2, hd + 'seedRandom(SD+ST*3,true);random(0.3,1)*' + jzN(W * 0.012));
    jzEX(ww, 7, hd + 'seedRandom(SD+ST*3+1,true);random()<0.5?0:180');
    // dimmed unscanned part + the bright line
    var S = jzEvShape(f.comp, 'JZ FX scanBar', ev.t, ev.dur), g, r;
    g = jzGrp(S, 'line'); r = jzAddRect(g, W + 4, Math.max(2, H * 0.003), 0, 0, 0); jzAddFill(g, dk ? '#FFFFFF' : col, 90);
    fx2_gx(g, 'ADBE Vector Position', hd + '[' + jzN(W / 2) + ',y]');
    // (the rect is configured before the fill is added \u2014 adding the fill invalidates r in AE)
    g = jzGrp(S, 'dim'); r = jzAddRect(g, 10, 10, 0, 0, 0);
    if (down) { jzSetExpr(r.property('ADBE Vector Rect Size'), hd + '[' + jzN(W + 4) + ',Math.max(0,HH-Math.max(0,y))]'); jzSetExpr(r.property('ADBE Vector Rect Position'), hd + '[' + jzN(W / 2) + ',(Math.max(0,y)+HH)/2]'); }
    else { jzSetExpr(r.property('ADBE Vector Rect Size'), hd + '[' + jzN(W + 4) + ',Math.max(0,Math.min(HH,y))]'); jzSetExpr(r.property('ADBE Vector Rect Position'), hd + '[' + jzN(W / 2) + ',Math.max(0,Math.min(HH,y))/2]'); }
    jzAddFill(g, dk ? '#000000' : sc.bg, dk ? 60 : 70);
    // glow around the line
    var G = fx2_solid(f, 'JZ FX scanBar glow', col, W, bh * 2, ev.t, t1);
    fx2_maskRect(G, -bh, bh * 0.55, W + bh, bh * 1.45, { feather: bh * 0.9 });
    jzSetExpr(jzXf(G, 'ADBE Position'), hd + '[' + jzN(W / 2) + ',y]');
    jzXf(G, 'ADBE Opacity').setValue(dk ? 55 : 40);
    G.blendingMode = dk ? BlendingMode.SCREEN : BlendingMode.MULTIPLY;
} });

/* ---- loopScroll \u2014 \u6A2A\u30EB\u30FC\u30D7: the whole frame scrolls one full width (wrapping) with motion blur, ending where it started */
jzReg('fx', 'loopScroll', { build: function (f, ev) {
    var W = f.W, H = f.H, s = fx2_s(ev), vert = W < H ? fx2_r(s, 1) < 0.6 : fx2_r(s, 1) < 0.2, dir = fx2_r(s, 2) < 0.5 ? 1 : -1, Lx = vert ? H : W;
    var hd = fx2_head(ev, ',LX=' + jzN(Lx) + ',DIR=' + dir) + 'var o=ioc(p)*LX*DIR,v=p<0.5?12*p*p:12*(1-p)*(1-p),bl=LX*0.05*v/3;\n';
    var L = jzAdjLayer(f.comp, 'JZ FX loopScroll', ev.t, ev.dur);
    var of = jzEffect(L, 'ADBE Offset', 'JZ Loop Scroll');
    jzEX(of, 1, hd + (vert ? '[' + jzN(W / 2) + ',' + jzN(H / 2) + '+o]' : '[' + jzN(W / 2) + '+o,' + jzN(H / 2) + ']'));
    var mb = jzEffect(L, 'ADBE Motion Blur', 'JZ Loop Blur'); jzEP(mb, 1, vert ? 0 : 90); jzEX(mb, 2, hd + 'bl');
} });

// ================================================================ pack horror part 1 (AE port of the horror set, layouts 1-6)
// hrFlashlight, hrDoorGap, hrWallScrawl, hrCctv, hrOuija, hrMissing + the helpers shared by every horror pack file (ahr_)
//
// Techniques:
// - darkness with a moving hole = a big solid with an inverted, feathered mask, moved / scaled by expressions
// - glyph centres of a laid-out text layer are estimated from its measured width (ahr_slots), exact enough to aim
//   beams, planchettes and labels at single glyphs
// - rows of many little glyphs (board letters, wall writing) are ONE text layer each, arranged with per-glyph animators

/* ================================================================ shared helpers (ahr_) */
// the browser's deterministic hash J.h / J.r / J.rs / J.rr (same numbers as the web frame)
function ahr_h(a, b, c, d, e) {
    var h = 0x9e3779b9 ^ (a | 0);
    h = jzImul(h ^ (h >>> 16), 0x85ebca6b);
    h = (h + jzImul((b | 0) + 0x632be5ab, 0xc2b2ae35)) | 0;
    h = jzImul(h ^ (h >>> 13), 0xc2b2ae35);
    h = (h + jzImul((c | 0) + 0x5bd1e995, 0x27d4eb2f)) | 0;
    h = jzImul(h ^ (h >>> 15), 0x165667b1);
    h = (h + jzImul((d | 0) + 0x1b873593, 0x85ebca6b)) | 0;
    h = jzImul(h ^ (h >>> 16), 0x27d4eb2f);
    h = (h + jzImul((e | 0) + 0x68e31da4, 0x9e3779b1)) | 0;
    h ^= h >>> 15; h = jzImul(h, 0x2c1b3c6d); h ^= h >>> 12; h = jzImul(h, 0x297a2d39); h ^= h >>> 15;
    return h >>> 0;
}
function ahr_r(a, b, c, d, e) { return ahr_h(a, b, c, d, e) / 4294967296; }
function ahr_rs(a, b, c, d, e) { return ahr_r(a, b, c, d, e) * 2 - 1; }
function ahr_rr(lo, hi, a, b, c, d, e) { return lo + (hi - lo) * ahr_r(a, b, c, d, e); }
function ahr_noise1(x, seed) { var i = Math.floor(x), f = x - i, u = f * f * (3 - 2 * f); return jzLerp(ahr_rs(seed, i), ahr_rs(seed, i + 1), u); }
// colours (same rules as the browser pack)
function ahr_dark(c) { return jzLum(c) < 0.45; }
function ahr_light(sc) { return jzLum(sc.fg) > jzLum(sc.bg) ? sc.fg : sc.bg; }
function ahr_darkOf(sc) { return jzLum(sc.fg) > jzLum(sc.bg) ? sc.bg : sc.fg; }
function ahr_night(sc) { return jzMixHex(sc.bg, '#000000', ahr_dark(sc.bg) ? 0.72 : 0.9); }
function ahr_onCol(sc, fill) {
    var c = [sc.bg, sc.fg, sc.ink, sc.sub, sc.accent], best = null, bv = 0, i;
    for (i = 0; i < c.length; i++) { if (!c[i] || String(c[i]).toUpperCase() === String(fill).toUpperCase()) continue; var k = jzContrast(c[i], fill); if (k > bv) { bv = k; best = c[i]; } }
    return bv >= 2.6 ? best : (jzLum(fill) > 0.5 ? '#111111' : '#FFFFFF');
}
function ahr_px(ctx) { return jzU(ctx) / 1080; }                  // one browser design px in comp px
// fake date / time strings from the cut seed (ASCII)
function ahr_date(seed, sep) { var y = 1987 + (ahr_h(seed, 1) % 19), m = 1 + (ahr_h(seed, 2) % 12), d = 1 + (ahr_h(seed, 3) % 28); return y + sep + jzPad(m, 2) + sep + jzPad(d, 2); }
function ahr_sec0(seed) { return 2 * 3600 + (ahr_h(seed, 4) % 7200); }
function ahr_clock(seed, t) { var s0 = ahr_sec0(seed) + Math.floor(Math.max(0, t)); return jzPad(Math.floor(s0 / 3600) % 24, 2) + ':' + jzPad(Math.floor(s0 / 60) % 60, 2) + ':' + jzPad(s0 % 60, 2); }
// source-text expression of a running clock (browser fakeTime(seed, lt))
function ahr_clockExpr(seed, prefix) {
    return 'function p2(n){return (n<10?"0":"")+n;}var s0=' + ahr_sec0(seed) + '+Math.floor(Math.max(0,time));"' + (prefix || '') + '"+p2(Math.floor(s0/3600)%24)+":"+p2(Math.floor(s0/60)%60)+":"+p2(s0%60)';
}
// extra easings for expressions: ios inOutSine, ioc inOutCubic, sm smoothstep, nz smooth value noise -1..1 (browser noise1 look-alike)
var AHR_FNS = 'function ios(x){x=cl(x);return -(Math.cos(Math.PI*x)-1)/2;}function ioc(x){x=cl(x);return x<0.5?4*x*x*x:1-Math.pow(-2*x+2,3)/2;}' +
    'function sm(a,b,x){x=cl((x-a)/(b-a));return x*x*(3-2*x);}function nz(x,s){return Math.sin(x*1.13+s*1.7)*0.55+Math.sin(x*2.31+s*3.1)*0.3+Math.sin(x*4.7+s*0.9)*0.15;}\n';
function ahr_TH(ctx) { return jzTH(ctx) + AHR_FNS; }
function ahr_arr(a) { var s = [], i; for (i = 0; i < a.length; i++) s.push(jzN(a[i])); return '[' + s.join(',') + ']'; }
function ahr_strs(a) { var s = [], i; for (i = 0; i < a.length; i++) s.push('"' + String(a[i]).replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"'); return '[' + s.join(',') + ']'; }
// opacity follows the cut: fade in over `len` from `delay`, times `k`, out with the cut's exit
function ahr_fade(ctx, L, delay, len, k) { jzSetExpr(jzXf(L, 'ADBE Opacity'), jzTH(ctx) + 'value*' + jzN(k == null ? 1 : k) + '*oc((time-' + jzN(delay || 0) + ')/' + jzN(Math.max(0.01, len || 0.3)) + ')*K'); }
// a text layer fitted into maxW x maxH with a fixed leading (lead x size), so glyph rows can be located later
function ahr_fit(ctx, text, o) {
    var lead = o.lead || 1.2;
    var L = jzText(ctx, text, { font: o.font, size: 100, color: o.color, x: o.x, y: o.y, track: o.track || 0, align: o.align, leading: 100 * lead, name: o.name });
    var r = jzRect(L), k = Math.min((o.maxW || 1e6) / Math.max(1, r.width), (o.maxH || 1e6) / Math.max(1, r.height));
    var s = 100 * k; if (o.maxSize) s = Math.min(s, o.maxSize); s = Math.max(1, s);
    jzTextDoc(L, function (td) { td.fontSize = s; try { td.autoLeading = false; td.leading = s * lead; } catch (e) {} });
    jzAnchor(L, o.align);
    jzXf(L, 'ADBE Position').setValue([o.x, o.y]);
    return L;
}
// advance of one glyph in em (estimate)
function ahr_cw(ch) {
    if (ch === ' ') return 0.3;
    var c = ch.charCodeAt(0);
    if (c < 0x2000) { if (/[A-Z]/.test(ch)) return 0.68; if (/[a-z]/.test(ch)) return 0.55; if (/[0-9]/.test(ch)) return 0.6; return 0.4; }
    if (c >= 0xFF61 && c <= 0xFF9F) return 0.5;
    return 1;
}
// estimated centres of every glyph of a text layer in comp px, in textIndex order: [{x, y, sp (space), ln (line), w}]
function ahr_slots(L) {
    var td = L.property('ADBE Text Properties').property('ADBE Text Document').value;
    var text = String(td.text), size = td.fontSize, tr = (td.tracking || 0) / 1000, lead = size * 1.2;
    try { if (!td.autoLeading && td.leading) lead = td.leading; } catch (e0) {}
    var lines = text.split(/\r\n|\r|\n/), nl = lines.length, wl = [], cs = [], i, j, mx = 0;
    for (i = 0; i < nl; i++) {
        var a = jzChars(lines[i]), w = 0, q = [];
        for (j = 0; j < a.length; j++) { var cw = (ahr_cw(a[j]) + tr) * size; q.push(cw); w += cw; }
        cs.push(a); wl.push(q); mx = Math.max(mx, w);
    }
    var r = jzRect(L), an = jzXf(L, 'ADBE Anchor Point').value, p = jzXf(L, 'ADBE Position').value, sc = jzXf(L, 'ADBE Scale').value;
    var k = mx > 0 ? r.width / mx : 1, sx = sc[0] / 100, sy = sc[1] / 100;
    var cxL = r.left + r.width / 2, cyL = r.top + r.height / 2, just = td.justification, out = [];
    for (i = 0; i < nl; i++) {
        var tot = 0; for (j = 0; j < wl[i].length; j++) tot += wl[i][j] * k;
        var x0 = just === ParagraphJustification.LEFT_JUSTIFY ? r.left : (just === ParagraphJustification.RIGHT_JUSTIFY ? r.left + r.width - tot : cxL - tot / 2);
        var yl = cyL + (i - (nl - 1) / 2) * lead, acc = x0;
        for (j = 0; j < cs[i].length; j++) {
            var gw = wl[i][j] * k, gx = acc + gw / 2 - (tr * size * k) / 2;
            acc += gw;
            out.push({ x: p[0] + (gx - an[0]) * sx, y: p[1] + (yl - an[1]) * sy, sp: cs[i][j] === ' ' || cs[i][j] === '\u3000', ln: i, w: gw * sx, ch: cs[i][j] });
        }
    }
    return out;
}
function ahr_nonSp(sl) { var o = [], i; for (i = 0; i < sl.length; i++) if (!sl[i].sp) o.push(sl[i]); return o; }
// a shape group with one path (+ stroke / fill); groups added LATER are drawn BELOW (AE draws the first group on top)
function ahr_path(S, name, pts, o) {
    var g = jzGrp(S, name);
    jzAddPath(g, pts, !!o.closed);
    if (o.stroke) { var st = jzAddStroke(g, o.stroke, o.sw || 2, o.sop); if (o.cap) try { st.property('ADBE Vector Stroke Line Cap').setValue(o.cap); } catch (e) {} }
    if (o.fill) jzAddFill(g, o.fill, o.fop);
    return g;
}
function ahr_solid(ctx, hex, name, w, h) { return ctx.comp.layers.addSolid(jzHex(hex), name, Math.max(4, Math.round(w)), Math.max(4, Math.round(h)), 1, ctx.comp.duration); }
function ahr_box(x0, y0, x1, y1) { return { x0: x0, y0: y0, x1: x1, y1: y1, cx: (x0 + x1) / 2, cy: (y0 + y1) / 2 }; }
// a rounded / plain rectangle path (for groups that also carry other paths)
function ahr_rectPts(x0, y0, x1, y1) { return [[x0, y0], [x1, y0], [x1, y1], [x0, y1]]; }

/* ================================================================ 1. hrFlashlight \u2014 \u61D0\u4E2D\u96FB\u706F */
jzReg('layout', 'hrFlashlight', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, rng.chance(0.6) ? ['serif', 'display'] : ['display'])), path: rng.pick(['read', 'read', 'search']), rk: rng.range(0.85, 1.1), dark: rng.range(0.9, 0.96), dust: rng.chance(0.7), sx: rng.range(-0.3, 0.3), sy: rng.range(-0.3, 0.3) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), s = c.seed | 0;
        var text = jzSplitLines(jzFlat(c.text), port ? 5 : 10);
        var L = ahr_fit(ctx, text, { font: jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'serif')), color: sc.fg, x: W / 2, y: H / 2, track: 0.06, lead: 1.25, maxW: W * 0.8, maxH: H * 0.42, maxSize: H * 0.24 });
        var size = jzFontSize(L), m = jzSize(L), bb = jzBB(L);
        var sl = ahr_nonSp(ahr_slots(L)), X = [], Y = [], i;
        for (i = 0; i < sl.length; i++) { X.push(sl[i].x); Y.push(sl[i].y); }
        if (!X.length) { X.push(W / 2); Y.push(H / 2); }
        jzAnimate(ctx, L, { mi: 0 });
        var R0 = Math.max(size * 0.9 * jzP(ctx, 'rk', 1), u * 0.1), Rall = Math.sqrt(m[0] * m[0] + m[1] * m[1]) * 0.55 + size * 0.5;
        var dur = c.dur, search = jzP(ctx, 'path', 'read') === 'search', t0 = search ? dur * 0.22 : 0.1, t1 = dur * 0.72;
        // the veil: a huge solid with a soft elliptic hole (radial falloff 0.32R..R), moved to the beam and scaled to its radius
        var big = Math.max(W, H) * 8, V = ahr_solid(ctx, ahr_night(sc), 'hr veil', big, big);
        V.name = 'hr veil';
        var mk = V.property('ADBE Mask Parade').addProperty('ADBE Mask Atom');
        var Rm = R0 * 0.66;
        mk.property('ADBE Mask Shape').setValue(jzCircleShape(big / 2, big / 2, Rm));
        mk.property('ADBE Mask Feather').setValue([R0 * 0.68, R0 * 0.68]);
        mk.inverted = true;
        var HD = ahr_TH(ctx) + 'var X=' + ahr_arr(X) + ',Y=' + ahr_arr(Y) + ',n=X.length,t0=' + jzN(t0) + ',t1=' + jzN(t1) + ',SZ=' + jzN(size) + ';\n';
        var pos = HD + 'var u=cl((time-t0)/Math.max(0.2,t1-t0)),bx=X[0],by=Y[0];' +
            'if(n>1){var f=u*(n-1),i=Math.min(n-2,Math.floor(f)),k=ios(f-Math.max(0,i));bx=X[i]+(X[i+1]-X[i])*k;by=Y[i]+(Y[i+1]-Y[i])*k;}' +
            (search ? 'if(time<t0){var q=ios(time/t0);var sx=' + jzN(W * (0.5 + jzP(ctx, 'sx', 0))) + '+nz(time*1.3,' + (s % 97) + ')*' + jzN(W * 0.25) + ',sy=' + jzN(H * (0.5 + jzP(ctx, 'sy', 0))) + '+nz(time*1.1,' + (s % 89 + 3) + ')*' + jzN(H * 0.2) + ';bx=sx+(bx-sx)*q*q;by=sy+(by-sy)*q*q;}' : '') +
            'bx+=(nz(time*2.2,5)*0.7+nz(time*7,6)*0.3)*SZ*0.18;by+=(nz(time*1.9,7)*0.7+nz(time*6.3,8)*0.3)*SZ*0.14;' +
            'var op=ioc((time-t1)/0.7);bx+=(' + jzN(W / 2) + '-bx)*op;by+=(' + jzN(H / 2) + '-by)*op;[bx,by]';
        jzSetExpr(jzXf(V, 'ADBE Position'), pos);
        jzSetExpr(jzXf(V, 'ADBE Scale'), HD + 'var op=ioc((time-t1)/0.7);var R=' + jzN(R0) + '+' + jzN(Math.max(0, Rall - R0)) + '*op;R*=0.3+0.7*oc(time/0.35);' +
            'posterizeTime(24);seedRandom(' + (s % 9973) + '+Math.floor(time*24),true);if(random()<0.05)R*=0.55;var k=R/' + jzN(R0) + '*100;[k,k*0.86]');
        jzSetExpr(jzXf(V, 'ADBE Opacity'), HD + jzN(jzP(ctx, 'dark', 0.93) * 100) + '*oc(time/0.25)*(1-ic(PO)*0.6)');
        jzNoGhost(V);
        // dust hanging in the beam
        if (jzP(ctx, 'dust', true)) {
            var D = jzShapeLayer(ctx, 'hr dust', W / 2, H / 2), g = jzGrp(D, 'dust');
            for (i = 0; i < 14; i++) {
                var a = ahr_r(s, i, 41) * Math.PI * 2, rr = Math.sqrt(ahr_r(s, i, 43)) * R0 * 0.75, dr = u * ahr_rr(0.0012, 0.003, s, i, 42) * 2;
                jzAddEllipse(g, dr, dr, Math.cos(a) * rr, Math.sin(a) * rr * 0.86);
            }
            jzAddFill(g, sc.fg);
            jzSetExpr(jzXf(D, 'ADBE Position'), 'thisComp.layer("hr veil").transform.position');
            jzSetExpr(jzXf(D, 'ADBE Scale'), 'var s=thisComp.layer("hr veil").transform.scale;[s[0],s[0]]');
            jzSetExpr(jzXf(D, 'ADBE Rotate Z'), 'time*9');
            jzSetExpr(jzXf(D, 'ADBE Opacity'), HD + '35*' + jzN(jzP(ctx, 'dark', 0.93)) + '*oc(time/0.25)*(1-ic(PO)*0.6)*(0.6+0.4*Math.sin(time*2))');
            jzNoGhost(D);
        }
        return bb;
    }
});

/* ================================================================ 2. hrDoorGap \u2014 \u6249\u306E\u9699\u9593 */
jzReg('layout', 'hrDoorGap', {
    plan: function (rng, cut, st) {
        var n = jzCount(cut.text);
        return { font: rng.pick(jzFontsOf(st, ['serif', 'display'])), vert: n <= 7 && !jzHasLatin(cut.text) && rng.chance(0.75), side: rng.pick([-1, 1]), pause: rng.range(0.16, 0.26), wedge: rng.chance(0.8) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, port = jzPortrait(ctx), s = c.seed | 0;
        var vert = !!jzP(ctx, 'vert', false) && !jzHasLatin(c.text), font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'serif'));
        var L = vert ? ahr_fit(ctx, jzVertical(c.text), { font: font, color: sc.fg, x: W / 2, y: H / 2, track: 0, lead: 1.08, maxW: W * 0.3, maxH: H * 0.74, maxSize: Math.min(W * 0.2, H * 0.2) })
                     : ahr_fit(ctx, jzSplitLines(jzFlat(c.text), port ? 5 : 11), { font: font, color: sc.fg, x: W / 2, y: H / 2, track: 0.08, lead: 1.2, maxW: W * 0.78, maxH: H * 0.36, maxSize: H * 0.2 });
        var size = jzFontSize(L), m = jzSize(L), bb = jzBB(L);
        jzAnimate(ctx, L, { mi: 0 });
        var full = (vert ? m[0] : m[1]) + size * 0.9, cx = W / 2, cy = H / 2, lw = Math.max(1, size * 0.02);
        var span = vert ? m[1] + size * 1.6 : m[0] + size * 1.6;      // length of the gap
        // opening: a crack, a pause, then wide enough to read; slams shut on the exit
        var G = ahr_TH(ctx) + 'var FULL=' + jzN(full) + ';var a1=cl(time/0.5),a2=cl((time-' + jzN(Math.max(0.35, c.dur * jzP(ctx, 'pause', 0.2))) + ')/0.6);' +
            'var o=0.18*oc(a1)+0.82*ioc(a2);posterizeTime(24);seedRandom(' + (s % 9973) + '+Math.floor(time*24),true);o+=random(-1,1)*0.015*(a2>0&&a2<1?1:0);o*=1-ie(PO);var g=Math.max(0,FULL*o);\n';
        var D = jzShapeLayer(ctx, 'hr door', cx, cy), gd = jzGrp(D, 'dark'), BW = W * 3, BH = H * 3;
        // four panels around the gap: two slide, two change length
        var r1 = jzAddRect(gd, vert ? BW : BW, vert ? BH : BH, 0);
        if (vert) {
            r1.property('ADBE Vector Rect Position').expression = G + '[-g/2-' + jzN(BW / 2) + ',0]';
            var r2 = jzAddRect(gd, BW, BH, 0); r2.property('ADBE Vector Rect Position').expression = G + '[g/2+' + jzN(BW / 2) + ',0]';
            var r3 = jzAddRect(gd, W, BH, 0, 0, -(span / 2 + BH / 2)); r3.property('ADBE Vector Rect Size').expression = G + '[g+2,' + jzN(BH) + ']';
            var r4 = jzAddRect(gd, W, BH, 0, 0, span / 2 + BH / 2); r4.property('ADBE Vector Rect Size').expression = G + '[g+2,' + jzN(BH) + ']';
        } else {
            r1.property('ADBE Vector Rect Position').expression = G + '[0,-g/2-' + jzN(BH / 2) + ']';
            var r5 = jzAddRect(gd, BW, BH, 0); r5.property('ADBE Vector Rect Position').expression = G + '[0,g/2+' + jzN(BH / 2) + ']';
            var r6 = jzAddRect(gd, BW, H, 0, -(span / 2 + BW / 2), 0); r6.property('ADBE Vector Rect Size').expression = G + '[' + jzN(BW) + ',g+2]';
            var r7 = jzAddRect(gd, BW, H, 0, span / 2 + BW / 2, 0); r7.property('ADBE Vector Rect Size').expression = G + '[' + jzN(BW) + ',g+2]';
        }
        jzAddFill(gd, ahr_night(sc));
        jzSetExpr(jzXf(D, 'ADBE Opacity'), jzTH(ctx) + '97*oc(time/0.2)');
        jzNoGhost(D);
        // light rims on the gap edges (+ light spilling onto the floor for the vertical door)
        var Lc = ahr_light(sc), RL = jzShapeLayer(ctx, 'hr door light', cx, cy), sides = [-1, 1], k;
        for (k = 0; k < 2; k++) {
            var gr = jzGrp(RL, 'rim' + k), sg = sides[k], rim = vert ? jzAddRect(gr, lw, span, 0) : jzAddRect(gr, span, lw, 0);
            rim.property('ADBE Vector Rect Position').expression = G + (vert ? '[' + sg + '*g/2,0]' : '[0,' + sg + '*g/2]');
            jzAddFill(gr, Lc);
            jzGX(gr).property('ADBE Vector Group Opacity').expression = G + '55*Math.min(1,g/' + jzN(size * 0.3) + ')*' + ((vert ? sg < 0 : sg > 0) ? 1 : 0.6) + '*(g>1?1:0)';
        }
        if (vert && jzP(ctx, 'wedge', true)) {
            var side = jzP(ctx, 'side', 1), gw = jzGrp(RL, 'spill'), y1 = span / 2, yb = H * 1.05 - cy, off = W * 0.08 * side;
            jzAddPath(gw, [[-full / 2, y1], [full / 2, y1], [full * 2.4 + off, yb], [-full * 1.6 + off, yb]], true);
            jzAddFill(gw, Lc);
            jzGX(gw).property('ADBE Vector Scale').expression = G + '[100*o,100]';
            jzGX(gw).property('ADBE Vector Group Opacity').expression = G + '7*Math.min(1,g/' + jzN(size) + ')';
        }
        jzNoGhost(RL);
        return bb;
    }
});

/* ================================================================ 3. hrWallScrawl \u2014 \u58C1\u306E\u843D\u66F8\u304D */
jzReg('layout', 'hrWallScrawl', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), hand: rng.chance(0.75) ? 'klee' : rng.pick(jzFontsOf(st, ['body', 'serif'])), rows: rng.int(7, 10), mainHand: rng.chance(0.4), red: rng.range(0.06, 0.16), rise: rng.range(0.55, 0.8) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, port = jzPortrait(ctx), s = c.seed | 0, i, j, q;
        var unit = jzFlat(c.text), hand = jzP(ctx, 'hand', 'klee'), font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display'));
        var mainFont = jzP(ctx, 'mainHand', false) ? hand : font;
        var L = ahr_fit(ctx, jzSplitLines(unit, port ? 5 : 10), { font: mainFont, color: sc.fg, x: W / 2, y: H / 2, track: 0.04, lead: 1.1, maxW: W * 0.8, maxH: H * 0.3, maxSize: H * 0.2 });
        var size = jzFontSize(L), mm = jzSize(L);
        var clear = { x0: W / 2 - mm[0] / 2 - size * 0.4, x1: W / 2 + mm[0] / 2 + size * 0.4, y0: H / 2 - mm[1] / 2 - size * 0.35, y1: H / 2 + mm[1] / 2 + size * 0.35 };
        var rows = jzP(ctx, 'rows', 8), rh = H / rows, fs = rh * 0.58, gs = jzGlyphs(unit), gN = Math.max(1, jzCount(unit));
        // measure one copy (and the gap of one space) in the hand font
        var M1 = jzText(ctx, unit, { font: hand, size: fs, x: 0, y: 0, track: 0.02 }), cw = jzRect(M1).width; M1.remove();
        var M2 = jzText(ctx, unit + ' ' + unit, { font: hand, size: fs, x: 0, y: 0, track: 0.02 }), spw = Math.max(fs * 0.2, jzRect(M2).width - cw * 2); M2.remove();
        var slots = [], r, k;
        for (r = 0; r < rows && slots.length < 240; r++) {
            var y = (r + 0.5) * rh + ahr_rs(s, r, 1) * rh * 0.12, x = W * 0.03 + ahr_r(s, r, 2) * fs * 2;
            for (k = 0; k < 16 && x < W * 0.97; k++) {
                var sz = fs * ahr_rr(0.75, 1.3, s, r, k, 3), w = cw * sz / fs;
                if (!(y + sz * 0.6 > clear.y0 && y - sz * 0.6 < clear.y1 && x + w > clear.x0 && x < clear.x1) && x + w < W * 1.02) slots.push({ x: x, y: y, sz: sz, w: w, r: r, k: k });
                x += w + fs * ahr_rr(0.4, 1.4, s, r, k, 4);
            }
        }
        var lim = Math.max(8, Math.min(64, Math.floor(420 / gN)));
        if (slots.length > lim) { var st2 = slots.length / lim, sl2 = []; for (i = 0; i < lim; i++) sl2.push(slots[Math.floor(i * st2)]); slots = sl2; }
        var K = slots.length, T = Math.max(0.6, c.dur * jzP(ctx, 'rise', 0.65));
        for (q = 0; q < K; q++) { slots[q].ta = T * Math.pow(q / Math.max(1, K), 0.62); slots[q].red = ahr_r(ahr_h(s, q, 9), 5) < jzP(ctx, 'red', 0.1); slots[q].q = q; }
        // segments: runs of copies in one row that are not split by the clear zone -> one text layer each
        var segs = [], cur = null;
        for (q = 0; q < K; q++) {
            var S0 = slots[q];
            if (!cur || cur.r !== S0.r || (cur.last.x < clear.x0 && S0.x > clear.x0)) { cur = { r: S0.r, items: [] }; segs.push(cur); }
            cur.items.push(S0); cur.last = S0;
        }
        var adv = cw / gN, TH0 = jzTH(ctx);
        for (i = 0; i < segs.length; i++) {
            var it = segs[i].items, str = [], offs = [], scl = [], rot = [], tms = [], red = [], nat = 0, x0 = it[0].x;
            for (q = 0; q < it.length; q++) {
                var S1 = it[q], ks = S1.sz / fs, seed = ahr_h(s, S1.q, 9);
                if (q) { str.push(' '); offs.push([0, 0]); scl.push(1); rot.push(0); tms.push(9999); red.push(false); nat += spw; }
                var gi = 0;
                for (j = 0; j < gs.length; j++) {
                    str.push(gs[j]);
                    var natX = nat + (j + 0.5) * adv, tx = (S1.x - x0) + (j + 0.5) * adv * ks;
                    offs.push([tx - natX, (S1.y - it[0].y) + ahr_rs(seed, gi, 7) * S1.sz * 0.12]);
                    scl.push(ks * (1 + ahr_rs(seed, gi, 10) * 0.12)); rot.push(ahr_rs(seed, 6) * 5 + ahr_rs(seed, gi, 8) * 9);
                    tms.push(S1.ta + 0.3 * (gs[j] === ' ' ? j : gi) / gN); red.push(S1.red);
                    if (gs[j] !== ' ') gi++;
                }
                nat += cw;
            }
            var TL = jzText(ctx, str.join(''), { font: hand, size: fs, color: sc.sub, x: x0, y: it[0].y, align: 'left', track: 0.02, name: 'hr scrawl ' + (i + 1) });
            jzCharOffsets(TL, offs, 'hr Place');
            jzCharScales(TL, scl, 'hr Size');
            jzCharRotations(TL, rot, 'hr Tilt');
            var anyRed = false; for (q = 0; q < red.length; q++) if (red[q]) anyRed = true;
            if (anyRed) jzCharColors(TL, sc.accent, red, 'hr Red');
            jzAnimator(TL, 'hr Write', [['ADBE Text Opacity', 0]], 'var a=' + ahr_arr(tms) + ';time<(a[textIndex-1]||0)?100:0');
            jzSetExpr(jzXf(TL, 'ADBE Opacity'), TH0 + '48*K');
            jzNoGhost(TL);
        }
        L.moveToBeginning();
        jzAnimate(ctx, L, { mi: 0 });
        return jzBB(L);
    }
});

/* ================================================================ 4. hrCctv \u2014 \u76E3\u8996\u30E2\u30CB\u30BF\u30FC */
jzReg('layout', 'hrCctv', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'body'])), v: rng.pick(['quad', 'quad', 'single']), act: rng.int(0, 3), cam0: rng.int(1, 12), box: rng.chance(0.7) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, port = jzPortrait(ctx), s = c.seed | 0, u = jzU(ctx), px = ahr_px(ctx), i, k;
        var mono = jzMonoF(ctx), fs = jzClamp(u * 0.022, 11 * px, 28 * px), m = u * 0.03, lw = Math.max(1, u * 0.0016);
        var feedBg = jzMixHex(ahr_darkOf(sc), '#000000', 0.35), LC = ahr_light(sc), frameC = jzMixHex(feedBg, LC, 0.5);
        var quad = jzP(ctx, 'v', 'quad') === 'quad', act = jzP(ctx, 'act', 0) % 4, cam0 = jzP(ctx, 'cam0', 1), date = ahr_date(s, '/');
        var TH0 = jzTH(ctx), feeds = [], fx0 = m, fy0 = m, fx1 = W - m, fy1 = H - m;
        if (quad) {
            var g = lw * 3, cwid = (W - m * 2 - g) / 2, chh = (H - m * 2 - g) / 2;
            for (k = 0; k < 4; k++) {
                var x0 = m + (k % 2) * (cwid + g), y0 = m + Math.floor(k / 2) * (chh + g);
                if (k === act) { fx0 = x0; fy0 = y0; fx1 = x0 + cwid; fy1 = y0 + chh; }
                else feeds.push({ x0: x0, y0: y0, x1: x0 + cwid, y1: y0 + chh, k: k });
            }
        }
        var fw = fx1 - fx0, fh = fy1 - fy0, fe;
        // feed backgrounds (+ the corridors seen from above a doorway in the empty ones)
        var F = jzShapeLayer(ctx, 'hr feeds', 0, 0);
        if (feeds.length) {
            var gc = jzGrp(F, 'corridors');
            for (i = 0; i < feeds.length; i++) {
                fe = feeds[i];
                var ccx = (fe.x0 + fe.x1) / 2 + ahr_rs(s, fe.k, 3) * (fe.x1 - fe.x0) * 0.12, ccy = (fe.y0 + fe.y1) / 2 + ahr_rs(s, fe.k, 4) * (fe.y1 - fe.y0) * 0.08, ww = (fe.x1 - fe.x0) * 0.16, hh2 = (fe.y1 - fe.y0) * 0.2;
                jzAddPath(gc, [[fe.x0, fe.y0], [ccx - ww, ccy - hh2]], false); jzAddPath(gc, [[fe.x1, fe.y0], [ccx + ww, ccy - hh2]], false);
                jzAddPath(gc, [[fe.x0, fe.y1], [ccx - ww, ccy + hh2]], false); jzAddPath(gc, [[fe.x1, fe.y1], [ccx + ww, ccy + hh2]], false);
                jzAddPath(gc, ahr_rectPts(ccx - ww, ccy - hh2, ccx + ww, ccy + hh2), true);
            }
            jzAddStroke(gc, jzMixHex(feedBg, LC, 0.25), lw, 80);
        }
        var gb = jzGrp(F, 'feeds');
        jzAddRect(gb, fw, fh, 0, fx0 + fw / 2, fy0 + fh / 2);
        for (i = 0; i < feeds.length; i++) { fe = feeds[i]; jzAddRect(gb, fe.x1 - fe.x0, fe.y1 - fe.y0, 0, (fe.x0 + fe.x1) / 2, (fe.y0 + fe.y1) / 2); }
        jzAddFill(gb, feedBg);
        jzSetExpr(jzXf(F, 'ADBE Opacity'), TH0 + '100*oc(time/0.2)');
        jzNoGhost(F);
        // TV snow: grey solids with per-pixel noise, one per feed
        var snow = [{ x0: fx0, y0: fy0, x1: fx1, y1: fy1, a: 5 }];
        for (i = 0; i < feeds.length; i++) snow.push({ x0: feeds[i].x0, y0: feeds[i].y0, x1: feeds[i].x1, y1: feeds[i].y1, a: 9 });
        for (i = 0; i < snow.length; i++) {
            var N = ahr_solid(ctx, '#808080', 'hr snow ' + (i + 1), snow[i].x1 - snow[i].x0, snow[i].y1 - snow[i].y0);
            jzXf(N, 'ADBE Position').setValue([(snow[i].x0 + snow[i].x1) / 2, (snow[i].y0 + snow[i].y1) / 2]);
            var ne = jzEffect(N, 'ADBE Noise', 'hr Snow'); jzEP(ne, 1, 100); jzEP(ne, 2, 0);
            jzSetExpr(jzXf(N, 'ADBE Opacity'), TH0 + jzN(snow[i].a) + '*oc(time/0.2)*K');
            jzNoGhost(N);
        }
        // OSD of one feed
        var osd = [];
        function camLabel(x0, y0, x1, y1, n, live, f) {
            var a = jzText(ctx, 'CAM ' + jzPad(n, 2), { font: mono, size: f, color: LC, x: x0 + f * 0.8, y: y0 + f * 1.2, align: 'left', track: 0.1 });
            ahr_fade(ctx, a, 0, 0.2, 0.85); osd.push(a);
            var b = jzText(ctx, date + ' ' + ahr_clock(s, 0), { font: mono, size: f * 0.9, color: LC, x: x1 - f * 0.8, y: y1 - f * 1.1, align: 'right', track: 0.06 });
            jzSetExpr(b.property('ADBE Text Properties').property('ADBE Text Document'), ahr_clockExpr(s, date + ' '));
            ahr_fade(ctx, b, 0, 0.2, 0.75); osd.push(b);
            if (live) {
                var rc = jzText(ctx, 'REC', { font: mono, size: f, color: LC, x: x1 - f * 3.3, y: y0 + f * 1.2, align: 'left' });
                ahr_fade(ctx, rc, 0, 0.2, 0.85); osd.push(rc);
                var dot = jzEllipseLayer(ctx, 'hr rec', x1 - f * 3.9, y0 + f * 1.2, f * 0.64, f * 0.64, jzFitContrast(sc.accent, feedBg, 3));
                jzSetExpr(jzXf(dot, 'ADBE Opacity'), TH0 + 'posterizeTime(12);(Math.floor(time*12)%4<2?100:0)*oc(time/0.2)*K');
                osd.push(dot);
            }
        }
        for (i = 0; i < feeds.length; i++) {
            fe = feeds[i];
            camLabel(fe.x0, fe.y0, fe.x1, fe.y1, cam0 + fe.k, false, fs * 0.8);
            if (ahr_r(s, fe.k, 5) < 0.35) {
                var ns = jzText(ctx, 'NO SIGNAL', { font: mono, size: fs * 1.1, color: jzMixHex(feedBg, LC, 0.6), x: (fe.x0 + fe.x1) / 2, y: (fe.y0 + fe.y1) / 2, track: 0.2 });
                jzSetExpr(jzXf(ns, 'ADBE Opacity'), TH0 + 'posterizeTime(12);(Math.floor(time*12)%6<4?80:30)*oc(time/0.2)*K');
                osd.push(ns);
            }
        }
        // the live feed with the lyric
        var text = jzSplitLines(jzFlat(c.text), quad ? (port ? 4 : 7) : (port ? 5 : 11));
        var L = ahr_fit(ctx, text, { font: jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), color: LC, x: fx0 + fw / 2, y: fy0 + fh / 2, track: 0.05, lead: 1.2, maxW: fw * 0.82, maxH: fh * 0.46, maxSize: fh * 0.3 });
        var size = jzFontSize(L), bb = jzBB(L);
        jzAnimate(ctx, L, { mi: 0 });
        // scanlines + a slow bright band
        var sp = Math.max(3 * px, fh / 90), SL = jzShapeLayer(ctx, 'hr scan', fx0 + fw / 2, fy0), gs = jzGrp(SL, 'lines');
        jzAddRect(gs, fw, sp * 0.6, 0, 0, sp * 0.3);
        var rp = jzVecs(gs).addProperty('ADBE Vector Filter - Repeater');
        rp.property('ADBE Vector Repeater Copies').setValue(Math.floor(fh / (sp * 2)));
        rp.property('ADBE Vector Repeater Transform').property('ADBE Vector Repeater Position').setValue([0, sp * 2]);
        jzAddFill(gs, '#000000', 12);
        var gband = jzGrp(SL, 'band');
        var rb = jzAddRect(gband, fw, fh * 0.05, 0, 0, fh * 0.025);
        rb.property('ADBE Vector Rect Position').expression = 'var T=time+' + jzN(c.start || 0) + ';[0,((T*0.13+' + jzN(ahr_r(s, 9)) + ')%1)*' + jzN(fh) + ']';
        jzAddFill(gband, LC, 3);
        jzSetExpr(jzXf(SL, 'ADBE Opacity'), TH0 + '100*oc(time/0.2)');
        jzNoGhost(SL);
        // motion-detection corners hunting the words
        if (jzP(ctx, 'box', true)) {
            var pd = size * 0.25, bx0 = bb.x0 - pd, by0 = bb.y0 - pd, bx1 = bb.x1 + pd, by1 = bb.y1 + pd, Lb = size * 0.3, acol = jzFitContrast(sc.accent, feedBg, 3);
            var B = jzShapeLayer(ctx, 'hr motion box', 0, 0), gm = jzGrp(B, 'corners');
            jzAddPath(gm, [[bx0, by0 + Lb], [bx0, by0], [bx0 + Lb, by0]], false); jzAddPath(gm, [[bx1 - Lb, by0], [bx1, by0], [bx1, by0 + Lb]], false);
            jzAddPath(gm, [[bx0, by1 - Lb], [bx0, by1], [bx0 + Lb, by1]], false); jzAddPath(gm, [[bx1 - Lb, by1], [bx1, by1], [bx1, by1 - Lb]], false);
            jzAddStroke(gm, acol, Math.max(1.2 * px, lw * 1.4));
            var jit = 'posterizeTime(12);seedRandom(' + (s % 9973) + '+Math.floor(time*12),true);[value[0]+random(-1,1)*' + jzN(size * 0.08) + ',value[1]+random(-1,1)*' + jzN(size * 0.08) + ']';
            jzSetExpr(jzXf(B, 'ADBE Position'), jit);
            var eIn = TH0 + 'var e=oc((time-' + jzN((c.inDur || 0.3) * 0.6) + ')/0.25)*K;';
            jzSetExpr(jzXf(B, 'ADBE Opacity'), eIn + '100*e');
            var ml = jzText(ctx, 'MOTION ' + jzPad(1 + (ahr_h(s, 23) % 9), 2), { font: mono, size: fs * 0.75, color: acol, x: bx0, y: by0 - fs * 0.7, align: 'left', track: 0.1 });
            jzSetExpr(jzXf(ml, 'ADBE Position'), jit);
            jzSetExpr(jzXf(ml, 'ADBE Opacity'), eIn + 'posterizeTime(12);100*e*(Math.floor(time*12)%3?1:0.4)');
            jzNoGhost(B); jzNoGhost(ml);
        }
        camLabel(fx0, fy0, fx1, fy1, cam0 + act, true, fs);
        var FR = jzRectLayer(ctx, 'hr frame', fx0 + fw / 2, fy0 + fh / 2, fw, fh, null, { stroke: quad ? jzFitContrast(sc.accent, feedBg, 3) : frameC, strokeW: lw * (quad ? 1.6 : 1) });
        ahr_fade(ctx, FR, 0, 0.2, quad ? 0.85 : 0.5);
        osd.push(FR);
        for (i = 0; i < osd.length; i++) jzNoGhost(osd[i]);
        return bb;
    }
});

/* ================================================================ 5. hrOuija \u2014 \u964D\u970A\u76E4 */
var AHR_KANA = '\u3042\u3044\u3046\u3048\u304A\u304B\u304D\u304F\u3051\u3053\u3055\u3057\u3059\u305B\u305D\u305F\u3061\u3064\u3066\u3068\u306A\u306B\u306C\u306D\u306E\u306F\u3072\u3075\u3078\u307B\u307E\u307F\u3080\u3081\u3082\u3084\u3086\u3088\u3089\u308A\u308B\u308C\u308D\u308F\u3092\u3093';
var AHR_ABC = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
// one text layer holding several glyph rows, each glyph moved to its own place: items [{ch, x, y, rot}] (one glyph per line)
function ahr_glyphCloud(ctx, items, o) {
    var chs = [], i, lead = o.size * 1.3;
    for (i = 0; i < items.length; i++) chs.push(items[i].ch);
    var L = jzText(ctx, chs.join('\r'), { font: o.font, size: o.size, color: o.color, x: o.x, y: o.y, track: o.track || 0, leading: lead, name: o.name });
    var n = items.length, offs = [], rots = [], anyRot = false, j, gl;
    for (i = 0; i < n; i++) {
        gl = jzChars(items[i].ch);
        for (j = 0; j < gl.length; j++) {    // every glyph of a line moves with its line (lines are centred)
            offs.push([items[i].x - o.x, items[i].y - (o.y + (i - (n - 1) / 2) * lead)]);
            rots.push(items[i].rot || 0); if (items[i].rot) anyRot = true;
        }
    }
    jzCharOffsets(L, offs, 'hr Place');
    if (anyRot) jzCharRotations(L, rots, 'hr Tilt');
    return L;
}
jzReg('layout', 'hrOuija', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['serif', 'display'])), fontB: rng.pick(jzFontsOf(st, ['serif'])), jit: rng.range(0.45, 0.9), latin: jzHasLatin(cut.text) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, port = jzPortrait(ctx), s = c.seed | 0, u = jzU(ctx), i, r;
        var lw = Math.max(1, u * 0.0018), fontB = jzP(ctx, 'fontB', jzSerifF(ctx)), latin = !!jzP(ctx, 'latin', jzHasLatin(c.text)), TH0 = ahr_TH(ctx);
        var bw = Math.min(W * 0.88, H * (port ? 0.62 : 1.2)), bh = bw * (port ? 0.72 : 0.52), bx = W / 2, by = port ? H * 0.34 : H * 0.36;
        var plate = jzMixHex(sc.bg, sc.fg, ahr_dark(sc.bg) ? 0.07 : 0.06), col = jzMixHex(plate, sc.fg, 0.55);
        var Bd = jzRectLayer(ctx, 'hr board', bx, by, bw, bh, plate, { round: bh * 0.14, stroke: jzMixHex(sc.bg, sc.sub, 0.6), strokeW: lw });
        ahr_fade(ctx, Bd, 0, 0.35); jzNoGhost(Bd);
        var rowsSrc = latin ? [AHR_ABC.substr(0, 13), AHR_ABC.substr(13), '1234567890'] : [AHR_KANA.substr(0, 16), AHR_KANA.substr(16, 16), AHR_KANA.substr(32)];
        var cells = [], bf = bh * (latin ? 0.1 : 0.085);
        for (r = 0; r < 3; r++) {
            var arr = jzChars(rowsSrc[r]), n = arr.length, R = bw * (0.64 - r * 0.17), cy = by + bh * (0.52 + r * 0.04), span = (r ? 96 : 112) * (port ? 0.95 : 1), row = [];
            for (i = 0; i < n; i++) {
                if (r === 2) { row.push({ ch: arr[i], x: bx + (i - (n - 1) / 2) * bw * 0.6 / Math.max(1, n - 1), y: by + bh * 0.3, rot: 0 }); continue; }
                var a = (-90 - span / 2 + span * (n > 1 ? i / (n - 1) : 0.5)) * Math.PI / 180;
                row.push({ ch: arr[i], x: bx + Math.cos(a) * R * 0.78, y: cy + Math.sin(a) * R * 0.62, rot: (a * 180 / Math.PI + 90) * 0.7 });
            }
            for (i = 0; i < row.length; i++) cells.push(row[i]);
            var RL = ahr_glyphCloud(ctx, row, { font: fontB, size: bf, color: col, x: bx, y: by, name: 'hr board row ' + (r + 1) });
            ahr_fade(ctx, RL, 0, 0.35); jzNoGhost(RL);
        }
        var cs = bf * 0.9, lab = ahr_glyphCloud(ctx, [{ ch: 'YES', x: bx - bw * 0.36, y: by - bh * 0.36 }, { ch: 'NO', x: bx + bw * 0.36, y: by - bh * 0.36 }, { ch: 'GOOD BYE', x: bx, y: by + bh * 0.42 }], { font: fontB, size: cs, color: col, x: bx, y: by, track: 0.2, name: 'hr board words' });
        ahr_fade(ctx, lab, 0, 0.35); jzNoGhost(lab);
        // lyric row + when each glyph is spelled out
        var text = jzFlat(c.text), chars = [], all = jzChars(text);
        for (i = 0; i < all.length; i++) if (all[i] !== ' ') chars.push(all[i]);
        var n2 = chars.length, rowY = port ? H * 0.74 : H * 0.8, jit = jzP(ctx, 'jit', 0.6);
        var lines = n2 > (port ? 6 : 12) ? jzSplitLines(text, Math.ceil(n2 / 2)) : text;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'serif'));
        var Mz = ahr_fit(ctx, lines, { font: font, color: sc.fg, x: W / 2, y: rowY, track: 0.18, lead: 1.2, maxW: W * 0.86, maxH: H * (port ? 0.26 : 0.2), maxSize: H * 0.13 });
        var size = jzFontSize(Mz), sl = ahr_nonSp(ahr_slots(Mz)); Mz.remove();
        var span2 = c.dur * 0.5, times = [], ws = [], acc = 0, c0 = 0.25;
        for (i = 0; i < n2; i++) { var wv = 0.35 + jit * ahr_r(s, i, 51) * 2; ws.push(wv); acc += wv; }
        for (i = 0; i < n2; i++) { c0 += ws[i] / acc * (span2 - 0.25); times.push(c0); }
        function target(i2) {
            var ch = chars[i2] || '', cc = ch.charCodeAt(0);
            if (cc >= 0x30A1 && cc <= 0x30F6) ch = String.fromCharCode(cc - 0x60);
            ch = ch.toUpperCase();
            for (var q = 0; q < cells.length; q++) if (cells[q].ch === ch) return cells[q];
            return cells[ahr_h(s, i2, 52) % cells.length];
        }
        var TX = [], TY = [], TR = [], TC = [];
        for (i = 0; i < n2; i++) { var tg = target(i); TX.push(tg.x); TY.push(tg.y); TR.push(tg.rot); TC.push(tg.ch); }
        // planchette: follows the targets (same timing as the browser)
        var pr = bf * 1.2, pc = sc.fg;
        var HP = TH0 + 'var T=' + ahr_arr(times) + ',X=' + ahr_arr(TX) + ',Y=' + ahr_arr(TY) + ',n=T.length,cur=-1;for(var i=0;i<n;i++)if(time>=T[i]-Math.min(0.4,(T[i]-(i?T[i-1]:0))*0.75))cur=i;\n';
        var P = jzShapeLayer(ctx, 'hr planchette', bx, by);
        var poly = [[0, -pr * 2.3], [pr * 1.5, pr * 0.4], [pr * 0.9, pr * 1.3], [-pr * 0.9, pr * 1.3], [-pr * 1.5, pr * 0.4]];
        var g1 = jzGrp(P, 'lens'); jzAddEllipse(g1, pr * 1.24, pr * 1.24, 0, -pr * 0.75); jzAddStroke(g1, pc, lw * 1.4);
        ahr_path(P, 'body', poly, { closed: true, stroke: pc, sw: lw * 1.4, fill: jzMixHex(plate, sc.fg, 0.12), fop: 85 });
        jzSetExpr(jzXf(P, 'ADBE Position'), HP + 'var px=' + jzN(bx) + ',py=' + jzN(by + bh * 0.1) + ';' +
            'if(cur>=0){var tA=cur?T[cur-1]:0,tB=T[cur],mv=Math.min(0.4,(tB-tA)*0.75);var ax=cur?X[cur-1]:px,ay=cur?Y[cur-1]:py;var k=ioc((time-(tB-mv))/mv);px=ax+(X[cur]-ax)*k+Math.sin(k*Math.PI)*' + jzN(bh * 0.06) + ';py=ay+(Y[cur]-ay)*k+' + jzN(bf * 1.2) + ';}else py+=' + jzN(bf * 1.2) + ';' +
            'px+=Math.sin(time*2.1+' + (s % 97) + ')*' + jzN(bf * 0.12) + ';py+=Math.cos(time*1.7+' + (s % 97) + ')*' + jzN(bf * 0.1) + ';[px,py]');
        ahr_fade(ctx, P, 0, 0.35);
        jzNoGhost(P);
        // the letter under the planchette lights up in the accent
        if (n2) {
            var HL = jzText(ctx, TC[0], { font: fontB, size: bf * 1.25, color: sc.accent, x: TX[0], y: TY[0], name: 'hr board lit' });
            jzSetExpr(HL.property('ADBE Text Properties').property('ADBE Text Document'), HP + 'var C=' + ahr_strs(TC) + ';C[Math.max(0,cur)]');
            jzSetExpr(jzXf(HL, 'ADBE Position'), HP + '[X[Math.max(0,cur)],Y[Math.max(0,cur)]]');
            jzSetExpr(jzXf(HL, 'ADBE Rotate Z'), 'var R=' + ahr_arr(TR) + ',T=' + ahr_arr(times) + ',n=T.length,cur=-1;for(var i=0;i<n;i++)if(time>=T[i]-Math.min(0.4,(T[i]-(i?T[i-1]:0))*0.75))cur=i;R[Math.max(0,cur)]');
            jzSetExpr(jzXf(HL, 'ADBE Opacity'), HP + 'var nx=cur+1<n?T[cur+1]-Math.min(0.4,(T[cur+1]-T[cur])*0.75):1e9;(cur>=0&&time>=T[cur]-0.02&&time<nx)?100*K:0');
            jzNoGhost(HL);
        }
        // the lyric: one layer per glyph, each entering when it is spelled out
        var bb = null, stg = c.stagger || 0.04;
        for (i = 0; i < sl.length && i < n2; i++) {
            var G = jzMain(ctx, sl[i].ch, { font: font, size: size, color: sc.fg, x: sl[i].x, y: sl[i].y, mi: Math.max(0, times[i]) / stg, name: sl[i].ch });
            bb = jzUnion(bb, jzBB(G));
        }
        return bb || ahr_box(W * 0.2, rowY - size, W * 0.8, rowY + size);
    }
});

/* ================================================================ 6. hrMissing \u2014 \u5C0B\u306D\u4EBA */
jzReg('layout', 'hrMissing', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), fontH: rng.pick(jzFontsOf(st, ['display'])), ang: rng.range(-3.5, 3.5), off: rng.range(-0.06, 0.06), torn: rng.int(1, 3), stains: rng.int(1, 3) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, port = jzPortrait(ctx), s = c.seed | 0, u = jzU(ctx), i, k;
        var ph = H * 0.9, pw = Math.min(W * (port ? 0.86 : 0.5), ph * 0.72), cx = W / 2 + jzP(ctx, 'off', 0) * W * (port ? 0.3 : 1), cy = H / 2;
        var paper = ahr_dark(sc.bg) ? jzMixHex(ahr_light(sc), sc.sub, 0.12) : jzMixHex(sc.bg, '#FFFFFF', 0.62), ink = ahr_onCol(sc, paper), red = jzFitContrast(sc.accent, paper, 2.4);
        var x0 = cx - pw / 2, y0 = cy - ph / 2, TH0 = jzTH(ctx), kids = [];
        // the poster: shadow + paper, stains, photo with a faceless silhouette, tape, tear-off tabs (one shape layer)
        var S = jzShapeLayer(ctx, 'hr poster', 0, 0), sil = jzMixHex(ink, paper, 0.05), photo = jzMixHex(ink, paper, 0.25);
        var fw = pw * 0.52, fh = fw * 1.18, fxl = cx - fw / 2, fy = y0 + ph * 0.17;
        var tabs = 7, tw = pw / tabs, ty = y0 + ph * 0.86, th = ph * 0.14, line = jzMixHex(paper, ink, 0.4), gt = jzGrp(S, 'tabs');
        jzAddPath(gt, [[x0, ty], [x0 + pw, ty]], false);
        for (k = 0; k < tabs; k++) if (ahr_h(s, k, 61) % 7 >= jzP(ctx, 'torn', 2)) jzAddPath(gt, [[x0 + k * tw, ty], [x0 + k * tw, ty + th]], false);
        jzAddStroke(gt, line, Math.max(1, u * 0.001));
        var sg = [-1, 1];
        for (k = 0; k < 2; k++) {
            var gtp = jzGrp(S, 'tape' + k);
            jzAddRect(gtp, pw * 0.2, pw * 0.06, 0);
            jzAddFill(gtp, jzMixHex(paper, sc.sub, 0.25), 70);
            jzGX(gtp).property('ADBE Vector Position').setValue([cx + sg[k] * pw * 0.4, y0 + ph * 0.005]);
            jzGX(gtp).property('ADBE Vector Rotation').setValue(sg[k] * 28);
        }
        var gs = jzGrp(S, 'silhouette');
        jzAddEllipse(gs, fw * 0.4, fw * 0.4, cx, fy + fh * 0.42);
        jzAddPath(gs, [[cx - fw * 0.42, fy + fh], [cx - fw * 0.36, fy + fh * 0.76], [cx - fw * 0.12, fy + fh * 0.66], [cx + fw * 0.12, fy + fh * 0.66], [cx + fw * 0.36, fy + fh * 0.76], [cx + fw * 0.42, fy + fh]], true);
        jzAddFill(gs, sil);
        var gp = jzGrp(S, 'photo'); jzAddRect(gp, fw, fh, 0, cx, fy + fh / 2); jzAddFill(gp, photo);
        var gst = jzGrp(S, 'stains');
        for (k = 0; k < jzP(ctx, 'stains', 2); k++) {
            var sx = x0 + pw * ahr_rr(0.22, 0.78, s, k, 1), sy = y0 + ph * ahr_rr(0.2, 0.8, s, k, 2), R = pw * ahr_rr(0.05, 0.12, s, k, 3), pts = [];
            for (i = 0; i < 12; i++) { var a = i / 12 * Math.PI * 2, rr = R * (0.7 + 0.5 * ahr_r(s, k, i, 4)); pts.push([sx + Math.cos(a) * rr, sy + Math.sin(a) * rr * 0.8]); }
            jzAddPath(gst, pts, true);
        }
        jzAddFill(gst, jzMixHex(paper, sc.sub, 0.35), 35);
        var gpa = jzGrp(S, 'paper'); jzAddRect(gpa, pw, ph, 0, cx, cy); jzAddFill(gpa, paper);
        var gsh = jzGrp(S, 'shadow'); jzAddRect(gsh, pw, ph, 0, cx + u * 0.012, cy + u * 0.016); jzAddFill(gsh, '#000000', 25);
        kids.push(S);
        // texts on the poster
        var hs = pw * 0.15, mono = jzMonoF(ctx), fs = pw * 0.034, ly = fy + fh + ph * 0.24;
        var MH = jzText(ctx, 'MISSING', { font: jzP(ctx, 'fontH', jzFontKeyOf(ctx.st, 'display')), size: hs, color: red, x: cx, y: y0 + ph * 0.09, track: 0.08, sx: 0.92 });
        var MW = jzSize(MH)[0]; if (MW > pw * 0.94) jzXf(MH, 'ADBE Scale').setValue([92 * pw * 0.94 / MW, 100 * pw * 0.94 / MW]);
        kids.push(MH);
        kids.push(jzText(ctx, 'LAST SEEN ' + ahr_date(s, '.') + '  ' + ahr_clock(s, 0).substr(0, 5), { font: mono, size: fs, color: ink, x: cx, y: ly, track: 0.06, opacity: 0.85 }));
        var sub = jzFlat(c.lineText || c.text), sa = jzChars(sub);
        kids.push(jzText(ctx, sa.length > 22 ? sa.slice(0, 21).join('') + '\u2026' : sub, { font: jzBodyF(ctx), size: fs * 1.05, color: ink, x: cx, y: ly + fs * 1.7, track: 0.05, opacity: 0.7 }));
        var tel = 'TEL 0' + (ahr_h(s, 7) % 90 + 10) + '-' + (ahr_h(s, 8) % 9000 + 1000), rowsT = [];
        for (k = 0; k < tabs; k++) rowsT.push(ahr_h(s, k, 61) % 7 < jzP(ctx, 'torn', 2) ? ' ' : tel);
        var TT = jzText(ctx, rowsT.join('\r'), { font: mono, size: Math.min(tw * 0.42, th * 0.1), color: ink, x: cx, y: ty + th / 2, leading: tw, opacity: 0.8, name: 'hr tabs' });
        jzXf(TT, 'ADBE Rotate Z').setValue(-90);
        kids.push(TT);
        for (i = 0; i < kids.length; i++) { jzSetExpr(jzXf(kids[i], 'ADBE Opacity'), TH0 + 'value*K'); jzNoGhost(kids[i]); }
        // the lyric as the name
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display'));
        var L = ahr_fit(ctx, jzSplitLines(jzFlat(c.text), port ? 6 : 7), { font: font, color: ink, x: cx, y: fy + fh + ph * 0.1, track: 0.04, lead: 1.1, maxW: pw * 0.86, maxH: ph * 0.13, maxSize: pw * 0.16 });
        var bb = jzBB(L);
        jzAnimate(ctx, L, { mi: 0 });
        kids.push(L);
        // everything rides on a null: tilt + the slap onto the wall (scale 1.06 -> 1 with overshoot)
        var N = ctx.comp.layers.addNull(ctx.comp.duration); N.name = 'hr poster pin';
        jzXf(N, 'ADBE Anchor Point').setValue([0, 0]); jzXf(N, 'ADBE Position').setValue([cx, cy]);
        for (i = 0; i < kids.length; i++) kids[i].parent = N;
        jzXf(N, 'ADBE Rotate Z').setValue(jzP(ctx, 'ang', 0));
        jzSetExpr(jzXf(N, 'ADBE Scale'), JZ_FNS + 'var q=ob(cl(time/0.3),1.3);var k=time<=0?0:(1.06-0.06*q)*100;[k,k]');
        return bb;
    }
});

// ================================================================ pack horror part 2 (AE port of the horror set, layouts 7-12)
// hrWrongOne, hrRisingDark, hrRedacted, hrStaticTv, hrSpiritPhoto, hrWrongShadow (helpers: ahr_ in p_horror1)

/* ================================================================ 7. hrWrongOne \u2014 \u4E00\u5B57\u3060\u3051\u9055\u3046 */
jzReg('layout', 'hrWrongOne', {
    plan: function (rng, cut, st) {
        var a = jzChars(jzFlat(cut.text)), cs = [], kan = [], i;
        for (i = 0; i < a.length; i++) if (a[i] !== ' ' && !jzIsPunct(a[i])) cs.push([a[i], i]);
        for (i = 0; i < cs.length; i++) if (jzIsKanji(cs[i][0]) || jzIsKata(cs[i][0]) || /[A-Za-z]/.test(cs[i][0])) kan.push(cs[i]);
        var pool = kan.length ? kan : cs;
        var pick = pool.length ? pool[rng.int(0, pool.length - 1)][1] : 0;
        return { font: rng.pick(jzFontsOf(st, ['serif', 'display'])), wrong: pick, ang: rng.pick([28, 90, 180, -90, -35]), red: rng.chance(0.35), nums: rng.chance(0.7), sink: rng.range(0.06, 0.2) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, port = jzPortrait(ctx), s = c.seed | 0, i;
        var L = ahr_fit(ctx, jzSplitLines(jzFlat(c.text), port ? 5 : 9), { font: jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'serif')), color: sc.fg, x: W / 2, y: H / 2, track: 0.22, lead: 1.5, maxW: W * 0.82, maxH: H * 0.46, maxSize: H * 0.2 });
        var size = jzFontSize(L), all = ahr_slots(L), bb = jzBB(L);
        // flat-text index (spaces kept) -> glyph (textIndex) of the laid-out layer
        var fa = jzChars(jzFlat(c.text)), want = jzP(ctx, 'wrong', 0), wk = 0, k = 0, ti = -1;
        for (i = 0; i < fa.length; i++) { if (fa[i] === ' ') continue; if (i === want) { wk = k; break; } k++; }
        k = 0; for (i = 0; i < all.length; i++) { if (all[i].sp) continue; if (k === wk) { ti = i; break; } k++; }
        if (ti < 0) for (i = all.length - 1; i >= 0; i--) if (!all[i].sp) { ti = i; break; }
        jzAnimate(ctx, L, { mi: 0 });
        var turnAt = c.dur * 0.35, TI = ti + 1;
        var q = ahr_TH(ctx) + 'var q=ios((time-' + jzN(turnAt) + ')/' + jzN(Math.max(0.5, c.dur * 0.45)) + ');if(textIndex!=' + TI + ')q=0;';
        var tw = 'posterizeTime(12);seedRandom(' + (s % 9973) + '+Math.floor(time*12),true);var tw=(random()<0.07&&time>' + jzN(turnAt) + ')?random(-1,1)*14:0;';
        var ang = jzP(ctx, 'ang', 28);
        // it turns slowly while the rest never move (with a twitch now and then), sinks a little, and may redden
        jzAnimator(L, 'hr Wrong Turn', [['ADBE Text Rotation', 180]], q + tw + '(textIndex==' + TI + ')?(' + jzN(ang) + '*q+tw)/180*100:0');
        jzAnimator(L, 'hr Wrong Sink', [['ADBE Text Position 3D', [size * 0.03, size * 0.2, 0]]], q + 'seedRandom(' + (s % 9973) + '+Math.floor(time*6),true);[random(-1,1)*50*q,' + jzN(jzP(ctx, 'sink', 0.12) / 0.2 * 100) + '*q,0]');
        if (jzP(ctx, 'red', false)) jzAnimator(L, 'hr Wrong Red', [['ADBE Text Fill Color', jzHex(sc.accent)]], q + '100*q');
        if (jzP(ctx, 'nums', true) && all.length) {
            var fs = jzClamp(size * 0.14, 9 * ahr_px(ctx), 22 * ahr_px(ctx)), items = [], idx = 0, wi = -1;
            for (i = 0; i < all.length; i++) { if (all[i].sp) continue; if (i === ti) wi = idx; items.push({ ch: jzPad(idx + 1, 2), x: all[i].x, y: all[i].y + size * 0.72 }); idx++; }
            var NL = ahr_glyphCloud(ctx, items, { font: jzMonoF(ctx), size: fs, color: sc.sub, x: W / 2, y: H / 2, track: 0.1, name: 'hr numbers' });
            var gone = turnAt + 0.3;
            jzAnimator(NL, 'hr Hide Wrong', [['ADBE Text Opacity', 0]], '(time>' + jzN(gone) + '&&(textIndex==' + (wi * 2 + 1) + '||textIndex==' + (wi * 2 + 2) + '))?100:0');
            var TH0 = jzTH(ctx), e = TH0 + 'var e=oc((time-' + jzN((c.inDur || 0.3) * 0.8) + ')/0.3)*K;';
            jzSetExpr(jzXf(NL, 'ADBE Opacity'), e + '70*e');
            jzNoGhost(NL);
            if (wi >= 0) {
                var QQ = jzText(ctx, '??', { font: jzMonoF(ctx), size: fs, color: sc.accent, x: items[wi].x, y: items[wi].y, track: 0.1 });
                jzSetExpr(jzXf(QQ, 'ADBE Opacity'), e + '(time>' + jzN(gone) + '?100:0)*e');
                jzNoGhost(QQ);
            }
        }
        return bb;
    }
});

/* ================================================================ 8. hrRisingDark \u2014 \u95C7\u304B\u3089\u9019\u3044\u51FA\u308B */
jzReg('layout', 'hrRisingDark', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), pulls: rng.int(3, 5), span: rng.range(0.45, 0.6), rim: rng.chance(0.8) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, port = jzPortrait(ctx), s = c.seed | 0, i, k;
        var text = jzSplitLines(jzFlat(c.text), port ? 5 : 9), cy = H * 0.44;
        var L = ahr_fit(ctx, text, { font: jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), color: sc.fg, x: W / 2, y: cy, track: 0.06, lead: 1.2, maxW: W * 0.82, maxH: H * 0.34, maxSize: H * 0.2 });
        var size = jzFontSize(L), all = ahr_slots(L), bb = jzBB(L);
        var nl = text.split('\r').length, surf = cy + nl * size * 1.2 / 2 + size * 0.3, pulls = jzP(ctx, 'pulls', 4), T = c.dur * jzP(ctx, 'span', 0.5);
        // hand over hand: a few sudden pulls per glyph (times precomputed like the browser)
        var TK = [], RE = [], RO = [], mx = 1, gi = 0;
        for (i = 0; i < all.length; i++) {
            var rest = surf - all[i].y + size * 0.62; RE.push(rest); mx = Math.max(mx, rest);
            RO.push(ahr_rs(s, gi, 83));
            for (k = 0; k < pulls; k++) TK.push(jzClamp(ahr_r(s, gi, k, 81) * 0.8 + k * 0.2 / pulls, 0, 1) * T * (k + 1) / pulls);
            if (!all[i].sp) gi++;
        }
        jzAnimate(ctx, L, { mi: 0 });
        var pr = 'var TK=' + ahr_arr(TK) + ',NP=' + pulls + ',j=textIndex-1,pg=0;for(var k=0;k<NP;k++)pg+=oc((time-(TK[j*NP+k]||0))/0.14)/NP;';
        jzAnimator(L, 'hr Rise', [['ADBE Text Position 3D', [size * 0.02, mx, 0]]], JZ_FNS + pr + 'var RE=' + ahr_arr(RE) + ';posterizeTime(24);seedRandom(textIndex*31+Math.floor(time*24),true);[pg<1?random(-100,100):0,(1-pg)*(RE[j]||0)/' + jzN(mx) + '*100,0]');
        jzAnimator(L, 'hr Rise Tilt', [['ADBE Text Rotation', 16]], JZ_FNS + pr + 'var RO=' + ahr_arr(RO) + ';(1-pg)*(RO[j]||0)*100');
        // the dark pool, drawn over the glyphs still under it
        var pc = ahr_night(sc), pts = [], n = 64;
        for (i = 0; i <= n; i++) { var x = -W * 0.3 + W * 1.6 * i / n; pts.push([x, surf + (ahr_noise1(i * 0.35, s) * 0.6 + Math.sin(i * 0.9) * 0.15) * size * 0.12]); }
        var Pl = jzShapeLayer(ctx, 'hr pool', 0, 0);
        if (jzP(ctx, 'rim', true)) ahr_path(Pl, 'rim', pts, { stroke: sc.sub, sw: Math.max(1, size * 0.012), sop: 35 });
        var body = pts.slice(0); body.push([W * 1.3, H * 1.3]); body.push([-W * 0.3, H * 1.3]);
        ahr_path(Pl, 'pool', body, { closed: true, fill: pc });
        var gr = jzEffect(Pl, 'ADBE Ramp', 'hr Pool Shade');
        jzEP(gr, 1, [W / 2, surf]); jzEP(gr, 2, jzHex(pc)); jzEP(gr, 3, [W / 2, H]); jzEP(gr, 4, jzHex(jzMixHex(pc, '#000000', 0.5)));
        jzSetExpr(jzXf(Pl, 'ADBE Position'), '[Math.sin(time*0.9)*' + jzN(W * 0.04) + ',Math.sin(time*1.4)*' + jzN(size * 0.02) + ']');
        jzSetExpr(jzXf(Pl, 'ADBE Opacity'), jzTH(ctx) + '100*K');
        jzNoGhost(Pl);
        bb.y1 = Math.min(bb.y1, surf); bb.cy = (bb.y0 + bb.y1) / 2;
        return bb;
    }
});

/* ================================================================ 9. hrRedacted \u2014 \u9ED2\u5857\u308A\u6587\u66F8 */
jzReg('layout', 'hrRedacted', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['serif', 'body', 'mono'])), row: rng.range(0.42, 0.58), stamp: rng.chance(0.75), stampAng: rng.range(-14, -6), leak: rng.range(0.1, 0.25) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, port = jzPortrait(ctx), s = c.seed | 0, u = jzU(ctx), px = ahr_px(ctx), i, r;
        var mx = W * (port ? 0.08 : 0.12), mw = W - mx * 2, mono = jzMonoF(ctx), fs = jzClamp(u * 0.028, 12 * px, 34 * px), rh = fs * 2.1;
        var barC = jzMixHex(sc.fg, sc.bg, 0.08), txtC = sc.sub, TH0 = jzTH(ctx);
        // header + rule
        var hd = jzText(ctx, 'FILE No. ' + (ahr_h(s, 1) % 9000 + 1000) + '   ' + ahr_date(s, '.'), { font: mono, size: fs * 0.8, color: txtC, x: mx, y: H * 0.08, align: 'left', track: 0.08 });
        ahr_fade(ctx, hd, 0, 0.25); jzNoGhost(hd);
        var RU = jzPathLayer(ctx, 'hr rule', [[mx, H * 0.08 + fs], [mx + mw, H * 0.08 + fs]], txtC, { width: Math.max(1, u * 0.0012), trim: JZ_FNS + '100*oc(time/0.25)' });
        jzSetExpr(jzXf(RU, 'ADBE Opacity'), TH0 + '60*K'); jzNoGhost(RU);
        // the lyric line
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'serif')), ly = H * jzP(ctx, 'row', 0.5);
        var L = ahr_fit(ctx, jzSplitLines(jzFlat(c.text), port ? 7 : 16), { font: font, color: sc.fg, x: mx, y: ly, align: 'left', track: 0.06, lead: 1.3, maxW: mw, maxH: H * 0.24, maxSize: Math.min(H * 0.1, fs * 3.4) });
        var size = jzFontSize(L), m = jzSize(L), lh = m[1];
        // filler rows: body text, mostly blacked out
        var bodyT = jzChars(jzFlat(c.lineText || c.text) + '\u3000'), latin = jzHasLatin(c.text), rowsN = Math.floor((H * 0.84 - H * 0.14) / rh), rowsT = [], rows = [];
        for (r = 0; r < rowsN; r++) {
            var y = H * 0.15 + r * rh;
            if (Math.abs(y - ly) < lh / 2 + rh * 0.7) { rowsT.push(' '); continue; }
            var len = r % 5 === 4 ? ahr_rr(0.3, 0.6, s, r, 2) : ahr_rr(0.85, 1, s, r, 2), nC = Math.max(1, Math.floor(mw * len / (fs * (latin ? 0.6 : 1.05)))), row = [];
            for (i = 0; i < nC; i++) row.push(bodyT[i % bodyT.length]);
            rowsT.push(row.join('')); rows.push({ r: r, y: y, len: len });
        }
        var BT = jzText(ctx, rowsT.join('\r'), { font: jzBodyF(ctx), size: fs, color: txtC, x: mx, y: H * 0.15 + (rowsN - 1) / 2 * rh, align: 'left', track: 0.05, leading: rh, name: 'hr file body' });
        ahr_fade(ctx, BT, 0, 0.3, 0.6); jzNoGhost(BT);
        var B = jzShapeLayer(ctx, 'hr bars', 0, 0), leak = jzP(ctx, 'leak', 0.18);
        for (i = rows.length - 1; i >= 0; i--) {
            var R0 = rows[i], g = jzGrp(B, 'row ' + R0.r), x = mx, end = mx + mw * R0.len, cnt = 0;
            while (x < end) {
                var w = mw * ahr_rr(0.08, 0.35, s, R0.r, Math.round(x), 3), gap = fs * ahr_rr(0.4, 2.4, s, R0.r, Math.round(x), 4), ww = Math.min(w, end - x);
                if (ahr_r(s, R0.r, Math.round(x), 5) > leak) { jzAddRect(g, ww, fs * 1.24, 0, x + ww / 2, R0.y); cnt++; }
                x += w + gap;
            }
            if (!cnt) jzAddRect(g, 1, 1, 0, mx, R0.y);
            jzAddFill(g, barC);
            jzGX(g).property('ADBE Vector Anchor').setValue([mx, R0.y]); jzGX(g).property('ADBE Vector Position').setValue([mx, R0.y]);
            jzGX(g).property('ADBE Vector Scale').expression = TH0 + '[100*cl((time-' + jzN(R0.r * 0.025) + ')/0.2)*K,100]';
        }
        jzNoGhost(B);
        L.moveToBeginning();
        var bb = jzBB(L);
        jzAnimate(ctx, L, { mi: 0 });
        // the bar over the lyric slides off, sticks, then goes
        var d0 = (c.inDur || 0.3) * 0.3, fwid = m[0] + size * 0.4, xr = mx - size * 0.2 + fwid;
        var OB = jzShapeLayer(ctx, 'hr lyric bar', xr, ly), go = jzGrp(OB, 'bar');
        jzAddRect(go, fwid, lh + size * 0.24, 0, -fwid / 2, 0); jzAddFill(go, barC);
        jzSetExpr(jzXf(OB, 'ADBE Scale'), ahr_TH(ctx) + 'var k=ioc((time-' + jzN(d0) + ')/0.25)*0.35+ioc((time-' + jzN(d0 + 0.25 + c.dur * 0.08) + ')/0.3)*0.65;[100*(1-k),100]');
        jzSetExpr(jzXf(OB, 'ADBE Opacity'), TH0 + '100*oc(time/0.25)');
        jzNoGhost(OB);
        if (jzP(ctx, 'stamp', true)) {
            var ss = fs * 1.6, word = 'CLASSIFIED', at = c.dur * 0.45;
            var ST = jzText(ctx, word, { font: mono, size: ss, color: sc.accent, x: 0, y: 0, track: 0.2 }), tw = jzSize(ST)[0], sxp = W - mx - tw * 0.55, syp = H * 0.8;
            jzXf(ST, 'ADBE Position').setValue([sxp, syp]);
            var SB = jzRectLayer(ctx, 'hr stamp', sxp, syp, tw + ss, ss * 1.8, null, { round: ss * 0.2, stroke: sc.accent, strokeW: Math.max(2 * px, ss * 0.1) });
            var e = TH0 + 'var q=cl((time-' + jzN(at) + ')/0.12);';
            for (i = 0; i < 2; i++) {
                var X = i ? SB : ST;
                jzXf(X, 'ADBE Rotate Z').setValue(jzP(ctx, 'stampAng', -10));
                jzSetExpr(jzXf(X, 'ADBE Scale'), e + 'var k=(1.6-0.6*oc(q))*100;[k,k]');
                jzSetExpr(jzXf(X, 'ADBE Opacity'), e + '85*Math.min(1,q*2)*K');
                jzNoGhost(X);
            }
        }
        return bb;
    }
});

/* ================================================================ 10. hrStaticTv \u2014 \u7802\u5D50\u306E\u30C6\u30EC\u30D3 */
jzReg('layout', 'hrStaticTv', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'body'])), off: rng.range(-0.06, 0.06), tune: rng.range(0.3, 0.5), ant: rng.range(18, 34) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, port = jzPortrait(ctx), s = c.seed | 0, u = jzU(ctx), k;
        var tw = Math.min(W * (port ? 0.86 : 0.62), H * (port ? 0.5 : 0.8) * 1.3), th = tw / 1.3, cx = W / 2 + jzP(ctx, 'off', 0) * W, cy = H / 2 + th * 0.06;
        var body = jzMixHex(sc.bg, sc.fg, ahr_dark(sc.bg) ? 0.13 : 0.22), edge = jzMixHex(body, sc.fg, 0.3), lw = Math.max(1, u * 0.0018), TH0 = ahr_TH(ctx);
        var top = cy - th / 2, sw = tw * 0.72, sh = th * 0.78, sx0 = cx - tw / 2 + tw * 0.05, sy0 = top + th * 0.11, kx = sx0 + sw + (tw - sw - tw * 0.05) / 2;
        // glow of the screen into the room
        var mxd = Math.max(W, H), GL = ahr_solid(ctx, ahr_light(sc), 'hr tv glow', W, H), gm = GL.property('ADBE Mask Parade').addProperty('ADBE Mask Atom');
        gm.property('ADBE Mask Shape').setValue(jzCircleShape(sx0 + sw / 2, sy0 + sh / 2, mxd * 0.3)); gm.property('ADBE Mask Feather').setValue([mxd * 0.6, mxd * 0.6]);
        ahr_fade(ctx, GL, 0, 0.3, 0.1); jzNoGhost(GL);
        // set: highlight, scanlines (separate), screen, knobs, grille, body, feet, antenna
        var S = jzShapeLayer(ctx, 'hr tv', 0, 0);
        var gk = jzGrp(S, 'knobs');
        for (k = 0; k < 2; k++) jzAddEllipse(gk, tw * 0.07, tw * 0.07, kx, top + th * (0.25 + k * 0.2));
        jzAddStroke(gk, edge, lw); jzAddFill(gk, jzMixHex(body, sc.bg, 0.4));
        var ggr = jzGrp(S, 'grille');
        for (k = 0; k < 5; k++) jzAddPath(ggr, [[kx - tw * 0.04, top + th * (0.62 + k * 0.05)], [kx + tw * 0.04, top + th * (0.62 + k * 0.05)]], false);
        jzAddStroke(ggr, edge, lw, 80);
        var gsc = jzGrp(S, 'screen'); jzAddRect(gsc, sw, sh, sh * 0.1, sx0 + sw / 2, sy0 + sh / 2); jzAddFill(gsc, jzMixHex(sc.bg, '#000000', 0.6));
        var gb = jzGrp(S, 'body'); jzAddRect(gb, tw, th, th * 0.08, cx, top + th / 2); jzAddStroke(gb, edge, lw * 1.5); jzAddFill(gb, body);
        var gf = jzGrp(S, 'feet'); jzAddRect(gf, tw * 0.05, th * 0.06, 0, cx - tw * 0.36 + tw * 0.025, top + th + th * 0.03); jzAddRect(gf, tw * 0.05, th * 0.06, 0, cx + tw * 0.31 + tw * 0.025, top + th + th * 0.03); jzAddFill(gf, body);
        var sg = [-1, 1];
        for (k = 0; k < 2; k++) {
            var a = (90 + sg[k] * jzP(ctx, 'ant', 26)) * Math.PI / 180, ga = jzGrp(S, 'antenna' + k);
            jzAddPath(ga, [[0, 0], [-Math.cos(a) * th * 0.45, -Math.sin(a) * th * 0.45]], false); jzAddStroke(ga, edge, lw * 2);
            jzGX(ga).property('ADBE Vector Position').setValue([cx, top]);
            jzGX(ga).property('ADBE Vector Rotation').expression = 'Math.sin(time*0.7+' + sg[k] + ')*1.5*' + (-sg[k]);
        }
        ahr_fade(ctx, S, 0, 0.3); jzNoGhost(S);
        // static that slowly gives way to the words
        var tune = 'var tune=ioc((time-0.15)/' + jzN(Math.max(0.05, c.dur * jzP(ctx, 'tune', 0.4))) + ');';
        var N = ahr_solid(ctx, '#808080', 'hr tv static', sw - sh * 0.06, sh - sh * 0.06);
        jzXf(N, 'ADBE Position').setValue([sx0 + sw / 2, sy0 + sh / 2]);
        var ne = jzEffect(N, 'ADBE Noise', 'hr Snow'); jzEP(ne, 1, 100); jzEP(ne, 2, 0);
        jzSetExpr(jzXf(N, 'ADBE Opacity'), TH0 + tune + 'posterizeTime(24);seedRandom(' + (s % 9973) + '+Math.floor(time*24),true);var b=random()<0.06?0.35:0;Math.min(100,(0.85-0.6*tune+b)*100)*oc(time/0.3)');
        jzNoGhost(N);
        var tc = jzLum(sc.fg) > 0.5 ? sc.fg : '#FFFFFF';
        var L = ahr_fit(ctx, jzSplitLines(jzFlat(c.text), port ? 5 : 7), { font: jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), color: tc, x: sx0 + sw / 2, y: sy0 + sh / 2, track: 0.04, lead: 1.15, maxW: sw * 0.82, maxH: sh * 0.6, maxSize: sh * 0.36 });
        var bb = jzBB(L);
        jzAnimate(ctx, L, { mi: 0 });
        // vertical roll while it is still tuning + the picture fading in (a Transform effect keeps the motion expressions)
        var tf = jzEffect(L, 'ADBE Geometry2', 'hr Tuning');
        jzEX(tf, 2, TH0 + tune + '[value[0],value[1]+(1-tune)*' + jzN(sh * 0.08) + '*Math.sin(time*23)]');
        jzEX(tf, 9, TH0 + tune + '100*(0.55+0.45*tune)');
        // scanlines + the curved-glass highlight
        var sp = Math.max(3 * ahr_px(ctx), sh / 70), SL = jzShapeLayer(ctx, 'hr tv glass', sx0 + sw / 2, sy0), gh = jzGrp(SL, 'highlight');
        jzAddRect(gh, sw * 0.4, sh * 0.12, sh * 0.06, -sw / 2 + sw * 0.06 + sw * 0.2, sh * 0.05 + sh * 0.06); jzAddFill(gh, '#FFFFFF', 5);
        var gl = jzGrp(SL, 'lines');
        jzAddRect(gl, sw, sp * 0.5, 0, 0, sp * 0.25);
        var rp = jzVecs(gl).addProperty('ADBE Vector Filter - Repeater');
        rp.property('ADBE Vector Repeater Copies').setValue(Math.floor(sh / (sp * 2)));
        rp.property('ADBE Vector Repeater Transform').property('ADBE Vector Repeater Position').setValue([0, sp * 2]);
        jzAddFill(gl, '#000000', 18);
        ahr_fade(ctx, SL, 0, 0.3); jzNoGhost(SL);
        return bb;
    }
});

/* ================================================================ 11. hrSpiritPhoto \u2014 \u5FC3\u970A\u5199\u771F */
jzReg('layout', 'hrSpiritPhoto', {
    plan: function (rng, cut, st) {
        return { hand: rng.chance(0.75) ? 'klee' : rng.pick(jzFontsOf(st, ['body', 'serif'])), ang: rng.range(-5, 5), spot: [rng.range(0.25, 0.75), rng.range(0.3, 0.6)], win: rng.chance(0.6), side: rng.pick([1, -1]) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, port = jzPortrait(ctx), s = c.seed | 0, u = jzU(ctx), i;
        var side = jzP(ctx, 'side', 1), angD = jzP(ctx, 'ang', 0), spot = jzP(ctx, 'spot', [0.5, 0.45]), TH0 = ahr_TH(ctx);
        var pw = port ? W * 0.78 : Math.min(W * 0.46, H * 0.86 * 1.25), ph = pw / 1.25;
        var pcx = port ? W / 2 : (side > 0 ? W * 0.3 : W * 0.7), pcy = port ? H * 0.33 : H * 0.5;
        var rad = angD * Math.PI / 180, cs = Math.cos(rad), sn = Math.sin(rad);
        function T(lx, ly) { return [pcx + lx * cs - ly * sn, pcy + lx * sn + ly * cs]; }
        var border = jzMixHex(ahr_light(sc), sc.sub, 0.1), dark = jzMixHex(ahr_darkOf(sc), '#000000', 0.35), bw = u * 0.018;
        var dev = 'var dv=ios(time/' + jzN(Math.max(0.6, c.dur * 0.35)) + ');';
        var spx = -pw / 2 + pw * spot[0], spy = -ph / 2 + ph * spot[1], sr = pw * 0.09;
        // the photo (local coordinates around its centre; the layer carries the tilt)
        var S = jzShapeLayer(ctx, 'hr photo', pcx, pcy);
        var rr = sr * 1.5, rp = [];
        for (i = 0; i <= 46; i++) { var a = i / 40 * Math.PI * 2 - 1.2, w = 1 + 0.12 * ahr_noise1(i * 0.3, s) + i / 40 * 0.08; rp.push([spx + Math.cos(a) * rr * w * 0.8, spy + Math.sin(a) * rr * w]); }
        var gr = ahr_path(S, 'ring', rp, { stroke: sc.accent, sw: Math.max(2, u * 0.005), sop: 95 });
        jzAddTrimPaths(gr, TH0 + '100*oc((time-' + jzN(c.dur * 0.3) + ')/0.5)*K');
        var gsp = jzGrp(S, 'smudge');
        for (i = 0; i < 5; i++) jzAddEllipse(gsp, sr * 0.75 * (0.5 + i * 0.5), sr * 1.25 * (0.5 + i * 0.5), spx, spy);
        jzAddFill(gsp, border, 13);
        jzGX(gsp).property('ADBE Vector Group Opacity').expression = TH0 + dev + '100*dv';
        var gm = jzGrp(S, 'floor'); jzAddRect(gm, pw, ph * 0.32, 0, 0, ph * 0.18 + ph * 0.16); jzAddFill(gm, border, 60);
        jzGX(gm).property('ADBE Vector Group Opacity').expression = TH0 + dev + '22*dv';
        if (jzP(ctx, 'win', true)) {
            var wx = -pw * 0.3, wy = -ph * 0.32, ww = pw * 0.3, wh = ph * 0.45, gwl = jzGrp(S, 'window bars');
            jzAddPath(gwl, [[wx + ww / 2, wy], [wx + ww / 2, wy + wh]], false); jzAddPath(gwl, [[wx, wy + wh / 2], [wx + ww, wy + wh / 2]], false);
            jzAddStroke(gwl, dark, Math.max(1, u * 0.004));
            var gw = jzGrp(S, 'window'); jzAddRect(gw, ww, wh, 0, wx + ww / 2, wy + wh / 2); jzAddFill(gw, border);
            jzGX(gw).property('ADBE Vector Group Opacity').expression = TH0 + dev + '12*dv';
        }
        var gd = jzGrp(S, 'dark'); jzAddRect(gd, pw, ph, 0, 0, 0); jzAddFill(gd, dark);
        var gbo = jzGrp(S, 'border'); jzAddRect(gbo, pw + bw * 2, ph + bw * 3.2, 0, 0, bw * 1.1); jzAddFill(gbo, border);
        var gsh = jzGrp(S, 'shadow'); jzAddRect(gsh, pw + bw * 2, ph + bw * 3, 0, u * 0.01, bw + u * 0.012); jzAddFill(gsh, '#000000', 25);
        jzXf(S, 'ADBE Rotate Z').setValue(angD);
        ahr_fade(ctx, S, 0, 0.3); jzNoGhost(S);
        var dp = T(pw / 2 - ph * 0.05, ph / 2 - ph * 0.06);
        var DT = jzText(ctx, "'" + ahr_date(s, ' ').substr(2), { font: jzMonoF(ctx), size: ph * 0.055, color: jzFitContrast(sc.accent, dark, 3), x: dp[0], y: dp[1], align: 'right', track: 0.1, rot: angD });
        jzSetExpr(jzXf(DT, 'ADBE Opacity'), TH0 + dev + '90*dv*K'); jzNoGhost(DT);
        // the note (the lyric, handwritten) + the arrow to it
        var hand = jzP(ctx, 'hand', 'klee'), nx = port ? W / 2 : (side > 0 ? W * 0.76 : W * 0.24), ny = port ? H * 0.75 : H * 0.5;
        var L = ahr_fit(ctx, jzSplitLines(jzFlat(c.text), port ? 7 : 6), { font: hand, color: sc.fg, x: nx, y: ny, track: 0.02, lead: 1.2, maxW: port ? W * 0.84 : W * 0.38, maxH: H * (port ? 0.26 : 0.46), maxSize: H * 0.14 });
        var size = jzFontSize(L), nm = jzSize(L);
        jzXf(L, 'ADBE Rotate Z').setValue(-angD * 0.4);
        var bb = jzBB(L);
        jzAnimate(ctx, L, { mi: 0 });
        var A = T(spx + sr * 1.4 * (port ? 0.3 : side), spy + sr * (port ? 1.6 : 0.3)), ax = A[0], ay = A[1];
        var tx = port ? nx : nx - side * (nm[0] / 2 + size * 0.35), ty = port ? ny - nm[1] / 2 - size * 0.35 : ny;
        var mxp = (ax + tx) / 2 + (port ? W * 0.1 : 0), myp = (ay + ty) / 2 - (port ? 0 : H * 0.1), pts = [];
        for (i = 16; i >= 0; i--) { var q = i / 16; pts.push([(1 - q) * (1 - q) * ax + 2 * q * (1 - q) * mxp + q * q * tx, (1 - q) * (1 - q) * ay + 2 * q * (1 - q) * myp + q * q * ty]); }
        var AR = jzShapeLayer(ctx, 'hr arrow', 0, 0), aw = Math.max(2, u * 0.004), an = Math.atan2(ay - pts[14][1], ax - pts[14][0]), La = u * 0.03;
        var ea = TH0 + 'var ae=oc((time-' + jzN(c.dur * 0.3 + 0.35) + ')/0.35)*K;';
        var gh = ahr_path(AR, 'head', [[ax + Math.cos(an + 2.6) * La, ay + Math.sin(an + 2.6) * La], [ax, ay], [ax + Math.cos(an - 2.6) * La, ay + Math.sin(an - 2.6) * La]], { stroke: sc.accent, sw: aw, sop: 90 });
        jzGX(gh).property('ADBE Vector Group Opacity').expression = ea + 'ae>0.95?100:0';
        var gs = ahr_path(AR, 'shaft', pts, { stroke: sc.accent, sw: aw, sop: 90 });
        jzAddTrimPaths(gs, ea + '100*ae');
        jzNoGhost(AR);
        L.moveToBeginning();
        return bb;
    }
});

/* ================================================================ 12. hrWrongShadow \u2014 \u5F71\u304C\u9055\u3046 */
jzReg('layout', 'hrWrongShadow', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), k: rng.range(1.5, 1.9), turn: rng.range(0.45, 0.65), dir: rng.pick([1, -1]), lean: rng.range(4, 9) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, port = jzPortrait(ctx), s = c.seed | 0;
        var text = jzSplitLines(jzFlat(c.text), port ? 4 : 7), font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display'));
        var L = ahr_fit(ctx, text, { font: font, color: sc.fg, x: W / 2, y: H * 0.6, track: 0.05, lead: 1.1, maxW: W * 0.66, maxH: H * 0.26, maxSize: H * 0.16 });
        var size = jzFontSize(L), m = jzSize(L), cy = H * 0.66 - m[1] * 0.2;
        jzXf(L, 'ADBE Position').setValue([W / 2, cy]);
        // the shadow: bigger, soft, and it moves on its own
        var SH = ahr_fit(ctx, text, { font: font, color: ahr_dark(sc.bg) ? jzMixHex(sc.bg, '#000000', 0.55) : jzMixHex(sc.bg, sc.fg, 0.28), x: W / 2, y: H / 2, track: 0.05, lead: 1.1, maxW: W * 0.92, maxH: H * 0.42, maxSize: size * jzP(ctx, 'k', 1.7) });
        var shM = jzSize(SH), sy = Math.max(H * 0.06 + shM[1] / 2, cy - m[1] * 0.5 - shM[1] * 0.42);
        jzXf(SH, 'ADBE Position').setValue([W / 2, sy]);
        SH.name = 'hr shadow';
        var TH0 = ahr_TH(ctx), lamp = TH0 + 'var on=time<0.08?0:time<0.16?1:time<0.24?0.25:1;posterizeTime(24);seedRandom(' + (s % 9973) + '+Math.floor(time*24),true);var Lm=on*(random()<0.04?0.5:1)*K;';
        // pool of lamp light behind (a soft ellipse)
        var pool = ahr_dark(sc.bg) ? jzMixHex(sc.bg, sc.fg, 0.16) : jzMixHex(sc.bg, '#FFFFFF', 0.5), R = Math.max(shM[0], size * jzP(ctx, 'k', 1.7) * 2.2) * 0.75;
        var PL = ahr_solid(ctx, pool, 'hr lamp pool', W, H), pm = PL.property('ADBE Mask Parade').addProperty('ADBE Mask Atom');
        pm.property('ADBE Mask Shape').setValue(jzCircleShape(W / 2, sy, R * 0.5)); pm.property('ADBE Mask Feather').setValue([R, R]);
        jzXf(PL, 'ADBE Anchor Point').setValue([W / 2, sy]); jzXf(PL, 'ADBE Position').setValue([W / 2, sy]); jzXf(PL, 'ADBE Scale').setValue([100, 72]);
        jzSetExpr(jzXf(PL, 'ADBE Opacity'), lamp + '100*Lm');
        PL.moveAfter(SH);
        var t0 = c.dur * jzP(ctx, 'turn', 0.55), dir = jzP(ctx, 'dir', 1), lean = jzP(ctx, 'lean', 6), sd = s % 97;
        jzSetExpr(jzXf(SH, 'ADBE Position'), TH0 + '[value[0]+nz(time*0.35,' + sd + ')*' + jzN(size * 0.5) + ',value[1]+nz(time*0.3,' + (sd + 2) + ')*' + jzN(size * 0.1) + ']');
        jzSetExpr(jzXf(SH, 'ADBE Scale'), TH0 + 'var sx=1-2*ioc((time-' + jzN(t0) + ')/0.5);if(Math.abs(sx)<0.04)sx=0.04;[value[0]*sx,value[1]]');
        jzSetExpr(jzXf(SH, 'ADBE Rotate Z'), TH0 + 'posterizeTime(12);seedRandom(' + (s % 9973) + '+Math.floor(time*12),true);var tw=random()<0.05?random(-1,1)*8:0;value+' + jzN(dir * lean) + '*nz(time*0.25+3,' + (sd + 4) + ')+tw');
        jzSetExpr(jzXf(SH, 'ADBE Opacity'), lamp + '85*Lm');
        var tf = jzEffect(SH, 'ADBE Geometry2', 'hr Shadow Skew');
        jzEX(tf, 6, TH0 + 'nz(time*0.2,' + (sd + 5) + ')*10');
        var bl = jzEffect(SH, 'ADBE Gaussian Blur 2', 'hr Shadow Soft'); jzEP(bl, 1, Math.max(1, jzFontSize(SH) * 0.03));
        jzNoGhost(SH); jzNoGhost(PL);
        L.moveToBeginning();
        var bb = jzBB(L);
        jzAnimate(ctx, L, { mi: 0 });
        // floor line
        var fyy = cy + m[1] / 2 + size * 0.2, FL = jzPathLayer(ctx, 'hr floor', [[-m[0] * 0.8, 0], [m[0] * 0.8, 0]], sc.sub, { width: Math.max(1, size * 0.012) });
        jzXf(FL, 'ADBE Position').setValue([W / 2, fyy]);
        jzSetExpr(jzXf(FL, 'ADBE Scale'), TH0 + 'var e=oc((time-0.1)/0.5)*K;[100*e,100]');
        jzSetExpr(jzXf(FL, 'ADBE Opacity'), TH0 + '50*K');
        jzNoGhost(FL);
        return bb;
    }
});

// ================================================================ pack horror part 3 (AE port of the horror set: enter / exit / hold / treat)
// Per-glyph work = text animators with Expression Selectors. Per-glyph numbers the browser derives from the whole lyric
// (glyph index in the lyric, irregular arrival times \u2026) are precomputed here and embedded as arrays indexed by textIndex.

/* ---------------------------------------------------------------- helpers (ahr3_) */
function ahr3_text(L) { try { return String(L.property('ADBE Text Properties').property('ADBE Text Document').value.text); } catch (e) { return ''; } }
function ahr3_N(m) { return Math.max(1, jzCount(String(m.c.text || ''))); }
function ahr3_cs(m) { return m.c.seed | 0; }
// per glyph of this layer (textIndex order): its index in the whole lyric (-1 for spaces); one-glyph layers use their motion index
function ahr3_idx(m) {
    var g = jzGlyphs(ahr3_text(m.L)), N = ahr3_N(m), out = [], k = 0, i, ns = jzCount(ahr3_text(m.L));
    var mi = Math.round(+(m.o && m.o.mi) || 0);
    for (i = 0; i < g.length; i++) {
        if (g[i] === ' ' || g[i] === '\u3000') { out.push(-1); continue; }
        out.push(ns === 1 && N > 1 ? Math.min(N - 1, mi) : k); k++;
    }
    return out;
}
// position 0..1 of each glyph along the lyric (browser orderOf)
function ahr3_ord(m) {
    var g = jzGlyphs(ahr3_text(m.L)), N = ahr3_N(m), n = g.length, out = [], i, mi = +(m.o && m.o.mi) || 0;
    for (i = 0; i < n; i++) out.push(n > 1 ? i / (n - 1) : (N > 1 ? jzClamp(mi / (N - 1), 0, 1) : 0));
    return out;
}
function ahr3_motionK(m) { return jzClamp((m.ctx.fx.motion == null ? 0.7 : m.ctx.fx.motion) / 0.7, 0, 1.6); }
// comp position of a point given in a layer's own (layer-space) coordinates, through its parents (static values)
function ahr3_toComp(L, p) {
    var x = p[0], y = p[1], P = L, g = 0;
    while (P && g++ < 20) {
        var a = jzXf(P, 'ADBE Anchor Point').value, ps = jzXf(P, 'ADBE Position').value, s = jzXf(P, 'ADBE Scale').value, r = jzXf(P, 'ADBE Rotate Z').value * Math.PI / 180;
        var dx = (x - a[0]) * s[0] / 100, dy = (y - a[1]) * s[1] / 100;
        x = ps[0] + dx * Math.cos(r) - dy * Math.sin(r); y = ps[1] + dx * Math.sin(r) + dy * Math.cos(r);
        P = P.parent;
    }
    return [x, y];
}
// a helper shape layer that rides on the lyric layer L (parented; sits just above it unless L uses a track matte)
function ahr3_rider(m, L, name) {
    var S = jzShapeLayer(m.ctx || m, name, 0, 0);
    try { if (!L.trackMatteType || L.trackMatteType === TrackMatteType.NO_TRACK_MATTE) S.moveBefore(L); } catch (e) {}
    return S;
}
function ahr3_attach(S, L) { S.parent = L; return S; }

/* ================================================================ ENTRANCES */

/* hrBlinkCreep \u2014 \u77AC\u304D\u306E\u9593\u306B: every blink, the glyphs are closer */
jzReg('enter', 'hrBlinkCreep', { apply: function (m) {
    var L = m.L, S = m.size, D = S * 2;
    m.parts.op.push('if((P>=0.26&&P<0.31)||(P>=0.52&&P<0.57)||(P>=0.78&&P<0.83))f=0;');
    var q = m.HD + 'var st=(P>=0.31?1:0)+(P>=0.57?1:0)+(P>=0.83?1:0),r=1-st/3;' + m.seedR + 'var an=random(0,Math.PI*2),ds=random(0.9,2),rt=random(-1,1);';
    jzAnimator(L, 'hr In Creep', [['ADBE Text Position 3D', [D, D, 0]]], q + '[Math.cos(an)*ds*' + jzN(S) + '*r/' + jzN(D) + '*100,Math.sin(an)*ds*' + jzN(S) + '*r*0.6/' + jzN(D) + '*100,0]');
    jzAnimator(L, 'hr In Creep Tilt', [['ADBE Text Rotation', 25]], q + 'rt*r*100');
    jzAnimator(L, 'hr In Creep Size', [['ADBE Text Scale 3D', [55, 55, 100]], ['ADBE Text Opacity', 30]], q + 'r*100');
} });

/* hrJumpScare \u2014 \u98DB\u3073\u51FA\u3057: faint and tiny, then it snaps at you and settles */
jzReg('enter', 'hrJumpScare', { apply: function (m) {
    var rnd = 'seedRandom(SD+Math.floor(time*24)*3,true);var j1=random(-1,1),j2=random(-1,1),j3=random(-1,1),j4=random();';
    m.parts.sc.push('var k0=P<0.62?0.42+0.08*P/0.62:(1+0.95*(1-oe((P-0.62)/0.38)));f=[f[0]*k0,f[1]*k0];');
    m.parts.op.push('if(P<0.62){' + rnd + 'f*=(0.1+0.12*P/0.62)*(j4<0.15?0.3:1);}');
    m.parts.pos.push(rnd + 'var kj=P<0.62?0.02*0.45:0.12*(1-oe((P-0.62)/0.38))*1.9;d=[d[0]+j1*SZ*kj,d[1]+j2*SZ*kj];');
    m.parts.rot.push(rnd + 'r+=P<0.62?0:j3*7*(1-oe((P-0.62)/0.38));');
} });

/* hrUneasy \u2014 \u9593\u306E\u60AA\u3044\u51FA\u73FE: glyphs arrive one by one after irregular, too-long pauses, each with a twitch */
jzReg('enter', 'hrUneasy', { selfHide: true, apply: function (m) {
    var L = m.L, S = m.size, cs = ahr3_cs(m), N = ahr3_N(m), idx = ahr3_idx(m), gaps = [], tot = 0, k, i;
    for (k = 0; k < N; k++) { var r = ahr_r(cs, k, 901), g = 0.25 + (r < 0.25 ? 2.2 : r * 1.1); gaps.push(g); tot += g; }
    var times = [], acc = 0; for (k = 0; k < N; k++) { times.push(acc / tot * 0.86); acc += gaps[k]; }
    var T0 = [], RX = [], RY = [], RR = [];
    for (i = 0; i < idx.length; i++) {
        var ix = idx[i] < 0 ? (i ? idx[i - 1] : 0) : idx[i]; ix = Math.max(0, Math.min(N - 1, ix));
        var h = ahr_h(cs, ix, 21);
        T0.push(times[ix] || 0); RX.push(ahr_rs(h, 1)); RY.push(ahr_rs(h, 2)); RR.push(ahr_rs(h, 3));
    }
    var q = m.HD + 'var T=' + ahr_arr(T0) + ',j=textIndex-1,q=(P-(T[j]||0))/0.1;';
    jzAnimator(L, 'hr In Wait', [['ADBE Text Opacity', 0]], q + '(q<0||time<DL)?100:0');
    jzAnimator(L, 'hr In Twitch', [['ADBE Text Position 3D', [S * 0.18, S * 0.1, 0]]], q + 'var RX=' + ahr_arr(RX) + ',RY=' + ahr_arr(RY) + ',a=(q>=0&&q<1)?(1-q)*100:0;[RX[j]*a,RY[j]*a,0]');
    jzAnimator(L, 'hr In Twitch Tilt', [['ADBE Text Rotation', 18]], q + 'var RR=' + ahr_arr(RR) + ';(q>=0&&q<1)?RR[j]*(1-q)*100:0');
    jzAnimator(L, 'hr In Red', [['ADBE Text Fill Color', jzHex(m.ctx.sc.accent)]], q + '(q>=0&&q<0.5)?100:0');
} });

/* hrVhold \u2014 \u5782\u76F4\u540C\u671F: the line rolls through its own block like a TV losing sync, then locks */
jzReg('enter', 'hrVhold', { apply: function (m) {
    var L = m.L, S = m.size, r = jzRect(L), pad = S * 0.25, HH = r.height + pad * 2, sl = ahr_slots(L), Y = [], i;
    var ap = jzXf(L, 'ADBE Position').value, sy = jzXf(L, 'ADBE Scale').value[1] / 100 || 1;
    var top = ap[1] - (r.height / 2 + pad) * sy;
    for (i = 0; i < sl.length; i++) Y.push((sl[i].y - top) / sy);
    var hs = 'var HH=' + jzN(HH) + ',e=oc(P),mm=(((1-e)*2.6*HH)%HH+HH)%HH;';
    jzAnimator(L, 'hr In Roll', [['ADBE Text Position 3D', [0, HH, 0]]], m.HD + hs + 'var Y=' + ahr_arr(Y) + ',y=Y[textIndex-1]||0;[0,(((y+mm)%HH)-y)/HH*100,0]');
    m.parts.pos.push('posterizeTime(24);seedRandom(SD+Math.floor(time*24),true);d=[d[0]+random(-1,1)*SZ*0.05*(1-oc(P)),d[1]];');
    m.parts.op.push('f*=Math.min(1,0.4+P*2);');
} });

/* hrMirrorSnap \u2014 \u93E1\u6587\u5B57: it appears reversed, shudders, and snaps the right way round */
jzReg('enter', 'hrMirrorSnap', { apply: function (m) {
    m.parts.sc.push('var ms=-1;if(P>=0.55&&P<0.72){seedRandom(SD+Math.floor(time*24)*7,true);ms=random()<0.5?-1:0.25;}else if(P>=0.72)ms=-0.2+1.2*ob((P-0.72)/0.28,2.2);if(P>=1)ms=1;if(Math.abs(ms)<0.04)ms=0.04;f=[f[0]*ms,f[1]];');
    m.parts.op.push('f*=Math.min(1,P/0.18);');
    m.parts.rot.push('if(P<0.55)r+=Math.sin(P*30)*1.5;');
} });

/* hrManifest \u2014 \u6D6E\u304B\u3073\u4E0A\u304C\u308B: each glyph wavers in and out on its own breath, and finally holds */
jzReg('enter', 'hrManifest', { selfHide: true, apply: function (m) {
    var L = m.L, S = m.size;
    var q = m.HD + m.seedR + 'var ph=random(0,Math.PI*2),fq=7+random(0,6),fin=P<=0.72?0:(P>=1?1:((P-0.72)/0.28)*((P-0.72)/0.28)*(3-2*(P-0.72)/0.28));' +
        'var wv=cl(P*1.3)*(0.45+0.55*Math.max(0,Math.sin(P*fq+ph)));var a=time<DL?0:wv+(1-wv)*fin;';
    jzAnimator(L, 'hr In Breath', [['ADBE Text Opacity', 0]], q + '(1-a)*100');
    jzAnimator(L, 'hr In Waver', [['ADBE Text Position 3D', [S * 0.12, S * 0.08, 0]]], q + '[Math.sin(P*4*3.1+ph)*(1-fin)*100,Math.sin(P*3*2.7+ph*1.3)*(1-fin)*100,0]');
} });

/* hrClawReveal \u2014 \u722A\u75D5\u304B\u3089: four slanted tears open across the line and widen until it is all there
   (an alpha matte of tilted bands riding on the text; layer masks when the text already has a matte) */
jzReg('enter', 'hrClawReveal', { apply: function (m) {
    var L = m.L, S = m.size, r = jzRect(L), pad = S * 0.3, seed = (m.c.seed | 0) + ((m.o && m.o.mi) || 0);
    var x0 = r.left - pad, x1 = r.left + r.width + pad, y0 = r.top - pad, y1 = r.top + r.height + pad, Wd = x1 - x0, Hh = y1 - y0;
    var n = 4, sp = Wd / n, slope = Math.min(0.55 * Hh, sp * 1.4), dir = (ahr_h(seed, 3) & 1) ? 1 : -1, k;
    var ang = Math.atan2(slope, Hh) * 180 / Math.PI * -dir, rcx = (x0 + x1) / 2, rcy = (y0 + y1) / 2;
    function wx(kk, edge) {
        return m.HD + AHR_FNS + 'var q=cl((P-' + jzN(kk * 0.08) + ')/0.7)' + (edge ? '*sm(0.5,0.9,P)' : '') + ';var e=ioc(q);var w=' +
            '(P>0.97||time>DL+IN+0.05)?' + jzN(Wd * 3) + ':(q<=0?0:' + jzN(sp * 0.08) + '+' + jzN(sp * 1.1) + '*e*e);';
    }
    var hasMatte = false; try { hasMatte = L.trackMatteType && L.trackMatteType !== TrackMatteType.NO_TRACK_MATTE; } catch (e0) {}
    if (!hasMatte) {
        var M = jzShapeLayer(m.ctx, 'hr claw matte', 0, 0);
        for (k = -2; k < n + 2; k++) {
            var kk = Math.max(0, Math.min(n - 1, k)), g = jzGrp(M, 'tear ' + k);
            var rc = jzAddRect(g, sp * 0.08, Hh * 1.5, 0);
            rc.property('ADBE Vector Rect Size').expression = wx(kk, k < 0 || k >= n) + '[w,' + jzN(Hh * 1.5) + ']';
            jzAddFill(g, '#FFFFFF');
            jzGX(g).property('ADBE Vector Position').setValue([x0 + (k + 0.5) * sp - rcx, 0]);
            jzGX(g).property('ADBE Vector Rotation').setValue(ang);
        }
        jzXf(M, 'ADBE Position').setValue(ahr3_toComp(L, [rcx, rcy]));
        M.moveBefore(L);
        M.parent = L;
        L.trackMatteType = TrackMatteType.ALPHA;
        jzNoGhost(M);
        return;
    }
    for (k = -2; k < n + 2; k++) {
        var cx = x0 + (k + 0.5) * sp, a = -slope / 2 * dir, b = slope / 2 * dir, w0 = sp * 0.5;
        var mk = L.property('ADBE Mask Parade').addProperty('ADBE Mask Atom'), sh = new Shape();
        sh.vertices = [[cx - w0 / 2 + a, y0], [cx + w0 / 2 + a, y0], [cx + w0 / 2 + b, y1], [cx - w0 / 2 + b, y1]]; sh.closed = true;
        mk.property('ADBE Mask Shape').setValue(sh);
        mk.property('ADBE Mask Offset').expression = wx(Math.max(0, Math.min(n - 1, k)), k < 0 || k >= n) + 'w>' + jzN(Wd * 2) + '?1e4:(w<=0?' + jzN(-w0) + ':(w-' + jzN(w0) + ')/2)';
    }
} });

/* ================================================================ EXITS */

/* hrPulledDown \u2014 \u5F15\u304D\u305A\u308A\u8FBC\u307F: yanked down one by one; the last one clings and trembles first */
jzReg('exit', 'hrPulledDown', { apply: function (m) {
    var L = m.L, S = m.size, cs = ahr3_cs(m), N = ahr3_N(m), idx = ahr3_idx(m), last = ahr_h(cs, 41) % N, T0 = [], LA = [], RR = [], i;
    for (i = 0; i < idx.length; i++) { var ix = Math.max(0, idx[i]); T0.push(ix === last ? 0.7 : ahr_r(cs, ix, 42) * 0.5); LA.push(ix === last ? 1.6 : 0.6); RR.push(ahr_rs(cs, ix, 46)); }
    var q = m.HD + 'var T=' + ahr_arr(T0) + ',j=textIndex-1,q=(PO-(T[j]||0))/0.28;';
    var tr = 'var LA=' + ahr_arr(LA) + ',tr=(PO>0&&q<0)?cl(1+q*1.5)*LA[j]:0;posterizeTime(24);seedRandom(textIndex*17+SD+Math.floor(time*24)*5,true);';
    jzAnimator(L, 'hr Out Tremble', [['ADBE Text Position 3D', [S * 0.03, S * 0.02, 0]]], q + tr + '[random(-1,1)*tr*100,random(-1,1)*tr*100,0]');
    jzAnimator(L, 'hr Out Tremble Tilt', [['ADBE Text Rotation', 6]], q + tr + 'random(-1,1)*tr*100');
    jzAnimator(L, 'hr Out Pull', [['ADBE Text Position 3D', [0, m.H * 1.1, 0]]], q + 'var qq=cl(q);qq*qq*100');
    jzAnimator(L, 'hr Out Pull Stretch', [['ADBE Text Scale 3D', [70, 280, 100]]], q + 'cl(q)*100');
    jzAnimator(L, 'hr Out Pull Tilt', [['ADBE Text Rotation', 12]], q + 'var RR=' + ahr_arr(RR) + ';RR[j]*cl(q)*100');
    jzAnimator(L, 'hr Out Pull Fade', [['ADBE Text Opacity', 0]], q + 'q>=1?100:ic(cl(q))*100');
} });

/* hrLookBack \u2014 \u4E00\u5B57\u6B8B\u308B: the line vanishes except one glyph, which turns to look, then is gone */
jzReg('exit', 'hrLookBack', { apply: function (m) {
    var L = m.L, cs = ahr3_cs(m), N = ahr3_N(m), idx = ahr3_idx(m), txt = jzChars(jzStrip(m.c.text)), k, i;
    var pick = ahr_h(cs, 51) % N;
    for (k = 0; k < N; k++) { var j = (pick + k) % N; if (jzIsKanji(txt[j] || ' ') || /[A-Za-z]/.test(txt[j] || '')) { pick = j; break; } }
    var dir = (ahr_h(cs, 52) & 1) ? 1 : -1, F = [];
    for (i = 0; i < idx.length; i++) F.push(idx[i] === pick ? 1 : 0);
    var q = m.HD + AHR_FNS + 'var F=' + ahr_arr(F) + ',me=F[textIndex-1]==1,q=ios((PO-0.12)/0.55);';
    jzAnimator(L, 'hr Out Leave', [['ADBE Text Opacity', 0]], q + 'PO<=0?0:(me?(PO>=0.86?100:0):(PO<0.06?60:100))');
    jzAnimator(L, 'hr Out Look', [['ADBE Text Rotation', 24 * dir], ['ADBE Text Scale 3D', [116, 116, 100]]], q + 'me&&PO>0?q*100:0');
    jzAnimator(L, 'hr Out Look Twitch', [['ADBE Text Rotation', 6]], q + 'posterizeTime(24);seedRandom(SD+Math.floor(time*24)*3,true);var a=random(),b=random(-1,1);me&&PO>0&&a<0.12?b*100:0');
    jzAnimator(L, 'hr Out Look Red', [['ADBE Text Fill Color', jzHex(m.ctx.sc.accent)]], q + 'me&&q>0.3?100:0');
} });

/* hrTurnAway \u2014 \u80CC\u3092\u5411\u3051\u308B: each glyph turns its back (edge-on, reversed, darkened) and fades */
jzReg('exit', 'hrTurnAway', { apply: function (m) {
    var L = m.L, S = m.size, ord = ahr3_ord(m);
    var q = m.HD + AHR_FNS + 'var O=' + ahr_arr(ord) + ',q=cl((PO-(O[textIndex-1]||0)*0.4)/0.6);';
    jzAnimator(L, 'hr Out Turn', [['ADBE Text Scale 3D', [-100, 100, 100]]], q + 'var sx=Math.cos(q*Math.PI*0.95);if(Math.abs(sx)<0.04)sx=0.04;[(1-sx)/2*100,0,0]');
    jzAnimator(L, 'hr Out Turn Dark', [['ADBE Text Fill Color', jzHex(ahr_night(m.ctx.sc))]], q + 'q>0.5?(0.35+q*0.5)*100:0');
    jzAnimator(L, 'hr Out Turn Fade', [['ADBE Text Opacity', 0], ['ADBE Text Position 3D', [0, S * 0.08, 0]]], q + 'q>=1?100:sm(0.6,1,q)*100');
} });

/* hrShiver \u2014 \u9707\u3048\u3066\u6D88\u3048\u308B: a tremor that keeps growing, and glyphs drop out of existence between frames */
jzReg('exit', 'hrShiver', { apply: function (m) {
    var L = m.L, S = m.size, cs = ahr3_cs(m), idx = ahr3_idx(m), T0 = [], i;
    for (i = 0; i < idx.length; i++) T0.push(0.3 + ahr_r(cs, Math.max(0, idx[i]), 61) * 0.62);
    var q = m.HD + 'var T=' + ahr_arr(T0) + ',j=textIndex-1;';
    jzAnimator(L, 'hr Out Gone', [['ADBE Text Opacity', 0]], q + 'PO>0&&PO>=T[j]?100:0');
    jzAnimator(L, 'hr Out Shiver', [['ADBE Text Position 3D', [S * 0.155, S * 0.155, 0]]], q + 'posterizeTime(24);seedRandom(textIndex*29+SD+Math.floor(time*24)*7,true);var a=PO>0?(0.025+0.13*PO)/0.155*100:0;[random(-1,1)*a,random(-1,1)*a,0]');
    jzAnimator(L, 'hr Out Shiver Tilt', [['ADBE Text Rotation', 14]], q + 'posterizeTime(24);seedRandom(textIndex*37+SD+Math.floor(time*24)*11,true);random(-1,1)*PO*100');
} });

/* hrSwallow \u2014 \u95C7\u306B\u5451\u307E\u308C\u308B: a stain of darkness spreads from one point and eats the line */
jzReg('exit', 'hrSwallow', { apply: function (m) {
    var L = m.L, S = m.size, r = jzRect(L), seed = (m.c.seed | 0) + ((m.o && m.o.mi) || 0) * 101, i;
    var bx0 = r.left, bx1 = r.left + r.width, by0 = r.top, by1 = r.top + r.height;
    var cx = jzLerp(bx0, bx1, ahr_rr(0.15, 0.85, seed, 71)), cy = jzLerp(by0, by1, ahr_rr(0.3, 0.7, seed, 72));
    var dxm = Math.max(cx - bx0, bx1 - cx), dym = Math.max(cy - by0, by1 - cy), Rmax = Math.sqrt(dxm * dxm + dym * dym) + S * 0.2;
    var RX = 'var R=' + jzN(Rmax * 1.08) + '*ic(cl(PO/0.75));';
    // the text is cut away inside the stain (a subtractive mask that grows) ...
    var rb = Rmax * 0.3, mk = L.property('ADBE Mask Parade').addProperty('ADBE Mask Atom'), sh = new Shape(), pts = [];
    for (i = 0; i < 22; i++) { var a = i / 22 * Math.PI * 2, rr = rb * (0.72 + 0.4 * ahr_r(seed, i, 73)); pts.push([cx + Math.cos(a) * rr, cy + Math.sin(a) * rr]); }
    sh.vertices = pts; sh.closed = true;
    mk.property('ADBE Mask Shape').setValue(sh);
    mk.maskMode = MaskMode.SUBTRACT;
    mk.property('ADBE Mask Opacity').expression = m.HD + 'PO>0?100:0';
    mk.property('ADBE Mask Offset').expression = m.HD + RX + 'PO<=0?' + jzN(-rb * 2) + ':R-' + jzN(rb * 0.9);
    m.parts.op.push('if(PO>0.97)f=0;');
    // ... and the stain itself is drawn over it (rides on the text layer)
    var B = ahr3_rider(m, L, 'hr swallow'), cp = ahr3_toComp(L, [cx, cy]), bp = [];
    for (i = 0; i < 22; i++) { var a2 = i / 22 * Math.PI * 2, r2 = Rmax * 1.08 * (0.72 + 0.4 * ahr_r(seed, i, 73)); bp.push([Math.cos(a2) * r2, Math.sin(a2) * r2]); }
    var g = jzGrp(B, 'stain'); jzAddPath(g, bp, true); jzAddFill(g, ahr_night(m.ctx.sc));
    jzXf(B, 'ADBE Position').setValue(cp);
    jzSetExpr(jzXf(B, 'ADBE Scale'), m.HD + 'var R=ic(cl(PO/0.75));var k=value[0]*R*(1+0.04*Math.sin(PO*9));[k,k]');
    jzSetExpr(jzXf(B, 'ADBE Opacity'), m.HD + AHR_FNS + 'PO<=0?0:85*(1-sm(0.62,0.95,PO))');
    jzNoGhost(B);
} });

/* hrFlickerDie \u2014 \u660E\u6EC5\u3057\u3066\u6D88\u3048\u308B: the words strobe out, with single frames where they come back wrong */
jzReg('exit', 'hrFlickerDie', { apply: function (m) {
    var rnd = 'posterizeTime(24);seedRandom(SD+Math.floor(time*24)*7,true);var r1=random(),r2=random(),r3=random(-1,1),r4=random();var hid=PO>0.92||r1>Math.pow(1-PO,1.3)*0.95,mir=!hid&&r2<0.22&&PO>0.15;';
    m.parts.op.push('if(PO>0){' + rnd + 'f*=hid?0:(mir?1:0.55+0.45*r4);}');
    m.parts.sc.push('if(PO>0){' + rnd + 'if(mir)f=[-f[0],f[1]];}');
    m.parts.pos.push('if(PO>0){' + rnd + 'if(mir)d=[d[0],d[1]+r3*SZ*0.1];}');
    jzAnimator(m.L, 'hr Out Wrong', [['ADBE Text Fill Color', jzHex(m.ctx.sc.accent)]], m.HD + 'var mir=false;if(PO>0){' + rnd + '}mir?100:0');
} });

/* hrDrain \u2014 \u6EF4\u308A\u843D\u3061\u308B: the ink level sinks inside the glyphs while drips run out of the bottom */
jzReg('exit', 'hrDrain', { apply: function (m) {
    var L = m.L, S = m.size, cs = ahr3_cs(m), idx = ahr3_idx(m), sl = ahr_slots(L), i, d;
    var r = jzRect(L), hasMatte = false; try { hasMatte = L.trackMatteType && L.trackMatteType !== TrackMatteType.NO_TRACK_MATTE; } catch (e0) {}
    var lev = m.HD + AHR_FNS + 'var lv=PO<=0?0:ios((PO-0.12)/0.72);';
    if (!hasMatte) {
        // the ink level: an inverted matte whose lower edge sinks through the text block
        var pad = S * 0.3, top = r.top - pad, hb = r.height + pad * 2, M = jzShapeLayer(m.ctx, 'hr drain level', 0, 0), gm = jzGrp(M, 'level');
        var rc = jzAddRect(gm, r.width + pad * 4, 1, 0);
        rc.property('ADBE Vector Rect Size').expression = lev + '[' + jzN(r.width + pad * 4) + ',Math.max(0,lv*' + jzN(hb) + ')]';
        rc.property('ADBE Vector Rect Position').expression = lev + '[0,Math.max(0,lv*' + jzN(hb) + ')/2]';
        jzAddFill(gm, '#FFFFFF');
        jzXf(M, 'ADBE Position').setValue(ahr3_toComp(L, [r.left + r.width / 2, top]));
        M.moveBefore(L); M.parent = L;
        L.trackMatteType = TrackMatteType.ALPHA_INVERTED;
        jzNoGhost(M);
    } else {
        var lw = jzEffect(L, 'ADBE Linear Wipe', 'hr Out Drain');
        jzEP(lw, 2, 180); jzEP(lw, 3, S * 0.25);
        jzEX(lw, 1, lev + '100*lv');
    }
    // drips below the glyphs
    var B = ahr3_rider(m, L, 'hr drain drips'), g = jzGrp(B, 'drips'), cnt = 0, col = jzTextColor(L), p0 = ahr3_toComp(L, [0, 0]);
    for (i = 0; i < sl.length && cnt < 16; i++) {
        if (sl[i].sp) continue;
        var k = ahr_h(cs, Math.max(0, idx[i]), 91), nd = 1 + (k % 2);
        for (d = 0; d < nd && cnt < 16; d++) {
            var x = sl[i].x + ahr_rs(k, d, 1) * sl[i].w * 0.3, yb = sl[i].y + S * 0.4, Ld = S * (0.3 + 1.4 * ahr_r(k, d, 2));
            jzAddPath(g, [[x - p0[0], yb - S * 0.05 - p0[1]], [x - p0[0], yb + Ld - p0[1]]], false); cnt++;
        }
    }
    if (!cnt) jzAddPath(g, [[0, 0], [0, 1]], false);
    var st = jzAddStroke(g, col, Math.max(1, S * 0.035)); try { st.property('ADBE Vector Stroke Line Cap').setValue(2); } catch (e) {}
    jzAddTrimPaths(g, m.HD + AHR_FNS + '100*oc(ios(PO/0.85))');
    jzXf(B, 'ADBE Position').setValue(p0);
    ahr3_attach(B, L);
    jzSetExpr(jzXf(B, 'ADBE Opacity'), m.HD + AHR_FNS + 'PO<=0?0:100*(1-sm(0.75,1,PO))');
    jzNoGhost(B);
} });

/* ================================================================ HOLDS */

/* hrTwitch \u2014 \u75D9\u6523: dead still, then a rare violent jerk of one glyph (or the whole line) */
jzReg('hold', 'hrTwitch', { apply: function (m) {
    var S = m.size, kM = ahr3_motionK(m);
    var q = m.HD + 'posterizeTime(12);seedRandom(SD+Math.floor(time*12)*13,true);var kk=AMT*' + jzN(kM) + ';var go=random()<0.07*kk*2,wh=random()<0.25,tg=Math.floor(random()*textTotal)+1;' +
        'var a1=random(-1,1),a2=random(-1,1),a3=random(-1,1),a4=random();var on=kk>0.02&&go&&(wh||textIndex==tg);';
    jzAnimator(m.L, 'hr Hold Twitch', [['ADBE Text Position 3D', [S * 0.22, S * 0.14, 0]]], q + 'on?[a1*kk*100,a2*kk*100,0]:[0,0,0]');
    jzAnimator(m.L, 'hr Hold Twitch Tilt', [['ADBE Text Rotation', 28]], q + 'on?a3*kk*100:0');
    jzAnimator(m.L, 'hr Hold Twitch Size', [['ADBE Text Scale 3D', [112, 112, 100]]], q + 'on?a4*kk*100:0');
} });

/* hrStare \u2014 \u898B\u3064\u3081\u308B\u5B57: now and then one glyph slowly tilts its head towards you, holds, and snaps back */
jzReg('hold', 'hrStare', { apply: function (m) {
    var S = m.size, kM = ahr3_motionK(m);
    var q = m.HD + 'var t=time-0.3,kk=AMT*' + jzN(kM) + ',v=0,dr=1,tg=-1;if(t>0&&kk>0.02){var cy=Math.floor(t/2),u=(t-cy*2)/2;tg=Math.floor(hh(cy*111+SD)*textTotal)+1;dr=hh(cy*112+SD)<0.5?1:-1;' +
        'v=u<0.6?-(Math.cos(Math.PI*u/0.6)-1)/2:(u<0.85?1:1-oe((u-0.85)/0.15));}var on=textIndex==tg;';
    jzAnimator(m.L, 'hr Hold Stare', [['ADBE Text Rotation', 22]], q + 'on?dr*v*kk*100:0');
    jzAnimator(m.L, 'hr Hold Stare Lean', [['ADBE Text Scale 3D', [112, 112, 100]], ['ADBE Text Position 3D', [0, -S * 0.04, 0]]], q + 'on?v*kk*100:0');
} });

/* hrLagOne \u2014 \u9045\u308C\u308B\u4E00\u5B57: the line sways together, one glyph follows a moment too late */
jzReg('hold', 'hrLagOne', { apply: function (m) {
    var S = m.size, kM = ahr3_motionK(m), cs = ahr3_cs(m), N = ahr3_N(m), idx = ahr3_idx(m), late = ahr_h(cs, 121) % N, F = [], i;
    for (i = 0; i < idx.length; i++) F.push(idx[i] === late ? 1 : 0);
    var sd = cs % 97;
    var q = m.HD + AHR_FNS + 'var F=' + ahr_arr(F) + ',kk=AMT*' + jzN(kM) + ',tt=time-(F[textIndex-1]==1?0.75:0);';
    jzAnimator(m.L, 'hr Hold Sway', [['ADBE Text Position 3D', [S * 0.1, S * 0.06, 0]]], q + '[nz(tt*0.9,' + sd + ')*kk*100,nz(tt*0.7+5,' + (sd + 1) + ')*kk*100,0]');
    jzAnimator(m.L, 'hr Hold Sway Tilt', [['ADBE Text Rotation', 4]], q + 'nz(tt*0.5+9,' + (sd + 2) + ')*kk*100');
} });

/* hrFlickerLight \u2014 \u5207\u308C\u304B\u3051\u306E\u706F: the words buzz, dim and drop out for a frame or two */
jzReg('hold', 'hrFlickerLight', { apply: function (m) {
    var kM = jzClamp(ahr3_motionK(m), 0.4, 1.3);
    var lv = 'posterizeTime(24);var st=Math.floor(time*24),run=Math.floor(st/2),kk=AMT*' + jzN(kM) + ';seedRandom(SD+st*3,true);var lv=0.9+0.1*random();seedRandom(SD+run*5+1,true);' +
        'if(random()<0.09*kk){seedRandom(SD+st*7+2,true);lv=random()<0.5?0.08:0.35;}else{seedRandom(SD+st*11+3,true);if(random()<0.05*kk)lv=0.5;}var al=kk<0.02?1:1-(1-lv)*Math.min(1,kk);';
    m.parts.op.push(lv + 'f*=al;');
    jzAnimator(m.L, 'hr Hold Dim', [['ADBE Text Fill Color', jzHex(jzMixHex(jzTextColor(m.L), m.ctx.sc.bg, 0.3))]], m.HD + lv + 'al<0.6?100:0');
} });

/* ================================================================ TREATMENTS */
function ahr3_alive(L) { try { var td = L.property('ADBE Text Properties').property('ADBE Text Document').value; return td.applyFill !== false && jzFontSize(L) > 1 && jzTrim(td.text) !== ''; } catch (e) { return false; } }

/* hrInkBleed \u2014 \u6EF2\u307F\u5782\u308C: a dark halo seeps out of the letters and thin drips run down from some of them */
jzReg('treat', 'hrInkBleed', {
    plan: function (rng, st) { return { drips: rng.range(0.25, 0.45), red: rng.chance(0.4) }; },
    apply: function (ctx, L, P, o) {
        if (!ahr3_alive(L)) return;
        var sc = ctx.sc, c = jzTextColor(L), halo = P.red ? sc.accent : jzMixHex(c, sc.bg, ahr_dark(sc.bg) ? 0.5 : 0.3), sz = jzFontSize(L), cs = ctx.cut.seed | 0, i;
        var single = jzCount(ahr3_text(L)) === 1 && jzCount(ctx.cut.text) > 4;
        if (!single) {
            var ds = jzEffect(L, 'ADBE Drop Shadow', 'hr Bleed');
            jzEP(ds, 1, jzHex(halo)); jzEP(ds, 2, 0.8 * 255); jzEP(ds, 3, 180); jzEP(ds, 4, sz * 0.02); jzEP(ds, 5, sz * 0.14);
        }
        var sl = ahr_slots(L), rate = P.drips || 0.35, p0 = ahr3_toComp(L, [0, 0]), pts = [];
        for (i = 0; i < sl.length; i++) {
            if (sl[i].sp || jzIsPunct(sl[i].ch)) continue;
            var k = ahr_h(cs, i, Math.round(sl[i].x), 141);
            if (ahr_r(k, 1) > rate) continue;
            var x = sl[i].x + ahr_rs(k, 2) * sl[i].w * 0.25, y = sl[i].y + sz * 0.36, Ld = sz * (0.2 + 0.9 * ahr_r(k, 3));
            pts.push([[x - p0[0], y - p0[1]], [x + ahr_rs(k, 5) * sz * 0.02 - p0[0], y + Ld - p0[1]]]);
        }
        if (!pts.length) return;
        var B = ahr3_rider(ctx, L, 'hr bleed drips'), g = jzGrp(B, 'drips');
        for (i = 0; i < pts.length; i++) jzAddPath(g, pts[i], false);
        var st = jzAddStroke(g, P.red ? sc.accent : c, Math.max(1, sz * 0.028)); try { st.property('ADBE Vector Stroke Line Cap').setValue(2); } catch (e) {}
        var cut = ctx.cut, d0 = (cut.inDur || 0.3) * 1.1;
        jzAddTrimPaths(g, JZ_FNS + '100*oc((time-' + jzN(d0) + ')/' + jzN(Math.max(0.8, cut.dur * 0.7)) + ')');
        jzXf(B, 'ADBE Position').setValue(p0);
        ahr3_attach(B, L);
        jzSetExpr(jzXf(B, 'ADBE Opacity'), jzTH(ctx) + '90*(1-PO)');
        jzNoGhost(B);
    }
});

/* hrEroded \u2014 \u98A8\u5316: letters worn away with pits and scratches (an inverted alpha matte riding on the text) */
jzReg('treat', 'hrEroded', {
    plan: function (rng, st) { return { dens: rng.range(0.7, 1.2), scr: rng.chance(0.7) }; },
    apply: function (ctx, L, P, o) {
        if (!ahr3_alive(L)) return;
        try { if (L.trackMatteType && L.trackMatteType !== TrackMatteType.NO_TRACK_MATTE) return; } catch (e0) {}
        var cs = ctx.cut.seed | 0, sz = jzFontSize(L), dens = P.dens || 1, sl = ahr_slots(L), p0 = ahr3_toComp(L, [0, 0]), i, j, budget = 200;
        var M = jzShapeLayer(ctx, 'hr erosion', 0, 0), gp = jzGrp(M, 'pits'), sc2 = [];
        for (i = 0; i < sl.length; i++) {
            if (sl[i].sp) continue;
            var k = ahr_h(cs, i, 151), n = Math.round((8 + ahr_r(k, 1) * 7) * dens), gw = sl[i].w, gh = sz;
            for (j = 0; j < n && budget-- > 0; j++) {
                var r = sz * (0.012 + 0.05 * Math.pow(ahr_r(k, j, 2), 2));
                jzAddEllipse(gp, r * 2, r * 2, sl[i].x + ahr_rs(k, j, 3) * gw * 0.45 - p0[0], sl[i].y + ahr_rs(k, j, 4) * gh * 0.45 - p0[1]);
            }
            if (P.scr && ahr_r(k, 5) < 0.45) { var a = ahr_r(k, 6) * Math.PI, Ls = gw * 0.6; sc2.push([[sl[i].x - Math.cos(a) * Ls / 2 - p0[0], sl[i].y - Math.sin(a) * Ls / 2 - p0[1]], [sl[i].x + Math.cos(a) * Ls / 2 - p0[0], sl[i].y + Math.sin(a) * Ls / 2 - p0[1]]]); }
        }
        jzAddFill(gp, '#000000');
        if (sc2.length) { var gs = jzGrp(M, 'scratches'); for (i = 0; i < sc2.length; i++) jzAddPath(gs, sc2[i], false); jzAddStroke(gs, '#000000', Math.max(1.5, sz * 0.022)); }
        jzXf(M, 'ADBE Position').setValue(p0);
        M.moveBefore(L);
        ahr3_attach(M, L);
        L.trackMatteType = TrackMatteType.ALPHA_INVERTED;
        jzNoGhost(M);
    }
});

/* hrRedact \u2014 \u9ED2\u5857\u308A: a bar covers part of the line and is pulled off later, leaving its outline */
jzReg('treat', 'hrRedact', {
    plan: function (rng, st) { return { at: rng.range(0.25, 0.5), frac: rng.range(0.3, 0.6), from: rng.range(0, 1) }; },
    apply: function (ctx, L, P, o) {
        if (!ahr3_alive(L)) return;
        var frac = P.frac || 0.45, from = P.from == null ? 0.5 : P.from, N = jzCount(ctx.cut.text), n1 = jzCount(ahr3_text(L));
        if (n1 === 1 && N > 1) { var od = jzClamp(((o && o.mi) || 0) / (N - 1), 0, 1), a0 = from * (1 - frac); if (od < a0 || od > a0 + frac) return; }
        var gl = ahr_nonSp(ahr_slots(L)); if (!gl.length) return;
        var s0 = 0, s1 = gl.length - 1, sz = jzFontSize(L), i;
        if (gl.length > 2) { var mm = Math.max(1, Math.round(gl.length * frac)); s0 = Math.min(gl.length - mm, Math.floor(from * (gl.length - mm + 1))); s1 = s0 + mm - 1; }
        var x0 = 1e9, x1 = -1e9, y0 = 1e9, y1 = -1e9;
        for (i = s0; i <= s1; i++) { x0 = Math.min(x0, gl[i].x - gl[i].w / 2); x1 = Math.max(x1, gl[i].x + gl[i].w / 2); y0 = Math.min(y0, gl[i].y - sz * 0.5); y1 = Math.max(y1, gl[i].y + sz * 0.5); }
        var pd = sz * 0.06, c = jzTextColor(L), cut = ctx.cut, t0 = (cut.inDur || 0.3) + (cut.dur - (cut.inDur || 0.3) - (cut.outDur || 0.2)) * (P.at || 0.35);
        var w = x1 - x0 + pd * 2, h = y1 - y0 + pd, ke = ahr_TH(ctx) + 'var k=ioc((time-' + jzN(t0) + ')/0.35);';
        var B = ahr3_rider(ctx, L, 'hr redact bar'), xr = x1 + pd, ym = (y0 + y1) / 2;
        var go = jzGrp(B, 'outline'); jzAddRect(go, w, h, 0, -w / 2, 0); jzAddStroke(go, ctx.sc.sub, Math.max(1, sz * 0.01));
        jzGX(go).property('ADBE Vector Group Opacity').expression = ke + '50*k';
        var gb = jzGrp(B, 'bar'); jzAddRect(gb, w, h, 0, -w / 2, 0); jzAddFill(gb, c);
        jzGX(gb).property('ADBE Vector Scale').expression = ke + '[100*(1-k),100]';
        jzXf(B, 'ADBE Position').setValue([xr, ym]);
        ahr3_attach(B, L);
        jzSetExpr(jzXf(B, 'ADBE Opacity'), jzTH(ctx) + '100*K');
        jzNoGhost(B);
    }
});

/* hrDoubleExp \u2014 \u4E8C\u91CD\u9732\u5149: a second, fainter take of the line drifts out of register */
jzReg('treat', 'hrDoubleExp', {
    plan: function (rng, st) { return { amp: rng.range(0.18, 0.28), k: rng.range(1.03, 1.08) }; },
    apply: function (ctx, L, P, o) {
        if (!ahr3_alive(L)) return;
        var sc = ctx.sc, own = jzTextColor(L), cands = [sc.sub, sc.ghostA, sc.fg], col = sc.sub, i, cs = ctx.cut.seed | 0, sz = jzFontSize(L), amp = P.amp || 0.2;
        for (i = 0; i < cands.length; i++) if (cands[i] && String(cands[i]).toUpperCase() !== String(own).toUpperCase() && jzContrast(cands[i], sc.bg) > 1.3) { col = cands[i]; break; }
        var D = L.duplicate();
        D.moveAfter(L); D.name = 'hr double';
        try { D.trackMatteType = TrackMatteType.NO_TRACK_MATTE; } catch (e0) {}
        var fl = jzEffect(D, 'ADBE Fill', 'hr Double Colour'); jzEP(fl, 3, jzHex(col));
        var tf = jzEffect(D, 'ADBE Geometry2', 'hr Double Drift'), sd = cs % 97;
        jzEX(tf, 2, AHR_FNS + JZ_FNS + '[value[0]+nz(time*0.6,' + (sd + 3) + ')*' + jzN(sz * amp) + ',value[1]+nz(time*0.5,' + (sd + 4) + ')*' + jzN(sz * amp * 0.7) + ']');
        jzEP(tf, 4, (P.k || 1.05) * 100);
        jzXf(D, 'ADBE Opacity').setValue(40);
        jzNoGhost(D);
    }
});

// ================================================================ pack horror part 4 (AE port of the horror set: decor / bg / cam / fx / trans)
// helpers: ahr_ (p_horror1)

/* ---------------------------------------------------------------- helpers (ahr4_) */
function ahr4_bb(ctx, bb) { return bb || { x0: ctx.W * 0.35, x1: ctx.W * 0.65, y0: ctx.H * 0.4, y1: ctx.H * 0.6, cx: ctx.W / 2, cy: ctx.H / 2 }; }
function ahr4_over(bb, x0, y0, x1, y1, pad) { pad = pad || 0; return !(x1 < bb.x0 - pad || x0 > bb.x1 + pad || y1 < bb.y0 - pad || y0 > bb.y1 + pad); }
// seconds the background had been running at the cut start (browser bgT: consecutive cuts with the same bg + seed count as one run)
function ahr4_run(b) {
    var c = b.cut, cs = (b.plan && b.plan.cuts) || [], s = c.start || 0, P0 = c.bgP || {}, idx = -1, i;
    for (i = 0; i < cs.length; i++) if (cs[i] === c) { idx = i; break; }
    for (i = idx - 1; i >= 0; i--) { var p = cs[i]; if (p.bg === c.bg && (p.bgP || {}).seed === P0.seed && Math.abs(p.end - s) < 0.06) s = p.start; else break; }
    return (c.start || 0) - s;
}
// bg expression header: T = song time (browser env.t), BT = time since the background started, FI = its 0.6 s fade-in
function ahr4_bh(b, fin) { return JZ_FNS + AHR_FNS + 'function wr(v,m){return ((v%m)+m)%m;}var T=time+' + jzN(b.cut.start || 0) + ',BT=time+' + jzN(ahr4_run(b)) + ',FI=oc(BT/' + jzN(fin || 0.6) + ');\n'; }
// failing-light level 0..1 on a 24 Hz clock (browser lamp(t, seed, rate)) -> var lv
function ahr4_lamp(seed, rate, tv) {
    return 'var lst=Math.floor(' + tv + '*24),lrun=Math.floor(lst/4),lv;seedRandom(' + (seed % 99991) + '+lrun*7,true);if(random()<' + jzN(0.1 * (rate || 1)) + '){seedRandom(' + (seed % 99991) + '+lst*13+1,true);lv=random()<0.5?0.15:0.55;}else{seedRandom(' + (seed % 99991) + '+lst*17+2,true);lv=0.88+0.12*random();}\n';
}
function ahr4_solid(comp, hex, name, W, H) { return comp.layers.addSolid(jzHex(hex), name, Math.max(4, Math.round(W)), Math.max(4, Math.round(H)), 1, comp.duration); }
function ahr4_ellMask(L, cx, cy, r, fe, inv, sy) {
    var m = L.property('ADBE Mask Parade').addProperty('ADBE Mask Atom'), sh = jzCircleShape(cx, cy, r), i;
    if (sy && sy !== 1) { var v = sh.vertices, it = sh.inTangents, ot = sh.outTangents; for (i = 0; i < 4; i++) { v[i] = [v[i][0], cy + (v[i][1] - cy) * sy]; it[i] = [it[i][0], it[i][1] * sy]; ot[i] = [ot[i][0], ot[i][1] * sy]; } sh.vertices = v; sh.inTangents = it; sh.outTangents = ot; }
    m.property('ADBE Mask Shape').setValue(sh);
    if (fe) m.property('ADBE Mask Feather').setValue([fe, fe]);
    if (inv) m.inverted = true;
    return m;
}

/* ================================================================ DECOR */

/* hrScratches \u2014 \u5F15\u3063\u63BB\u304D\u50B7: parallel jagged gouges scraped in around the words */
jzReg('decor', 'hrScratches', { back: false, build: function (ctx, bb0, d) {
    var bb = ahr4_bb(ctx, bb0), W = ctx.W, H = ctx.H, sc = ctx.sc, s = d.seed | 0, u = jzU(ctx), n = d.n || 2, g, k, i;
    var S = jzShapeLayer(ctx, 'hr scratches', 0, 0), TH0 = jzTH(ctx);
    for (g = 0; g < n; g++) {
        var corner = (g + (d.v | 0)) % 4, right = corner % 2 === 1, low = corner >= 2;
        var Lg = u * ahr_rr(0.28, 0.42, s, g, 1), ang = (right ? -1 : 1) * ahr_rr(55, 75, s, g, 2) * Math.PI / 180 * (low ? -1 : 1);
        var cx = right ? ahr_rr(W * 0.72, W * 0.9, s, g, 3) : ahr_rr(W * 0.1, W * 0.28, s, g, 3), cy = low ? ahr_rr(H * 0.72, H * 0.88, s, g, 4) : ahr_rr(H * 0.12, H * 0.28, s, g, 4);
        if (ahr4_over(bb, cx - Lg / 2, cy - Lg / 2, cx + Lg / 2, cy + Lg / 2, u * 0.02)) cy = low ? Math.max(cy, bb.y1 + Lg * 0.6) : Math.min(cy, bb.y0 - Lg * 0.6);
        var dx = Math.sin(ang), dy = -Math.cos(ang), px = Math.cos(ang), py = Math.sin(ang);
        var col = (g % 2 && jzContrast(sc.accent, sc.bg) > 1.8) ? sc.accent : sc.fg, gr = jzGrp(S, 'gouge ' + g);
        for (k = 0; k < 4; k++) {
            var off = (k - 1.5) * u * 0.042, len = Lg * (0.75 + 0.3 * ahr_r(s, g, k, 5)), st = ahr_r(s, g, k, 6) * Lg * 0.1;
            var x0 = cx + px * off - dx * Lg / 2 + dx * st, y0 = cy + py * off - dy * Lg / 2 + dy * st, pts = [];
            for (i = 0; i <= 10; i++) { var f = i / 10, j = ahr_rs(s, g, k, i) * u * 0.004; pts.push([x0 + dx * len * f + px * j, y0 + dy * len * f + py * j]); }
            jzAddPath(gr, pts, false);
        }
        var stk = jzAddStroke(gr, col, Math.max(1, u * 0.008), 85); try { stk.property('ADBE Vector Stroke Line Cap').setValue(2); } catch (e) {}
        jzAddTrimPaths(gr, TH0 + '100*oe((time-' + jzN(0.08 + g * 0.18) + ')/0.16)');
    }
    jzSetExpr(jzXf(S, 'ADBE Opacity'), TH0 + '100*K');
    jzNoGhost(S);
} });

/* hrSigil \u2014 \u9B54\u6CD5\u9663: a slow-turning ring of marks and a seven-pointed star in faint lines behind the words */
jzReg('decor', 'hrSigil', { back: true, build: function (ctx, bb0, d) {
    var bb = ahr4_bb(ctx, bb0), sc = ctx.sc, s = d.seed | 0, u = jzU(ctx), i;
    var cx = (bb.x0 + bb.x1) / 2, cy = (bb.y0 + bb.y1) / 2, R = Math.min(u * 0.46, Math.max(bb.x1 - bb.x0, bb.y1 - bb.y0) * 0.62 + u * 0.08);
    var col = jzLayC(sc, ahr_dark(sc.bg) ? 0.3 : 0.28), lw = Math.max(1.2 * ahr_px(ctx), u * 0.0022), dir = d.right ? -1 : 1, TH0 = ahr_TH(ctx), e = 'var e=ios(time/1.2);';
    var S = jzShapeLayer(ctx, 'hr sigil', cx, cy);
    // ring of marks between the circles (turns the other way)
    var gm = jzGrp(S, 'marks'), m = 42;
    for (i = 0; i < m; i++) {
        var an = i / m * Math.PI * 2, t = ahr_h(s, i, 3) % 4, r0 = R * 0.915, r1 = R * 0.985, c = Math.cos(an), sn = Math.sin(an), q = (r0 + r1) / 2, dd = R * 0.018;
        if (t === 0) jzAddPath(gm, [[c * r0, sn * r0], [c * r1, sn * r1]], false);
        else if (t === 1) jzAddEllipse(gm, R * 0.024, R * 0.024, c * q, sn * q);
        else if (t === 2) jzAddPath(gm, [[c * q - sn * dd, sn * q + c * dd], [c * q + sn * dd, sn * q - c * dd]], false);
    }
    jzAddStroke(gm, col, lw);
    jzAddTrimPaths(gm, TH0 + e + '100*e');
    jzGX(gm).property('ADBE Vector Rotation').expression = '-time*' + jzN(4 * 0.6 * dir);
    // heptagram {7/3}
    var pts = [];
    for (i = 0; i < 7; i++) { var a2 = -Math.PI / 2 + (i * 3 % 7) / 7 * Math.PI * 2; pts.push([Math.cos(a2) * R * 0.9, Math.sin(a2) * R * 0.9]); }
    pts.push(pts[0]);
    var gh = ahr_path(S, 'star', pts, { stroke: col, sw: lw });
    jzAddTrimPaths(gh, TH0 + '100*cl((time-0.3)/1.2)');
    jzGX(gh).property('ADBE Vector Rotation').expression = 'time*' + jzN(4 * dir);
    // three circles drawing themselves
    var rs = [[R, lw * 1.4, 100], [R * 0.9, lw, 100], [R * 0.62, lw, 80]];
    for (i = 0; i < 3; i++) {
        var gc = jzGrp(S, 'circle ' + i);
        jzAddEllipse(gc, rs[i][0] * 2, rs[i][0] * 2);
        jzAddStroke(gc, col, rs[i][1], rs[i][2]);
        jzAddTrimPaths(gc, TH0 + e + '100*e');
        jzGX(gc).property('ADBE Vector Rotation').setValue(i === 1 ? 180 : 0);
    }
    jzSetExpr(jzXf(S, 'ADBE Opacity'), TH0 + '100*K');
    jzNoGhost(S);
} });

/* hrWatchEye \u2014 \u898B\u3066\u3044\u308B\u76EE: an outline eye in a corner that opens, looks at the words and blinks at the wrong moments */
jzReg('decor', 'hrWatchEye', { back: false, build: function (ctx, bb0, d) {
    var bb = ahr4_bb(ctx, bb0), W = ctx.W, H = ctx.H, sc = ctx.sc, s = d.seed | 0, u = jzU(ctx), i, k;
    var ew = u * (d.big ? 0.22 : 0.16), eh = ew * 0.26;
    var x = d.right ? W - u * 0.07 - ew / 2 : u * 0.07 + ew / 2, y = d.low ? H - u * 0.08 - eh : u * 0.08 + eh;
    if (ahr4_over(bb, x - ew / 2, y - eh, x + ew / 2, y + eh, u * 0.02)) y = d.low ? Math.max(y, Math.min(H - eh * 1.2, bb.y1 + eh * 1.6)) : Math.min(y, Math.max(eh * 1.2, bb.y0 - eh * 1.6));
    var bl = [];
    for (k = 0; k < 3; k++) bl.push(0.9 + ahr_r(s, k, 5) * 3 + k * 1.3);
    var op = ahr_TH(ctx) + 'var o=oc((time-0.2)/0.6)*K,B=' + ahr_arr(bl) + ';for(var i=0;i<3;i++){var dd=Math.abs(time-B[i]);if(dd<0.08)o*=dd/0.08;}\n';
    var col = sc.fg, lw = Math.max(1.2 * ahr_px(ctx), u * 0.003), S = jzShapeLayer(ctx, 'hr eye', x, y);
    function lid(sg) { var p = []; for (var j = 0; j <= 16; j++) { var f = j / 16; p.push([-ew / 2 + ew * f, sg * Math.sin(Math.PI * f) * eh]); } return p; }
    var top = lid(-1);
    var gl = jzGrp(S, 'lashes');
    for (k = 0; k < 5; k++) { var f2 = 0.2 + k * 0.15, p = top[Math.round(f2 * 16)]; jzAddPath(gl, [p, [p[0] + (f2 - 0.5) * ew * 0.12, p[1] - eh * 0.35]], false); }
    jzAddStroke(gl, col, lw * 0.8, 80);
    jzGX(gl).property('ADBE Vector Scale').expression = op + '[100,100*o]';
    var gt = ahr_path(S, 'top lid', top, { stroke: col, sw: lw * 1.3 });
    jzGX(gt).property('ADBE Vector Scale').expression = op + '[100,100*o]';
    var gb = ahr_path(S, 'bottom lid', lid(1), { stroke: col, sw: lw });
    jzGX(gb).property('ADBE Vector Scale').expression = op + '[100,100*o]';
    // iris + pupil looking at the words (squeezed with the lids instead of clipped)
    var tx = (bb.x0 + bb.x1) / 2, ty = (bb.y0 + bb.y1) / 2, an = Math.atan2(ty - y, tx - x), look = Math.min(1, Math.sqrt((tx - x) * (tx - x) + (ty - y) * (ty - y)) / u);
    var gp = jzGrp(S, 'pupil'); jzAddEllipse(gp, eh * 0.68, eh * 0.68); jzAddFill(gp, ahr_dark(sc.bg) ? sc.accent : sc.fg);
    jzGX(gp).property('ADBE Vector Position').expression = op + '[' + jzN(Math.cos(an) * ew * 0.18 * look) + '+nz(time*0.8,' + (s % 97) + ')*' + jzN(ew * 0.03) + ',' + jzN(Math.sin(an) * eh * 0.3 * look) + ']';
    jzGX(gp).property('ADBE Vector Scale').expression = op + '[100,100*Math.min(1,o*1.2)]';
    var gi = jzGrp(S, 'iris'); jzAddEllipse(gi, eh * 1.56, eh * 1.56); jzAddStroke(gi, col, lw);
    jzGX(gi).property('ADBE Vector Position').expression = op + '[' + jzN(Math.cos(an) * ew * 0.18 * look) + ',' + jzN(Math.sin(an) * eh * 0.3 * look) + ']';
    jzGX(gi).property('ADBE Vector Scale').expression = op + '[100,100*o]';
    jzSetExpr(jzXf(S, 'ADBE Opacity'), jzTH(ctx) + '100*K*oc(time/0.2)');
    jzNoGhost(S);
} });

/* hrStaticPatch \u2014 \u7802\u5D50\u306E\u6B20\u7247: small rectangles of TV snow flicker at the edges of the frame */
jzReg('decor', 'hrStaticPatch', { back: false, build: function (ctx, bb0, d) {
    var bb = ahr4_bb(ctx, bb0), W = ctx.W, H = ctx.H, s = d.seed | 0, u = jzU(ctx), n = 3 + (d.n || 2) * 2, k, TH0 = jzTH(ctx);
    for (k = 0; k < n; k++) {
        var w = u * ahr_rr(0.06, 0.2, s, k, 2), h = u * ahr_rr(0.02, 0.07, s, k, 3), edge = ahr_h(s, k, 4) % 4;
        var x = ahr_r(s, k, 5) * (W - w), y = ahr_r(s, k, 6) * (H - h);
        if (edge === 0) y = ahr_rr(0.02, 0.18, s, k, 7) * H; else if (edge === 1) y = H - h - ahr_rr(0.02, 0.18, s, k, 7) * H;
        else if (edge === 2) x = ahr_rr(0.01, 0.1, s, k, 7) * W; else x = W - w - ahr_rr(0.01, 0.1, s, k, 7) * W;
        if (ahr4_over(bb, x, y, x + w, y + h, u * 0.02)) continue;
        var N = ahr_solid(ctx, '#808080', 'hr snow patch ' + (k + 1), w, h);
        jzXf(N, 'ADBE Position').setValue([x + w / 2, y + h / 2]);
        var ne = jzEffect(N, 'ADBE Noise', 'hr Snow'); jzEP(ne, 1, 100); jzEP(ne, 2, 0);
        var sd = (s + k * 131) % 99991;
        jzSetExpr(jzXf(N, 'ADBE Position'), 'posterizeTime(12);seedRandom(' + sd + '+Math.floor(time*12),true);[value[0]+random(-1,1)*' + jzN(u * 0.02) + ',value[1]]');
        jzSetExpr(jzXf(N, 'ADBE Opacity'), TH0 + 'posterizeTime(24);seedRandom(' + sd + '+Math.floor(time*24)*3,true);var on=random()>=0.35;(on?random(45,85):0)*oc(time/0.2)*K');
        jzNoGhost(N);
    }
} });

/* hrDustBeam \u2014 \u5149\u306E\u7B4B\u3068\u57C3: a slanted beam from a high window with dust hanging in it */
jzReg('decor', 'hrDustBeam', { back: true, build: function (ctx, bb0, d) {
    var W = ctx.W, H = ctx.H, sc = ctx.sc, s = d.seed | 0, u = jzU(ctx), i, dark = ahr_dark(sc.bg);
    var Lc = ahr_light(sc), right = !!d.right, x0 = right ? W * 0.78 : W * 0.22, w0 = W * 0.12, w1 = W * 0.34, sl = (right ? -1 : 1) * W * 0.28;
    var poly = [[x0 - w0 / 2, -2], [x0 + w0 / 2, -2], [x0 + sl + w1 / 2, H + 2], [x0 + sl - w1 / 2, H + 2]];
    var io = jzTH(ctx) + 'var a=oc(time/0.8)*K;';
    if (!dark) {
        // on light paper the room around the beam is dimmed instead
        var Dm = ahr_solid(ctx, ahr_night(sc), 'hr beam room', W, H), mk = Dm.property('ADBE Mask Parade').addProperty('ADBE Mask Atom'), sh = new Shape();
        sh.vertices = poly; sh.closed = true; mk.property('ADBE Mask Shape').setValue(sh); mk.inverted = true;
        jzSetExpr(jzXf(Dm, 'ADBE Opacity'), io + '12*a');
        jzNoGhost(Dm);
    }
    var B = jzShapeLayer(ctx, 'hr beam', 0, 0), gb = jzGrp(B, 'beam');
    jzAddPath(gb, poly, true); jzAddFill(gb, Lc);
    var lw = jzEffect(B, 'ADBE Linear Wipe', 'hr Beam Fade'); jzEP(lw, 1, 45); jzEP(lw, 2, 0); jzEP(lw, 3, H * 0.9);
    jzSetExpr(jzXf(B, 'ADBE Opacity'), io + jzN((dark ? 0.1 : 0.22) * 100 * 1.6) + '*a');
    jzNoGhost(B);
    // dust inside the beam
    var Du = jzShapeLayer(ctx, 'hr beam dust', 0, 0), gd = jzGrp(Du, 'dust');
    for (i = 0; i < 40; i++) {
        var fy = ahr_r(s, i, 1) * 0.9, y = fy * H, f = ahr_r(s, i, 3), cx = x0 + sl * fy, ww = w0 + (w1 - w0) * fy, r = u * ahr_rr(0.0012, 0.0035, s, i, 4) * 2;
        jzAddEllipse(gd, r, r, cx + (f - 0.5) * ww * 0.9, y);
    }
    jzAddFill(gd, dark ? Lc : sc.sub);
    jzSetExpr(jzXf(Du, 'ADBE Position'), '[Math.sin(time*0.7)*' + jzN(u * 0.01) + ',time*' + jzN(H * 0.01) + ']');
    jzSetExpr(jzXf(Du, 'ADBE Opacity'), io + '55*a*(0.7+0.3*Math.sin(time*1.5))');
    jzNoGhost(Du);
} });

/* hrDrips \u2014 \u5782\u308C\u308B\u58A8: dark ink running down from the top edge of the frame, slowly */
jzReg('decor', 'hrDrips', { back: false, build: function (ctx, bb0, d) {
    var bb = ahr4_bb(ctx, bb0), W = ctx.W, H = ctx.H, sc = ctx.sc, s = d.seed | 0, u = jzU(ctx), i, k;
    var col = ahr_dark(sc.bg) ? jzMixHex(sc.accent, sc.bg, 0.35) : ahr_night(sc), band = u * 0.03, TH0 = jzTH(ctx);
    var S = jzShapeLayer(ctx, 'hr drips', 0, 0);
    var n = Math.min(13, 7 + (d.n | 0) * 3), lim = Math.max(band * 2, bb.y0 - u * 0.05);
    for (k = 0; k < n; k++) {
        var x = W * (0.03 + 0.94 * ahr_r(s, k, 2)), Ld = u * ahr_rr(0.06, 0.3, s, k, 5);
        if (x > bb.x0 - u * 0.03 && x < bb.x1 + u * 0.03) Ld = Math.min(Ld, lim - band);
        if (Ld <= 1) continue;
        var w = u * ahr_rr(0.004, 0.012, s, k, 6), g = jzGrp(S, 'drip ' + k);
        jzAddPath(g, [[x, band * 0.5], [x, band + Ld]], false);
        var st = jzAddStroke(g, col, w * 1.7); try { st.property('ADBE Vector Stroke Line Cap').setValue(2); } catch (e) {}
        jzAddTrimPaths(g, TH0 + '100*oc((time-' + jzN(ahr_r(s, k, 3) * 0.8) + ')/' + jzN(2.5 + ahr_r(s, k, 4) * 3) + ')');
    }
    var pts = [[-4, -4], [W + 4, -4]];
    for (i = 40; i >= 0; i--) pts.push([W * i / 40, band * (0.6 + 0.6 * ahr_r(s, i, 1))]);
    var gb = ahr_path(S, 'band', pts, { closed: true, fill: col });
    jzGX(gb).property('ADBE Vector Scale').expression = TH0 + '[100,100*oc(time/0.4)]';
    jzSetExpr(jzXf(S, 'ADBE Opacity'), TH0 + '90*K');
    jzNoGhost(S);
} });

/* hrCracks \u2014 \u3072\u3073\u5272\u308C: fine fractures creep out of a corner of the frame */
jzReg('decor', 'hrCracks', { back: false, build: function (ctx, bb0, d) {
    var bb = ahr4_bb(ctx, bb0), W = ctx.W, H = ctx.H, sc = ctx.sc, s = d.seed | 0, u = jzU(ctx), r;
    var ox = d.right ? W : 0, oy = d.low ? H : 0, lw = Math.max(1, u * 0.0018), base = Math.atan2(H / 2 - oy, W / 2 - ox), byD = [[], [], []];
    function branch(x, y, an, len, depth, id) {
        var pts = [[x, y]], cx = x, cy = y, i, k;
        for (i = 1; i <= 7; i++) { var a = an + ahr_rs(s, id, i, 1) * 0.5; cx += Math.cos(a) * len / 7; cy += Math.sin(a) * len / 7; if (ahr4_over(bb, cx, cy, cx, cy, u * 0.03)) break; pts.push([cx, cy]); }
        if (pts.length > 1) byD[depth].push(pts);
        if (depth < 2) for (k = 0; k < 2; k++) { var j = 2 + (ahr_h(s, id, k, 3) % (pts.length - 1 || 1)), p = pts[Math.min(j, pts.length - 1)]; branch(p[0], p[1], an + (k ? 0.6 : -0.6) * ahr_rr(0.6, 1.2, s, id, k, 4), len * 0.5, depth + 1, id * 3 + k + 1); }
    }
    for (r = 0; r < 3; r++) branch(ox, oy, base + (r - 1) * 0.35 + ahr_rs(s, r, 9) * 0.15, u * ahr_rr(0.28, 0.42, s, r, 10), 0, r + 1);
    var S = jzShapeLayer(ctx, 'hr cracks', 0, 0), TH0 = jzTH(ctx), dd, i2;
    for (dd = 2; dd >= 0; dd--) {
        if (!byD[dd].length) continue;
        var g = jzGrp(S, 'depth ' + dd);
        for (i2 = 0; i2 < byD[dd].length; i2++) jzAddPath(g, byD[dd][i2], false);
        jzAddStroke(g, sc.fg, lw * (1.4 - dd * 0.35), 75);
        jzAddTrimPaths(g, TH0 + '100*cl(oc(time/1.6)*' + jzN(1 + dd * 0.2) + '-' + jzN(dd * 0.25) + ')');
    }
    jzSetExpr(jzXf(S, 'ADBE Opacity'), TH0 + '100*K');
    jzNoGhost(S);
} });

/* ================================================================ BACKGROUNDS */

/* hrFailingLamp \u2014 \u5207\u308C\u304B\u3051\u306E\u706F: light from a stuttering tube at the top, dark corners that breathe with it */
jzReg('bg', 'hrFailingLamp', {
    plan: function (rng, st) { return { seed: rng.int(1, 1e9), x: rng.range(0.35, 0.65), rate: rng.range(0.7, 1.3) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, u = jzU(b), dark = ahr_dark(sc.bg), cx = W * (P.x || 0.5), cy = -u * 0.1, hyp = Math.sqrt(W * W + H * H), seed = P.seed | 0;
        var HD = ahr4_bh(b) + ahr4_lamp(seed, P.rate || 1, 'T');
        var Lg = ahr4_solid(b.comp, ahr_light(sc), 'bg lamp light', W, H);
        ahr4_ellMask(Lg, cx, cy, hyp * 0.3, hyp * 0.9);
        jzSetExpr(jzXf(Lg, 'ADBE Opacity'), HD + jzN((dark ? 0.16 : 0.3) * 100 * 1.4) + '*lv*FI');
        var V = ahr4_solid(b.comp, ahr_night(sc), 'bg lamp dark', W, H);
        ahr4_ellMask(V, W / 2, H / 2, Math.min(W, H) * 0.3 + hyp * 0.15, hyp * 0.3, true);
        jzSetExpr(jzXf(V, 'ADBE Opacity'), HD + '(75-25*lv)*FI');
        var Tb = jzRectLayer(b, 'bg lamp tube', cx, u * 0.012 + Math.max(2, u * 0.006) / 2, u * 0.24, Math.max(2, u * 0.006), jzLayC(sc, dark ? 0.35 : 0.15));
        jzSetExpr(jzXf(Tb, 'ADBE Opacity'), HD + '(25+50*lv)*FI');
    }
});

/* hrCorridor \u2014 \u6697\u3044\u5ECA\u4E0B: a corridor drawn in faint lines, frames sliding towards the camera, a flickering light at the end */
jzReg('bg', 'hrCorridor', {
    plan: function (rng, st) { return { seed: rng.int(1, 1e9), vx: rng.range(0.44, 0.56), vy: rng.range(0.44, 0.54), spd: rng.range(0.25, 0.45), doors: rng.chance(0.75) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, u = jzU(b), dark = ahr_dark(sc.bg), i, k;
        var vx = W * (P.vx || 0.5), vy = H * (P.vy || 0.5), bw = W * 0.08, bh = H * 0.1, col = jzLayC(sc, dark ? 0.28 : 0.22), lw = Math.max(1.2, u * 0.0026);
        var HB = ahr4_bh(b);
        // the light at the far end
        var Lg = ahr4_solid(b.comp, ahr_light(sc), 'bg corridor light', W, H);
        ahr4_ellMask(Lg, vx, vy, Math.max(bw, bh) * 1.2, Math.max(bw, bh) * 3);
        jzSetExpr(jzXf(Lg, 'ADBE Opacity'), HB + ahr4_lamp((P.seed | 0) + 1, 1, 'T') + jzN((dark ? 0.22 : 0.3) * 100 * 1.3) + '*lv*FI');
        var S = jzShapeLayer(b, 'bg corridor', 0, 0), n = 7, sp = P.spd || 0.35;
        // frames sliding towards the camera (floor and ceiling edges)
        for (k = 0; k < n; k++) {
            var g = jzGrp(S, 'frame ' + k), fz = HB + 'var f=wr(' + jzN(k / n) + '+BT*' + jzN(sp * 0.12) + ',1),z=Math.pow(f,2.2);var x0=' + jzN(vx - bw) + '*(1-z),x1=' + jzN(vx + bw) + '*(1-z)+' + jzN(W) + '*z,y0=' + jzN(vy - bh) + '*(1-z),y1=' + jzN(vy + bh) + '*(1-z)+' + jzN(H) + '*z;';
            var r1 = jzAddRect(g, 10, lw, 0);
            r1.property('ADBE Vector Rect Size').expression = fz + '[x1-x0,' + jzN(lw) + ']';
            r1.property('ADBE Vector Rect Position').expression = fz + '[(x0+x1)/2,y0]';
            var r2 = jzAddRect(g, 10, lw, 0);
            r2.property('ADBE Vector Rect Size').expression = fz + '[x1-x0,' + jzN(lw) + ']';
            r2.property('ADBE Vector Rect Position').expression = fz + '[(x0+x1)/2,y1]';
            jzAddFill(g, col);
        }
        var gl = jzGrp(S, 'walls'), back = [[vx - bw, vy - bh], [vx + bw, vy - bh], [vx + bw, vy + bh], [vx - bw, vy + bh]], corners = [[0, 0], [W, 0], [W, H], [0, H]];
        for (i = 0; i < 4; i++) jzAddPath(gl, [corners[i], back[i]], false);
        jzAddPath(gl, back, true);
        jzAddStroke(gl, col, lw);
        jzSetExpr(jzXf(S, 'ADBE Opacity'), HB + '100*FI');
        var V = ahr4_solid(b.comp, ahr_night(sc), 'bg corridor dark', W, H), hyp = Math.sqrt(W * W + H * H);
        ahr4_ellMask(V, vx, vy, Math.min(W, H) * 0.2 + hyp * 0.2, hyp * 0.4, true);
        jzSetExpr(jzXf(V, 'ADBE Opacity'), HB + '55*FI');
    }
});

/* hrMold \u2014 \u5E83\u304C\u308B\u67D3\u307F: stains of damp creep in from the edges */
jzReg('bg', 'hrMold', {
    plan: function (rng, st) { return { seed: rng.int(1, 1e9), n: rng.int(3, 5), k: rng.range(0.08, 0.14) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, u = jzU(b), s = P.seed | 0, k = P.k || 0.1, dark = ahr_dark(sc.bg), i, j;
        var stain = dark ? jzMixHex(sc.bg, jzMixHex(sc.fg, sc.accent2 || sc.sub, 0.5), k) : jzMixHex(sc.bg, jzMixHex(sc.sub, '#000000', 0.3), k * 1.4);
        var rim = dark ? jzMixHex(sc.bg, sc.fg, k * 1.6) : jzMixHex(sc.bg, '#000000', k * 1.3), HB = ahr4_bh(b, 0.8);
        var S = jzShapeLayer(b, 'bg mold', 0, 0);
        for (i = 0; i < (P.n || 4); i++) {
            var edge = ahr_h(s, i, 1) % 4, f = ahr_r(s, i, 2);
            var cx = edge === 0 ? f * W : edge === 1 ? W : edge === 2 ? f * W : 0, cy = edge === 0 ? 0 : edge === 1 ? f * H : edge === 2 ? H : f * H;
            var R = u * ahr_rr(0.25, 0.5, s, i, 3), pts = [], g = jzGrp(S, 'stain ' + i);
            for (j = 0; j < 36; j++) { var a = j / 36 * Math.PI * 2, r = R * (0.7 + 0.45 * ahr_noise1(j * 0.45, s + i)); pts.push([Math.cos(a) * r, Math.sin(a) * r]); }
            jzAddPath(g, pts, true);
            for (j = 0; j < 14; j++) { var a2 = ahr_r(s, i, j, 5) * Math.PI * 2, r2 = R * ahr_rr(1.02, 1.25, s, i, j, 6), d2 = u * ahr_rr(0.002, 0.008, s, i, j, 7) * 2; jzAddEllipse(g, d2, d2, Math.cos(a2) * r2, Math.sin(a2) * r2); }
            jzAddStroke(g, rim, Math.max(1, u * 0.003), 60);
            jzAddFill(g, stain, 80);
            jzGX(g).property('ADBE Vector Position').setValue([cx, cy]);
            jzGX(g).property('ADBE Vector Scale').expression = HB + 'var s=(0.35+0.65*oc(BT/' + jzN(ahr_rr(8, 14, s, i, 4)) + '))*oc(BT/0.8)*100;[s,s]';
        }
    }
});

/* hrDeadTrees \u2014 \u67AF\u308C\u6728\u306E\u68EE: two rows of bare branching trees and ground fog */
jzReg('bg', 'hrDeadTrees', {
    plan: function (rng, st) { return { seed: rng.int(1, 1e9), n: rng.int(5, 8), fog: rng.range(0.1, 0.2) }; },
    build: function (b, P) {
        var W = b.W, H = b.H, sc = b.sc, u = jzU(b), s = P.seed | 0, dark = ahr_dark(sc.bg), HB = ahr4_bh(b, 0.8), l, i, d;
        var layers = [[0.07, 0.75, 0.8], [0.13, 1, 1.15]];
        var S = jzShapeLayer(b, 'bg dead trees', 0, 0);
        for (l = 1; l >= 0; l--) {
            var kk = layers[l][0], hk = layers[l][1], sk = layers[l][2], col = dark ? jzLayC(sc, kk * 0.9) : jzMixHex(sc.bg, '#000000', kk * 1.6);
            var segs = [[], [], [], [], []], n = (P.n || 6) + l * 2;
            for (i = 0; i < n; i++) {
                var x = W * (i + 0.5 + ahr_rs(s, l, i, 1) * 0.35) / n, h = H * ahr_rr(0.7, 1.05, s, l, i, 2) * hk;
                (function seg(x0, y0, an, len, dp, id) {
                    var x1 = x0 + Math.cos(an) * len, y1 = y0 + Math.sin(an) * len;
                    segs[dp].push([[x0, y0], [x1, y1]]);
                    if (dp >= 4) return;
                    for (var bI = 0; bI < 2; bI++) seg(x1, y1, an + (bI ? 1 : -1) * ahr_rr(0.25, 0.7, s, id, bI, 3), len * ahr_rr(0.55, 0.78, s, id, bI, 4), dp + 1, id * 2 + bI + 1);
                })(x, H + 2, -Math.PI / 2 + ahr_rs(s, l, i, 5) * 0.08, h * 0.42, 0, (l * 50 + i) * 64 + 1);
            }
            for (d = 0; d < 5; d++) {
                var g = jzGrp(S, 'row ' + l + ' depth ' + d);
                for (i = 0; i < segs[d].length; i++) jzAddPath(g, segs[d][i], false);
                var st = jzAddStroke(g, col, Math.max(1, u * 0.02 * sk * Math.pow(0.55, d))); try { st.property('ADBE Vector Stroke Line Cap').setValue(2); } catch (e) {}
            }
        }
        jzSetExpr(jzXf(S, 'ADBE Opacity'), HB + '100*FI');
        jzXf(S, 'ADBE Anchor Point').setValue([W / 2, H]);
        jzXf(S, 'ADBE Position').setValue([W / 2, H]);
        jzSetExpr(jzXf(S, 'ADBE Rotate Z'), 'Math.sin((time+' + jzN(b.cut.start || 0) + ')*0.4)*0.25');
        // ground fog
        var F = ahr4_solid(b.comp, dark ? jzLayC(sc, 0.5) : sc.bg, 'bg fog', W, H), fm = F.property('ADBE Mask Parade').addProperty('ADBE Mask Atom'), sh = new Shape();
        sh.vertices = [[-W * 0.2, H * 0.8], [W * 1.2, H * 0.8], [W * 1.2, H * 1.3], [-W * 0.2, H * 1.3]]; sh.closed = true;
        fm.property('ADBE Mask Shape').setValue(sh); fm.property('ADBE Mask Feather').setValue([H * 0.5, H * 0.5]);
        jzSetExpr(jzXf(F, 'ADBE Opacity'), HB + jzN((P.fog || 0.15) * 100 * 1.4) + '*FI');
    }
});

/* ================================================================ CAMERA */

/* hrNervous \u2014 \u602F\u3048\u305F\u624B\u6301\u3061: a scared hand-held camera with a sudden flinch now and then */
jzReg('cam', 'hrNervous', {
    plan: function (rng, st) { return { f: rng.range(0.9, 1.3), jerk: rng.range(0.6, 1) }; },
    apply: function (cam, P) {
        var H0 = jzCamHead(cam) + AHR_FNS, f = P.f || 1, sd = (cam.cut.seed | 0) % 97, W = cam.W, H = cam.H;
        var fl = 'var per=1.6,cyc=Math.floor(time/per);seedRandom(SD+cyc*7,true);var since=time-cyc*per-random()*per*0.6,go=random()<' + jzN(0.65 * (P.jerk || 0.8)) + ',j1=random(-1,1),j2=random(-1,1),j3=random(-1,1);var dk=(since>0&&go)?Math.exp(-since*9)*KM:0;';
        jzSetExpr(jzXf(cam.nul, 'ADBE Position'), H0 + fl + 'var t=time*' + jzN(f) + ';' +
            'var x=(nz(t*1.7,' + sd + ')*0.6+nz(t*5.3,' + (sd + 1) + ')*0.4)*' + jzN(W * 0.009) + '*KM+j1*' + jzN(W * 0.025) + '*dk;' +
            'var y=(nz(t*1.4,' + (sd + 2) + ')*0.6+nz(t*4.7,' + (sd + 3) + ')*0.4)*' + jzN(H * 0.011) + '*KM+Math.sin(time*2.2)*' + jzN(H * 0.004) + '*KM+j2*' + jzN(H * 0.03) + '*dk;[value[0]+x,value[1]+y]');
        jzSetExpr(jzXf(cam.nul, 'ADBE Rotate Z'), H0 + fl + 'value+nz(time*' + jzN(f * 1.1) + ',' + (sd + 4) + ')*1.2*KM+j3*3*dk');
        jzSetExpr(jzXf(cam.nul, 'ADBE Scale'), '[value[0]*1.03,value[1]*1.03]');
    }
});

/* hrDutchSnap \u2014 \u4E0D\u610F\u306E\u50BE\u304D: a slow push, then the frame suddenly tips over */
jzReg('cam', 'hrDutchSnap', {
    plan: function (rng, st) { return { at: rng.range(0.45, 0.65), a: rng.range(3, 4.8) * rng.pick([1, -1]) }; },
    apply: function (cam, P) {
        var H0 = jzCamHead(cam) + AHR_FNS + 'var q=cl((time-DUR*' + jzN(P.at || 0.55) + ')/0.1),e=q<=0?0:ob(q,2.5);';
        jzSetExpr(jzXf(cam.nul, 'ADBE Scale'), H0 + 'var s=1+0.045*KM*ios(cu)+0.02*KM*e;[value[0]*s,value[1]*s]');
        jzSetExpr(jzXf(cam.nul, 'ADBE Rotate Z'), H0 + 'value+' + jzN(P.a || 4) + '*Math.min(1,KM)*e');
        jzSetExpr(jzXf(cam.nul, 'ADBE Position'), H0 + '[value[0],value[1]-' + jzN(cam.H * 0.006) + '*KM*e]');
    }
});

/* ================================================================ SCREEN EFFECTS */

/* hrSubliminal \u2014 \u30B5\u30D6\u30EA\u30DF\u30CA\u30EB: one frame of a zoomed, red-stained negative */
jzReg('fx', 'hrSubliminal', { build: function (f, ev) {
    var W = f.W, H = f.H, s = ahr_h(Math.round(ev.t * 1000), 9127), z = 1.25 + 0.2 * ahr_r(s, 1), cx = W * (0.5 + ahr_rs(s, 2) * 0.08), cy = H * (0.5 + ahr_rs(s, 3) * 0.08);
    var L = jzAdjLayer(f.comp, 'JZ FX hrSubliminal', ev.t, Math.max(1 / 24, ev.dur * 0.55));
    var tf = jzEffect(L, 'ADBE Geometry2', 'hr Sub Zoom'); jzEP(tf, 1, [cx, cy]); jzEP(tf, 2, [cx, cy]); jzEP(tf, 4, z * 100);
    jzEffect(L, 'ADBE Invert', 'hr Sub Negative');
    var sc = ev.sc || f.st.schemes[0], red = jzMixHex(jzFitContrast(sc.accent, '#ffffff', 2.5), '#000000', 0.25);
    var ti = L.property('ADBE Effect Parade').addProperty('ADBE Tint'); ti.name = 'hr Sub Stain';
    jzEP(ti, 1, [0, 0, 0]); jzEP(ti, 2, jzHex(red)); jzEP(ti, 3, 100);
} });

/* hrSignalLoss \u2014 \u6620\u50CF\u306E\u9014\u5207\u308C: the picture tears, drops to black with a signal-lost caption, and rolls back in */
jzReg('fx', 'hrSignalLoss', { build: function (f, ev) {
    var W = f.W, H = f.H, s = ahr_h(Math.round(ev.t * 1000), 9127), HD = jzEvHead(ev.t, ev.dur);
    // tearing (p < 0.3) and the roll back in (p > 0.78) on an adjustment layer
    var A = jzAdjLayer(f.comp, 'JZ FX hrSignalLoss', ev.t, ev.dur);
    var ww = jzEffect(A, 'ADBE Wave Warp', 'hr Tear'); jzEP(ww, 1, 2); jzEP(ww, 4, 0); jzEP(ww, 5, 0); jzEP(ww, 6, 1);
    jzEX(ww, 2, HD + 'posterizeTime(24);seedRandom(' + (s % 9973) + '+Math.floor(time*24),true);p<0.3?random(0.3,1)*' + jzN(W * 0.12) + '*p/0.3:0');
    jzEX(ww, 3, HD + 'posterizeTime(24);seedRandom(' + (s % 9973) + '+Math.floor(time*24)+5,true);random(' + jzN(H * 0.1) + ',' + jzN(H * 0.4) + ')');
    var of = jzEffect(A, 'ADBE Offset', 'hr Roll');
    jzEX(of, 1, HD + 'var q=cl((p-0.78)/0.22);[value[0],value[1]+(p>0.78?(1-q)*' + jzN(H * 0.5) + ':0)]');
    var B = jzEvSolid(f.comp, 'JZ FX hrSignalLoss black', '#000000', ev.t, ev.dur);
    jzSetExpr(jzXf(B, 'ADBE Opacity'), HD + 'p<0.3?50*p/0.3:(p<0.78?100:0)');
    var bar = jzEvShape(f.comp, 'JZ FX hrSignalLoss seam', ev.t, ev.dur), gb = jzGrp(bar, 'seam'), bh = Math.max(3, H * 0.03);
    jzAddRect(gb, W, bh, 0, W / 2, -bh / 2); jzAddFill(gb, '#000000');
    jzSetExpr(jzXf(bar, 'ADBE Position'), HD + 'var q=cl((p-0.78)/0.22);[0,(1-q)*' + jzN(H * 0.5) + ']');
    jzSetExpr(jzXf(bar, 'ADBE Opacity'), HD + 'p>0.78?100:0');
    // caption on the black
    var fs = Math.max(10, Math.round(Math.min(W, H) * 0.035)), tctx = { comp: f.comp, roles: null };
    var T1 = jzText(tctx, 'NO SIGNAL', { font: 'mono', size: fs, color: '#FFFFFF', x: W * 0.06, y: H * 0.08, align: 'left' });
    var T2 = jzText(tctx, 'CH ' + jzPad(3 + (s % 9), 2), { font: 'mono', size: fs, color: '#FFFFFF', x: W * 0.06, y: H * 0.08 + fs * 1.5, align: 'left' });
    T1.inPoint = T2.inPoint = Math.max(0, ev.t); T1.outPoint = T2.outPoint = Math.min(f.comp.duration, ev.t + Math.max(ev.dur, 1 / 24));
    jzSetExpr(jzXf(T1, 'ADBE Opacity'), HD + 'posterizeTime(12);(p>=0.3&&p<0.78)?(Math.floor(time*12)%4<3?85:30):0');
    jzSetExpr(jzXf(T2, 'ADBE Opacity'), HD + '(p>=0.3&&p<0.78)?60:0');
} });

/* hrPassingShadow \u2014 \u6A2A\u5207\u308B\u5F71: something tall and dark crosses the frame in a few frames */
jzReg('fx', 'hrPassingShadow', { build: function (f, ev) {
    var W = f.W, H = f.H, s = ahr_h(Math.round(ev.t * 1000), 9127), dir = ahr_r(s, 1) < 0.5 ? 1 : -1, M = Math.min(W, H);
    var w = M * ahr_rr(0.22, 0.32, s, 2), h = H * 1.1, top = H * ahr_rr(0.05, 0.2, s, 3), sc = ev.sc || f.st.schemes[0], dk = ahr_dark(sc.bg);
    var col = dk ? jzMixHex(sc.fg, sc.bg, 0.35) : '#000000';
    var S = jzEvShape(f.comp, 'JZ FX hrPassingShadow', ev.t, ev.dur), k;
    var gg = [[1, dk ? 30 : 65], [1.12, dk ? 15 : 30]];
    for (k = 0; k < 2; k++) {
        var gk = gg[k][0], g = jzGrp(S, 'figure ' + k);
        jzAddEllipse(g, w * 0.6 * gk, w * 0.76 * gk, 0, top + w * 0.35);
        var pts = [[-w * 0.5 * gk, h], [-w * 0.55 * gk, top + w * 0.95], [-w * 0.3 * gk, top + w * (0.72 - 0.05 * gk)], [0, top + w * (0.7 - 0.05 * gk)], [w * 0.3 * gk, top + w * (0.72 - 0.05 * gk)], [w * 0.55 * gk, top + w * 0.95], [w * 0.5 * gk, h]];
        jzAddPath(g, pts, true);
        jzAddFill(g, col, gg[k][1]);
    }
    jzSetExpr(jzXf(S, 'ADBE Position'), jzEvHead(ev.t, ev.dur) + '[' + (dir > 0 ? jzN(-W * 0.25) + '+' + jzN(W * 1.5) + '*p' : jzN(W * 1.25) + '-' + jzN(W * 1.5) + '*p') + ',0]');
} });

/* ================================================================ TRANSITIONS */

/* hrStaticCut \u2014 \u7802\u5D50\u30AB\u30C3\u30C8: the old shot drowns in snow, the new one surfaces out of it */
jzReg('trans', 'hrStaticCut', {
    plan: function (rng, st) { return { roll: rng.chance(0.6) }; },
    build: function (t) {
        var W = t.W, H = t.H, HD = jzEvHead(t.t0, t.dur) + 'var inW=time>=T0&&time<T0+TD;var nzv=p<0.5?iq(p/0.5):1-(1-(1-(p-0.5)/0.5)*(1-(p-0.5)/0.5));';
        jzSetExpr(jzXf(t.B, 'ADBE Opacity'), HD + '(inW&&p<0.5)?0:value');
        if (t.P.roll) {
            var oa = jzEffect(t.A, 'ADBE Offset', 'hr Static Roll A');
            jzEX(oa, 1, HD + '(inW&&p<0.5)?[value[0],value[1]+Math.round(Math.sin(p*Math.PI)*' + jzN(H * 0.08) + ')]:value');
            var ob2 = jzEffect(t.B, 'ADBE Offset', 'hr Static Roll B');
            jzEX(ob2, 1, HD + '(inW&&p>=0.5)?[value[0],value[1]-Math.round(Math.sin(p*Math.PI)*' + jzN(H * 0.08) + ')]:value');
        }
        var N = jzEvSolid(t.comp, 'JZ Trans hrStaticCut snow', '#808080', t.t0, t.dur);
        var ne = jzEffect(N, 'ADBE Noise', 'hr Snow'); jzEP(ne, 1, 100); jzEP(ne, 2, 0);
        jzSetExpr(jzXf(N, 'ADBE Opacity'), HD + 'Math.min(1,nzv*1.15)*100');
        var Bd = jzEvShape(t.comp, 'JZ Trans hrStaticCut band', t.t0, t.dur), g = jzGrp(Bd, 'band');
        jzAddRect(g, W, H * 0.12, 0, W / 2, H * 0.06); jzAddFill(g, '#000000');
        jzSetExpr(jzXf(Bd, 'ADBE Position'), 'posterizeTime(24);seedRandom(Math.floor(time*24)+7,true);[0,random()*' + jzN(H) + ']');
        jzSetExpr(jzXf(Bd, 'ADBE Opacity'), HD + '50*nzv');
    }
});

/* hrBlink \u2014 \u307E\u3070\u305F\u304D: eyelids close on the old shot and open on the new one */
jzReg('trans', 'hrBlink', {
    plan: function (rng, st) { return { half: rng.chance(0.3) }; },
    build: function (t) {
        var W = t.W, H = t.H, bow = H * 0.18 * 0.7, i;
        var HD = jzEvHead(t.t0, t.dur) + 'var inW=time>=T0&&time<T0+TD;var c=p<0.5?ic(p/0.5):1-oc((p-0.5)/0.5);var h=' + jzN(H / 2 * 1.02) + '*c;';
        jzSetExpr(jzXf(t.B, 'ADBE Opacity'), HD + '(inW&&p<0.5)?0:value');
        var S = jzEvShape(t.comp, 'JZ Trans hrBlink lids', t.t0, t.dur), sg = [1, -1];
        for (i = 0; i < 2; i++) {
            var g = jzGrp(S, i ? 'lower lid' : 'upper lid'), pts = [[0, -H * 1.2 * sg[i]], [W, -H * 1.2 * sg[i]]], k;
            for (k = 16; k >= 0; k--) { var f2 = k / 16; pts.push([W * f2, sg[i] * (-bow + 4 * bow * f2 * (1 - f2))]); }
            jzAddPath(g, pts, true); jzAddFill(g, '#000000');
            jzGX(g).property('ADBE Vector Position').expression = HD + (i ? '[0,' + jzN(H) + '-h+' + jzN(bow * 0.5) + ']' : '[0,h-' + jzN(bow * 0.5) + ']');
        }
        // a soft shadow around the lids
        var Sf = jzEvSolid(t.comp, 'JZ Trans hrBlink shade', '#000000', t.t0, t.dur);
        for (i = 0; i < 2; i++) {
            var m = Sf.property('ADBE Mask Parade').addProperty('ADBE Mask Atom'), sh = new Shape(), y0 = i ? H * 0.8 : -H * 0.3, y1 = i ? H * 1.3 : H * 0.2;
            sh.vertices = [[-W * 0.1, y0], [W * 1.1, y0], [W * 1.1, y1], [-W * 0.1, y1]]; sh.closed = true;
            m.property('ADBE Mask Shape').setValue(sh); m.property('ADBE Mask Feather').setValue([H * 0.5, H * 0.5]);
        }
        jzSetExpr(jzXf(Sf, 'ADBE Opacity'), HD + '60*c');
    }
});

// ================================================================ pack kinetic part 1 \u2014 kinetic typography layouts (1\u20137) + shared layout helpers
// Layout pattern: each moving piece is a text layer parented to a null ("rig"); the null carries the layout's choreography
// (absolute position / scale / rotation from a function U(i, t) embedded in the expression), the text layer keeps the
// cut's own enter / hold / exit / treatment. Word onsets are spread evenly over the cut (no beat grid in AE).

// ---- word units: the cut's word chunks, merged / split to a usable count (browser unitsOf)
function akn_gc(t) { return jzCount(t); }
function akn_splitUnit(s) {
    var t = jzTrim(s), i;
    if (/\s/.test(t)) {
        var mid = t.length / 2, bi = -1, bd = 1e9;
        for (i = 0; i < t.length; i++) if (t.charAt(i) === ' ' && Math.abs(i - mid) < bd) { bd = Math.abs(i - mid); bi = i; }
        return [jzTrim(t.substr(0, bi)), jzTrim(t.substr(bi + 1))];
    }
    var n = jzChars(t).length, parts = jzSplitLines(t, Math.ceil(n / 2)).split('\r');
    return parts.length >= 2 ? [parts[0], parts.slice(1).join('')] : [t];
}
function akn_units(cut, maxU, minU) {
    var text = String(cut.text || ''), lat = jzHasLatin(text), w = [], src = (cut.words && cut.words.length) ? cut.words : (lat ? text.split(/\s+/) : jzChunk(text)), i, g, s;
    for (i = 0; i < src.length; i++) { s = jzTrim(String(src[i])); if (s) w.push(s); }
    if (jzStrip(w.join('')) !== jzStrip(text)) { w = []; src = lat ? text.split(/\s+/) : jzChunk(text); for (i = 0; i < src.length; i++) { s = jzTrim(String(src[i])); if (s) w.push(s); } }
    if (!w.length) w = [jzTrim(text) || '\u2026'];
    while (w.length > maxU) {
        var bi = 0, bv = 1e9;
        for (i = 0; i < w.length - 1; i++) { var v = akn_gc(w[i]) + akn_gc(w[i + 1]); if (v < bv) { bv = v; bi = i; } }
        w.splice(bi, 2, w[bi] + (lat ? ' ' : '') + w[bi + 1]);
    }
    for (g = 0; g < 8 && w.length < minU; g++) {
        var bj = -1, bn = 1;
        for (i = 0; i < w.length; i++) { var n = akn_gc(w[i]), ok = /\s/.test(w[i]) || (!jzHasLatin(w[i]) && n >= 2); if (ok && n > bn) { bn = n; bj = i; } }
        if (bj < 0) break;
        var parts = akn_splitUnit(w[bj]);
        if (parts.length < 2 || !parts[0] || !parts[1]) break;
        w.splice(bj, 1, parts[0], parts[1]);
    }
    return w;
}
// word clock: onset of each unit inside the cut
function akn_onsets(cut, n, frac, gap, t0) {
    var dur = cut.dur || 2, last = Math.max(0, Math.min(dur * frac, (n - 1) * gap)), v = [], i;
    for (i = 0; i < n; i++) v.push((t0 || 0) + (n > 1 ? last * i / (n - 1) : 0));
    return v;
}
function akn_mi(ctx, t) { return Math.max(0, t) / Math.max(0.005, ctx.cut.stagger || 0.04); }
// measuring with a probe layer (size 100): [w, h] per 1 px of font size
var AKN_MEAS = {};
function akn_m(ctx, text, font, o) {
    o = o || {};
    var key = [text, font, o.track || 0, o.lead || 0, o.vertical ? 1 : 0].join('\u0001');
    if (AKN_MEAS[key]) return AKN_MEAS[key];
    var str = o.vertical ? jzVertical(text) : text, P = jzText(ctx, str, { font: font, size: 100, color: '#FFFFFF', x: -9999, y: -9999, track: o.track || 0, leading: o.lead ? 100 * o.lead : null });
    var s = jzSize(P); P.remove();
    var r = { w: Math.max(0.05, s[0] / 100), h: Math.max(0.05, s[1] / 100) };
    AKN_MEAS[key] = r;
    return r;
}
function akn_em(ctx, text, font, track) { return akn_m(ctx, text, font, { track: track }).w; }
function akn_fit(ctx, text, font, maxW, maxH, o) { var m = akn_m(ctx, text, font, o); return Math.min(maxW / m.w, maxH / m.h); }
// flow units into balanced lines (browser flowUnits); positions are unit centres relative to the box centre
function akn_parts(n, L) {
    var out = [];
    function rec(start, left, acc) {
        if (out.length > 80) return;
        if (left === 1) { out.push(acc.concat([[start, n]])); return; }
        for (var e = start + 1; e <= n - left + 1; e++) rec(e, left - 1, acc.concat([[start, e]]));
    }
    if (L >= 1 && L <= n) rec(0, L, []);
    return out;
}
function akn_flow(ctx, units, font, maxW, maxH, o) {
    o = o || {};
    var lat = false, i, L, q;
    for (i = 0; i < units.length; i++) if (jzHasLatin(units[i])) lat = true;
    var sp = o.sp != null ? o.sp : (lat ? 0.32 : 0.08), lead = o.lead || 1.22, track = o.track || 0, ws = [], n = units.length, best = null;
    for (i = 0; i < n; i++) ws.push(akn_em(ctx, units[i], font, track));
    for (L = 1; L <= Math.min(n, o.maxLines || 4); L++) {
        var pts = akn_parts(n, L), bp = null, bw = 1e9;
        for (q = 0; q < pts.length; q++) {
            var lw = [], mw = 0;
            for (var li = 0; li < pts[q].length; li++) { var a = pts[q][li][0], b = pts[q][li][1], w = 0; for (i = a; i < b; i++) w += ws[i] + (i > a ? sp : 0); lw.push(w); if (w > mw) mw = w; }
            if (mw < bw) { bw = mw; bp = { pt: pts[q], lw: lw }; }
        }
        if (!bp) continue;
        var size = Math.min(maxW / bw, maxH / (L * lead - (lead - 1)), o.maxSize || 1e9);
        if (!best || size > best.size * (o.lineBonus || 1.1)) best = { size: size, L: L, pt: bp.pt, lw: bp.lw };
    }
    var S = best.size, pos = [], mx = 0;
    for (li = 0; li < best.pt.length; li++) {
        var y = (li - (best.L - 1) / 2) * lead * S, x = -best.lw[li] * S / 2;
        if (best.lw[li] > mx) mx = best.lw[li];
        for (i = best.pt[li][0]; i < best.pt[li][1]; i++) { var ww = ws[i] * S; pos[i] = { x: x + ww / 2, y: y, w: ww, li: li }; x += ww + sp * S; }
    }
    return { size: S, pos: pos, L: best.L, w: mx * S, h: (best.L * lead - (lead - 1)) * S, ws: ws };
}
// colours
function akn_accOn(sc) { return jzContrast(sc.accent, sc.bg) >= 1.7 ? sc.accent : sc.fg; }
function akn_onCol(sc, fill) {
    var c = [sc.bg, sc.fg, sc.ink, sc.accent, sc.sub], best = null, bv = 0, i;
    for (i = 0; i < c.length; i++) { if (!c[i] || c[i] === fill) continue; var k = jzContrast(c[i], fill); if (k > bv) { bv = k; best = c[i]; } }
    return bv >= 2.6 ? best : (jzLum(fill) > 0.5 ? '#111111' : '#FFFFFF');
}
// expression header for a layout: timing (DUR, IN, OS, OD, PO, K) + easings + W, H
function akn_H(ctx, data) { return jzTH(ctx) + AKN_FNS + AKN_CI_L + 'var W=' + jzN(ctx.W) + ',H=' + jzN(ctx.H) + ';' + (data || '') + '\n'; }
var AKN_CI_L = 'function cix(ts,t){var k=-1;for(var q=0;q<ts.length;q++)if(t>=ts[q])k=q;return k;}';
function akn_A2(a) { var s = [], i; for (i = 0; i < a.length; i++) s.push(akn_arr(a[i])); return '[' + s.join(',') + ']'; }
function akn_null(ctx, name, x, y) {
    var N = ctx.comp.layers.addNull(ctx.comp.duration); N.name = name;
    jzXf(N, 'ADBE Anchor Point').setValue([0, 0]); jzXf(N, 'ADBE Position').setValue([x, y]);
    return jzNoGhost(N);
}
// a lyric piece on a rig. o: jzText options + mi, noHold, H (header with U), pos / sc / rot / a expressions (pos -> [x, y] absolute,
// sc -> [sx, sy] factors, rot -> degrees, a -> 0..1 alpha)
function akn_word(ctx, str, o) {
    var N = akn_null(ctx, 'kn rig ' + String(str).substr(0, 12), o.x, o.y), L = jzText(ctx, str, o), bb = jzBB(L);
    L.parent = N;
    if (o.a) jzAnimator(L, 'JZ Layout Alpha', [['ADBE Text Opacity', 0]], o.H + 'var al=(' + o.a + ');(1-cl(al))*100');
    jzAnimate(ctx, L, { mi: o.mi || 0, noHold: o.noHold });
    if (o.pos) jzSetExpr(jzXf(N, 'ADBE Position'), o.H + 'var u=' + o.pos + ';[u[0],u[1]]');
    if (o.sc) jzSetExpr(jzXf(N, 'ADBE Scale'), o.H + 'var u=' + o.sc + ';[u[0]*100,u[1]*100]');
    if (o.rotE) jzSetExpr(jzXf(N, 'ADBE Rotate Z'), o.H + o.rotE);
    return { N: N, L: L, bb: bb };
}
// straight line / bar as a shape layer: rect w x h, anchor on its left edge (scale x reveals it)
function akn_bar(ctx, name, x, y, w, h, col, opacity) {
    var S = jzShapeLayer(ctx, name, x, y), g = jzGrp(S, name);
    jzAddRect(g, w, h, 0, w / 2, 0); jzAddFill(g, col);
    if (opacity != null) jzXf(S, 'ADBE Opacity').setValue(opacity * 100);
    return jzNoGhost(S);
}
function akn_bbL(list) { var b = null, i; for (i = 0; i < list.length; i++) b = jzUnion(b, list[i].bb || jzBB(list[i])); return b; }

/* ================================================================== 1 knSlamStack \u2014 \u7A4D\u307F\u4E0A\u3052 */
jzReg('layout', 'knSlamStack', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display'])), align: rng.pick(['center', 'center', 'left', 'right']), from: rng.pick(['scale', 'scale', 'drop', 'side']),
            acc: rng.int(0, 5), tilt: rng.chance(0.35) ? rng.range(2, 4) * rng.pick([1, -1]) : 0, rule: rng.chance(0.55) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), i, k;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), al = jzP(ctx, 'align', 'center'), from = jzP(ctx, 'from', 'scale'), tilt = jzP(ctx, 'tilt', 0), acc = jzP(ctx, 'acc', 0);
        var units = akn_units(c, port ? 6 : 5, 2), n = units.length, ts = akn_onsets(c, n, 0.42, 0.32), ms = [];
        for (i = 0; i < n; i++) ms.push(akn_em(ctx, units[i], font, 0.02));
        var maxW = W * (tilt ? 0.8 : 0.86), Hb = H * (port ? 0.66 : 0.8), cap = Math.min(H * 0.34, W * 0.44), gap = 0.08;
        function lay(kk) {
            var w = maxW, sizes = [], q, j;
            for (q = 0; q < 5; q++) {
                sizes = []; var lo = 1e9;
                for (j = 0; j < kk; j++) { sizes.push(Math.min(w / ms[j], cap)); lo = Math.min(lo, sizes[j]); }
                var S = 0; for (j = 0; j < kk; j++) { sizes[j] = Math.min(sizes[j], lo * 2.2); S += sizes[j]; }
                S += gap * sizes[0] * (kk - 1);
                if (S <= Hb) break;
                w *= Hb / S;
            }
            var g = gap * sizes[0], tot = 0; for (j = 0; j < kk; j++) tot += sizes[j]; tot += g * (kk - 1);
            var y = -tot / 2, ys = [], sw = 0;
            for (j = 0; j < kk; j++) { ys.push(y + sizes[j] / 2); y += sizes[j] + g; sw = Math.max(sw, sizes[j] * ms[j]); }
            return { sizes: sizes, ys: ys, sw: sw };
        }
        var SA = [], YA = [], SW = [];
        for (k = 1; k <= n; k++) { var A = lay(k); SA.push(A.sizes); YA.push(A.ys); SW.push(A.sw); }
        var F = lay(n), cx = W / 2, cy = H / 2;
        var HD = akn_H(ctx, 'var TS=' + akn_arr(ts) + ',SA=' + akn_A2(SA) + ',YA=' + akn_A2(YA) + ',SW=' + akn_arr(SW) + ',AL=' + (al === 'left' ? -1 : al === 'right' ? 1 : 0) +
            ',FR=' + (from === 'scale' ? 0 : from === 'drop' ? 1 : 2) + ',TL=' + jzN(tilt) + ',GP=' + gap + ';' +
            'function U(I,t){var k=cix(TS,t)+1;if(k<I+1)k=I+1;var A=SA[k-1],B=k>1?SA[k-2]:A,e=oe((t-TS[k-1])/0.34),fr=I===k-1;' +
            'var size=fr?A[I]:lrp(B[I],A[I],e),y=fr?YA[k-1][I]:lrp(YA[k-2][I],YA[k-1][I],e),sw=lrp(k>1?SW[k-2]:SW[k-1],SW[k-1],e),x=AL*sw/2,rot=0,al=1,q=(t-TS[I])/0.24;' +
            'if(q<1){var f=1-oe(cl(q));if(FR===0){size*=1+1.6*f;al=cl(q*5);rot=f*8*(I%2?1:-1);}else if(FR===1){y-=H*0.7*Math.pow(1-cl(q),2);}else{x+=(I%2?1:-1)*W*0.9*f;}}' +
            'var ld=t-TS[k-1]-0.12;if(!fr&&k>1&&ld>0&&ld<0.6)y+=A[k-1]*0.07*sprg(ld,9,26);var px=x,py=y;if(TL){var v=rvx(x,y,TL);px=v[0];py=v[1];rot+=TL;}' +
            'return [' + jzN(cx) + '+px,' + jzN(cy) + '+py,size,rot,al,sw,y];}');
        var Ls = [], accC = akn_accOn(sc);
        for (i = 0; i < n; i++) {
            var fs = F.sizes[i], x = cx + (al === 'left' ? -F.sw / 2 : al === 'right' ? F.sw / 2 : 0), y = cy + F.ys[i];
            if (tilt) { var v = [x - cx, y - cy], r = [v[0] * Math.cos(tilt * Math.PI / 180) - v[1] * Math.sin(tilt * Math.PI / 180), v[0] * Math.sin(tilt * Math.PI / 180) + v[1] * Math.cos(tilt * Math.PI / 180)]; x = cx + r[0]; y = cy + r[1]; }
            var w = akn_word(ctx, units[i], { font: font, size: fs, color: n > 1 && i === acc % n ? accC : sc.fg, x: x, y: y, align: al === 'left' ? 'left' : al === 'right' ? 'right' : 'center', track: 0.02,
                mi: akn_mi(ctx, ts[i]), H: HD, pos: 'U(' + i + ',time)', sc: '(function(){var z=U(' + i + ',time)[2]/' + jzN(fs) + ';return [z,z];})()', rotE: 'U(' + i + ',time)[3]', a: from === 'scale' ? 'U(' + i + ',time)[4]' : null });
            Ls.push(w);
        }
        // hairline rules between the stacked lines draw in as each next line lands
        if (jzP(ctx, 'rule', false) && !tilt) {
            var lw = Math.max(1.5 * ctx.u, u * 0.003);
            for (i = 0; i < n - 1; i++) {
                var R = akn_bar(ctx, 'kn stack rule', cx - F.sw / 2, cy, 1, lw, sc.sub, 0.8);
                jzSetExpr(jzXf(R, 'ADBE Position'), HD + 'var u=U(' + i + ',time),k=cix(TS,time)+1;k=Math.max(1,k);[' + jzN(cx) + '-u[5]/2,' + jzN(cy) + '+u[6]+u[2]*0.5+SA[k-1][0]*GP*0.5]');
                jzSetExpr(jzXf(R, 'ADBE Scale'), HD + 'var u=U(' + i + ',time),re=oe((time-TS[' + (i + 1) + ']-0.05)/0.35)*K;[u[5]*re*100,100]');
                R.moveToEnd();
            }
        }
        return akn_bbL(Ls);
    }
});

/* ================================================================== 2 knQuarterTurn \u2014 \u76F4\u89D2\u30BF\u30FC\u30F3 */
jzReg('layout', 'knQuarterTurn', {
    plan: function (rng, cut, st) { return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), sgn: rng.pick([1, -1]), end: 'all', acc: rng.int(0, 3), joint: rng.chance(0.6) }; },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, i, font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), sgn = jzP(ctx, 'sgn', 1), acc = jzP(ctx, 'acc', 0);
        var units = akn_units(c, 4, 2), n = units.length, ts = akn_onsets(c, n, 0.46, 0.5), Lm = [], la = [];
        for (i = 0; i < n; i++) { Lm.push(akn_em(ctx, units[i], font, 0.03)); var cs = jzChars(jzTrim(units[i])); la.push(akn_em(ctx, cs[cs.length - 1] || '\u5B57', font, 0)); }
        function rv(x, y, a) { var cc = Math.cos(a * Math.PI / 180), s = Math.sin(a * Math.PI / 180); return [x * cc - y * s, x * s + y * cc]; }
        var ch = [], sx0 = 0, sy0 = 0, a = 0;
        for (i = 0; i < n; i++) {
            if (i > 0) {
                var pd = rv(1, 0, a), na = a + 90 * sgn * (i % 2 ? 1 : -1), nd = rv(1, 0, na), pe = ch[i - 1];
                sx0 = pe.ex - pd[0] * la[i - 1] * 0.5 + nd[0] * 0.62; sy0 = pe.ey - pd[1] * la[i - 1] * 0.5 + nd[1] * 0.62; a = na;
            }
            var d = rv(1, 0, a);
            ch.push({ a: a, cx: sx0 + d[0] * Lm[i] / 2, cy: sy0 + d[1] * Lm[i] / 2, ex: sx0 + d[0] * Lm[i], ey: sy0 + d[1] * Lm[i] });
        }
        var CR = [], CX = [], CY = [], CZ = [], x0 = 1e9, y0 = 1e9, x1 = -1e9, y1 = -1e9;
        for (i = 0; i < n; i++) {
            var g = ch[i]; CR.push(-g.a); CX.push(g.cx); CY.push(g.cy); CZ.push(Math.min(W * 0.76 / Lm[i], H * 0.3, W * 0.42));
            var hw = Lm[i] / 2, hh = 0.55, vv = Math.abs(g.a % 180) > 45, w2 = vv ? hh : hw, h2 = vv ? hw : hh;
            x0 = Math.min(x0, g.cx - w2); x1 = Math.max(x1, g.cx + w2); y0 = Math.min(y0, g.cy - h2); y1 = Math.max(y1, g.cy + h2);
        }
        var ov = { x: (x0 + x1) / 2, y: (y0 + y1) / 2, z: Math.min(W * 0.84 / (x1 - x0), H * 0.78 / (y1 - y0), H * 0.3) };
        var tOv = Math.min(ts[n - 1] + 0.62, Math.max(ts[n - 1] + 0.3, c.dur - (c.outDur || 0) - 0.55)), useOv = jzP(ctx, 'end', 'all') !== 'last';
        var FS = 0; for (i = 0; i < n; i++) FS = Math.max(FS, CZ[i]); FS = Math.max(FS, ov.z);
        var jx = [], jy = [];
        for (i = 1; i < n; i++) { var pg = ch[i - 1], pd2 = rv(1, 0, pg.a); jx.push(pg.ex + pd2[0] * 0.35); jy.push(pg.ey + pd2[1] * 0.35); }
        var HD = akn_H(ctx, 'var TS=' + akn_arr(ts) + ',CR=' + akn_arr(CR) + ',CX=' + akn_arr(CX) + ',CY=' + akn_arr(CY) + ',CZ=' + akn_arr(CZ) + ',GA=' + akn_arr(CR) +
            ',JX=' + akn_arr(jx.concat([0])) + ',JY=' + akn_arr(jy.concat([0])) + ',OV=[0,' + jzN(ov.x) + ',' + jzN(ov.y) + ',' + jzN(ov.z) + '],TOV=' + jzN(tOv) + ',UO=' + (useOv ? 1 : 0) + ',NU=' + n + ';' +
            'function CAM(t){var k=cix(TS,t);if(k<0)k=0;var a=Math.max(0,k-1),A=[CR[a],CX[a],CY[a],CZ[a]],B=[CR[k],CX[k],CY[k],CZ[k]],e=k>0?ioc(cl((t-TS[k])/0.36)):1,sm=a!==k?1:0;' +
            'if(UO&&t>=TOV){A=[CR[NU-1],CX[NU-1],CY[NU-1],CZ[NU-1]];B=OV;e=ioc(cl((t-TOV)/0.5));sm=1;}var dp=1-0.22*bel(e)*sm;' +
            'return [lrp(A[0],B[0],e),lrp(A[1],B[1],e),lrp(A[2],B[2],e),Math.exp(lrp(Math.log(A[3]),Math.log(B[3]),e))*dp];}' +
            'function U(I,t){var C=CAM(t),v=rvx(CX[I]-C[1],CY[I]-C[2],C[0]),q=cl((t-TS[I])/0.22),pp=I===0?1:lrp(0.3,1,ob(q,2.2));' +
            'return [W/2+v[0]*C[3],H/2+v[1]*C[3],C[3]*pp,-GA[I]+C[0],I===0?1:cl(q*4)];}');
        var Ls = [], accC = akn_accOn(sc);
        for (i = 0; i < n; i++) {
            var p0 = rv(CX[i] - ov.x, CY[i] - ov.y, 0), fx = W / 2 + p0[0] * ov.z, fy = H / 2 + p0[1] * ov.z;
            var w = akn_word(ctx, units[i], { font: font, size: FS, color: i === acc % n ? accC : sc.fg, x: fx, y: fy, track: 0.03, mi: akn_mi(ctx, ts[i]), H: HD,
                pos: 'U(' + i + ',time)', sc: '(function(){var z=U(' + i + ',time)[2]/' + jzN(FS) + ';return [z,z];})()', rotE: 'U(' + i + ',time)[3]', a: i ? 'U(' + i + ',time)[4]' : null });
            var zb = w.bb, zk = ov.z / FS, vv = Math.abs(ch[i].a % 180) > 45, hw2 = (zb.x1 - zb.x0) / 2 * zk, hh2 = (zb.y1 - zb.y0) / 2 * zk;
            if (vv) { var tq = hw2; hw2 = hh2; hh2 = tq; }
            Ls.push({ bb: { x0: fx - hw2, x1: fx + hw2, y0: fy - hh2, y1: fy + hh2, cx: fx, cy: fy } });
            // joint mark: a small accent square where the line turns
            if (jzP(ctx, 'joint', false) && i > 0) {
                var S = jzShapeLayer(ctx, 'kn joint', W / 2, H / 2), gq = jzGrp(S, 'joint'); jzAddRect(gq, 1, 1); jzAddFill(gq, sc.accent);
                var J = 'var C=CAM(time),v=rvx(JX[' + (i - 1) + ']-C[1],JY[' + (i - 1) + ']-C[2],C[0]),q=cl((time-TS[' + i + '])/0.22),s=C[3]*0.16*ob(cl(q*1.3),2)*K;';
                jzSetExpr(jzXf(S, 'ADBE Position'), HD + J + '[W/2+v[0]*C[3],H/2+v[1]*C[3]]');
                jzSetExpr(jzXf(S, 'ADBE Scale'), HD + J + 's=time<TS[' + i + ']?0:s;[s*100,s*100]');
                jzSetExpr(jzXf(S, 'ADBE Rotate Z'), HD + J + 'C[0]+45');
                jzNoGhost(S);
            }
        }
        // the lyric layers were made at the overview framing (static values = final look)
        return akn_bbL(Ls);
    }
});

/* ================================================================== 3 knSwapCenter \u2014 \u5165\u308C\u66FF\u308F\u308A */
jzReg('layout', 'knSwapCenter', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display'])), fontL: rng.pick(jzFontsOf(st, ['display', 'serif'])), mode: rng.pick(['roll', 'punch', 'slide', 'roll']), ticks: rng.chance(0.7), acc: rng.chance(0.5) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), i;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), fontL = jzP(ctx, 'fontL', font), mode = jzP(ctx, 'mode', 'roll'), acc = jzP(ctx, 'acc', false);
        var units = akn_units(c, 6, 2), n = units.length, ts = akn_onsets(c, n, 0.4, 0.36), big = [];
        for (i = 0; i < n; i++) big.push(Math.min(akn_fit(ctx, units[i], font, W * 0.8, H * 0.4, { track: 0.02 }), H * 0.34, W * 0.6));
        var F = akn_flow(ctx, units, fontL, W * 0.84, H * (port ? 0.46 : 0.34), { maxSize: H * (port ? 0.13 : 0.2), track: 0.03 });
        var tR = ts[n - 1] + 0.5, lim = c.dur - (c.outDur || 0) - 0.4;
        if (tR > lim) tR = Math.max(ts[n - 1] + 0.18, lim);
        var cx = W / 2, cy = H / 2, accC = akn_accOn(sc), PX = [], PY = [];
        for (i = 0; i < n; i++) { PX.push(F.pos[i].x); PY.push(F.pos[i].y); }
        var HD = akn_H(ctx, 'var TS=' + akn_arr(ts) + ',BG=' + akn_arr(big) + ',PX=' + akn_arr(PX) + ',PY=' + akn_arr(PY) + ',FSZ=' + jzN(F.size) + ',TR=' + jzN(tR) + ',MD=' + (mode === 'punch' ? 1 : mode === 'slide' ? 2 : 0) + ',NU=' + n + ',CX=' + jzN(cx) + ',CY=' + jzN(cy) + ';' +
            // phase 1: [x, y, size, sy, alpha] of the big word I
            'function B1(I,t){var k=cix(TS,t);if(k<0||t>=TR)return [CX,CY,BG[I],1,0];var e=k>0?oe(cl((t-TS[k])/0.26)):1,old;if(I===k)old=0;else if(I===k-1&&e<1)old=1;else return [CX,CY,BG[I],1,0];' +
            'var q=e,s0=BG[I],x=CX,y=CY,size=s0,sy=1,al=1;if(MD===0){y+=s0*1.05*(old?-q:(1-q));sy=old?1-q:q;}else if(MD===1){size=s0*(old?lrp(1,0.45,q):lrp(1.9,1,q));al=old?1-q:cl(q*3);}' +
            'else{x+=(old?-q:1-ob(q,1.3))*W*0.7;al=old?1-q*q:1;}if(sy<0.02)al=0;return [x,y,size,Math.max(0.001,sy),al];}' +
            // phase 2: the line resolves
            'function B2(I,t){var last=I===NU-1,q=oe(cl((t-TR-(last?0:0.05+I*0.035))/0.42));return [lrp(CX,CX+PX[I],q),lrp(CY,CY+PY[I],q),last?lrp(BG[I],FSZ,q):FSZ*lrp(0.3,1,q),1,last?1:cl(q*2.5)];}');
        var Ls = [];
        for (i = 0; i < n; i++) {
            var col = acc && i % 2 ? accC : sc.fg, last = i === n - 1;
            var wb = akn_word(ctx, units[i], { font: font, size: big[i], color: col, x: cx, y: cy, track: 0.02, mi: akn_mi(ctx, ts[i]), H: HD,
                pos: (last ? '(time<TR?B1(' + i + ',time):B2(' + i + ',time))' : 'B1(' + i + ',time)'),
                sc: '(function(){var b=' + (last ? '(time<TR?B1(' + i + ',time):B2(' + i + ',time))' : 'B1(' + i + ',time)') + ',z=b[2]/' + jzN(big[i]) + ';return [z,z*b[3]];})()',
                a: last ? '(time<TR?B1(' + i + ',time)[4]:1)' : 'B1(' + i + ',time)[4]' });
            wb.L.outPoint = last ? Math.min(c.dur, tR + 0.03) : Math.min(c.dur, tR);
            // the resolved line (font of the full line)
            var ws = akn_word(ctx, units[i], { font: fontL, size: F.size, color: col, x: cx + PX[i], y: cy + PY[i], track: 0.03, mi: last ? akn_mi(ctx, ts[i]) : akn_mi(ctx, tR), H: HD,
                pos: 'B2(' + i + ',time)', sc: '(function(){var z=B2(' + i + ',time)[2]/FSZ;return [z,z];})()', a: last ? null : 'B2(' + i + ',time)[4]' });
            ws.L.inPoint = Math.max(0, last ? tR + 0.03 : tR);
            Ls.push(ws);
        }
        // progress ticks under the word
        if (jzP(ctx, 'ticks', false) && n > 1) {
            var tw = u * 0.035, th = Math.max(3 * ctx.u, u * 0.006), gx = tw * 1.5, S = jzShapeLayer(ctx, 'kn swap ticks', cx, cy);
            for (i = 0; i < n; i++) {
                var g = jzGrp(S, 'tick ' + i);
                jzAddRect(g, tw, th, 0, (i - (n - 1) / 2) * gx, 0);
                var fl = jzAddFill(g, sc.sub);
                jzSetExpr(fl.property('ADBE Vector Fill Color'), HD + 'var on=' + i + '<=cix(TS,time);on?' + akn_col(accC) + ':' + akn_col(sc.sub));
                jzSetExpr(fl.property('ADBE Vector Fill Opacity'), HD + i + '<=cix(TS,time)?100:40');
            }
            jzSetExpr(jzXf(S, 'ADBE Position'), HD + 'var k=Math.max(0,cix(TS,time));[CX,Math.min(H*0.9,CY+BG[k]*0.5+' + jzN(u * 0.08) + ')]');
            jzSetExpr(jzXf(S, 'ADBE Opacity'), HD + '100*K*cl(time/0.2)');
            S.outPoint = Math.min(c.dur, tR);
            jzNoGhost(S);
        }
        return akn_bbL(Ls);
    }
});
function akn_col(hex) { var c = jzHex(hex); return '[' + jzN(c[0]) + ',' + jzN(c[1]) + ',' + jzN(c[2]) + ',1]'; }

/* ================================================================== 4 knZoomDive \u2014 \u6587\u5B57\u3078\u6F5C\u308B */
// centre offset (em) of a word's focus glyph: first kanji, else the middle glyph
function akn_focus(text, track) {
    var cs = jzChars(text), ad = [], tot = 0, i, fi = -1, ks = [];
    for (i = 0; i < cs.length; i++) { ad.push(akn_adv(cs[i]) + (i < cs.length - 1 ? track : 0)); tot += ad[i]; if (!akn_isSp(cs[i])) { ks.push(i); if (fi < 0 && jzIsKanji(cs[i])) fi = i; } }
    if (fi < 0) fi = ks.length ? ks[Math.floor(ks.length / 2)] : 0;
    var x = 0; for (i = 0; i < fi; i++) x += ad[i];
    return (x + akn_adv(cs[fi] || ' ') / 2) - tot / 2;
}
jzReg('layout', 'knZoomDive', {
    plan: function (rng, cut, st) { return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), cap: rng.chance(0.75), acc: rng.chance(0.5) }; },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), i, font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), acc = jzP(ctx, 'acc', false);
        var units = akn_units(c, 5, 2), n = units.length, ts = akn_onsets(c, n, 0.5, 0.55), sz = [], fx = [];
        for (i = 0; i < n; i++) {
            sz.push(Math.min(akn_fit(ctx, units[i], font, W * 0.78, H * 0.4, { track: 0.02 }), H * 0.34, W * 0.6));
            fx.push(akn_focus(units[i], 0.02) * akn_em(ctx, units[i], font, 0.02) / Math.max(0.1, (function (t) { var cs = jzChars(t), s = 0; for (var q = 0; q < cs.length; q++) s += akn_adv(cs[q]) + 0.02; return s; })(units[i])));
        }
        var cx = W / 2, cy = H / 2, accC = akn_accOn(sc);
        var HD = akn_H(ctx, 'var TS=' + akn_arr(ts) + ',SZ0=' + akn_arr(sz) + ',FX=' + akn_arr(fx) + ',T=0.42,CX=' + jzN(cx) + ',CY=' + jzN(cy) + ';' +
            'function U(I,t){var k=cix(TS,t);if(k===I){var q=k>0?oe(cl((t-TS[k]-T*0.35)/0.45)):1;return [CX,CY,SZ0[I]*lrp(0.03,1,q),cl(q*3)];}' +
            'if(k===I+1){var e=cl((t-TS[k])/T);if(e<1){var s0=SZ0[I],f=FX[I]*s0,ie=ioc(e),px=lrp(CX+f,CX,ie),s=Math.exp(Math.pow(e,1.6)*Math.log(36));return [px-f*s,CY,s0*s,1-smo(0.55,1,e)];}}' +
            'return [CX,CY,SZ0[I],0];}');
        var bb = null;
        for (i = 0; i < n; i++) {
            var w = akn_word(ctx, units[i], { font: font, size: sz[i], color: acc && i % 2 ? accC : sc.fg, x: cx, y: cy, track: 0.02, mi: akn_mi(ctx, ts[i]), H: HD, noHold: i < n - 1,
                pos: 'U(' + i + ',time)', sc: '(function(){var z=U(' + i + ',time)[2]/' + jzN(sz[i]) + ';return [z,z];})()', a: 'U(' + i + ',time)[3]' });
            if (i < n - 1) w.L.outPoint = Math.min(c.dur, ts[i + 1] + 0.43);
            if (i === n - 1) bb = w.bb;
        }
        // the whole line as a caption once the last word has settled
        if (jzP(ctx, 'cap', false) && n > 1 && bb) {
            var t = jzFlat(c.text), bf = jzBodyF(ctx), fs = Math.min(jzClamp(u * 0.034, 14 * ctx.u, 40 * ctx.u), akn_fit(ctx, t, bf, W * 0.8, H * 0.1, { track: 0.18 }));
            var y = Math.min(H * 0.92, bb.y1 + fs * 1.6), T = jzText(ctx, t, { font: bf, size: fs, track: 0.18, x: cx, y: y, color: sc.sub });
            var a = HD + 'var a=oc((time-' + jzN(ts[n - 1] + 0.55) + ')/0.35)*K;';
            jzSetExpr(jzXf(T, 'ADBE Opacity'), a + 'a*100');
            var tw = jzSize(T)[0], R = akn_bar(ctx, 'kn caption rule', cx - tw / 2, y - fs * 1.1, tw, Math.max(1, u * 0.002), sc.sub, 0.6);
            jzSetExpr(jzXf(R, 'ADBE Position'), a + '[' + jzN(cx) + '-' + jzN(tw / 2) + '*a,value[1]]');
            jzSetExpr(jzXf(R, 'ADBE Scale'), a + '[a*100,100]');
            jzNoGhost(T);
        }
        return bb || { x0: W * 0.3, y0: H * 0.4, x1: W * 0.7, y1: H * 0.6, cx: W / 2, cy: H / 2 };
    }
});

/* ================================================================== 5 knFlowSnap \u2014 \u6D41\u308C\u3066\u6574\u5217 */
jzReg('layout', 'knFlowSnap', {
    plan: function (rng, cut, st) { return { font: rng.pick(jzFontsOf(st, ['display', 'body'])), amp: rng.range(0.1, 0.16), lam: rng.range(0.45, 0.7), grid: rng.pick(['cells', 'cells', 'rules']), acc: rng.int(0, 15) }; },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), i, j, font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display'));
        var lat = jzHasLatin(c.text), t0 = jzFlat(c.text), chars = jzChars(t0), n = chars.length, ft, trk, rows = [];
        if (lat) { ft = jzSplitLines(t0, port ? 10 : 18); trk = 0.02; }
        else {
            var c0 = Math.max(2, Math.min(port ? 4 : 6, Math.ceil(Math.sqrt(n * (port ? 0.7 : 1.6))))), cols = Math.ceil(n / Math.ceil(n / c0));
            for (i = 0; i < n; i += cols) rows.push(chars.slice(i, i + cols).join(''));
            ft = rows.join('\r'); trk = 0.3;
        }
        var lead = lat ? 1.25 : 1.3, m = akn_m(ctx, ft, font, { track: trk, lead: lead }), fsz = Math.min(W * 0.8 / m.w, H * (port ? 0.5 : 0.62) / m.h, H * 0.2);
        var L = jzText(ctx, ft, { font: font, size: fsz, color: sc.fg, x: W / 2, y: H / 2, track: trk, leading: fsz * lead, align: lat ? 'center' : 'left' });
        var r = jzRect(L);
        if (!lat) jzXf(L, 'ADBE Position').setValue([W / 2 - r.width / 2, H / 2]);
        var acc = jzP(ctx, 'acc', 0), accC = akn_accOn(sc);
        if (!lat) { var fl = []; for (i = 0; i < n; i++) fl.push(i === acc % n); jzCharColors(L, accC, fl, 'JZ Layout Accent'); }
        // final glyph centres (comp px) and the train
        var G = akn_geo(L, fsz), ap = jzXf(L, 'ADBE Anchor Point').value, lp = jzXf(L, 'ADBE Position').value, FX = [], FY = [], advs = [], totA = 0;
        for (i = 0; i < G.n; i++) {
            FX.push(lp[0] + G.g[i].x - ap[0]); FY.push(lp[1] + G.g[i].y - ap[1]);
            advs.push(akn_adv(G.g[i].ch) * 1.08 + (i > 0 && G.g[i].li !== G.g[i - 1].li ? 0.35 : 0)); totA += advs[i];
        }
        var tS = jzClamp(c.dur * 0.42, 0.45, 1.5), fs = Math.min(H * 0.15, W * 0.95 / Math.max(4, totA)), offs = [], acA = 0;
        for (i = 0; i < G.n; i++) { offs.push((acA + advs[i] / 2) * fs); acA += advs[i]; }
        var Xs = W / 2 - totA * fs / 2, X0 = W + fs * 0.8, A = Math.min(H * jzP(ctx, 'amp', 0.13), W * 0.16), lam = W * jzP(ctx, 'lam', 0.55) * (port ? 1.7 : 1);
        var HG = akn_H(ctx, 'var FX=' + akn_arr(FX) + ',FY=' + akn_arr(FY) + ',OF=' + akn_arr(offs) + ',TSN=' + jzN(tS) + ',XS=' + jzN(Xs) + ',XZ=' + jzN(X0) + ',AM=' + jzN(A) + ',LM=' + jzN(lam) + ',RS=' + jzN(fs / fsz) + ';' +
            'function cy0(x,t){return H/2+AM*Math.sin(x/LM*6.28319+t*3.2);}' +
            'var i=textIndex-1,tf=Math.min(time,TSN),x=lrp(XZ,XS,oc(tf/TSN))+(OF[i]||0),y=cy0(x,tf),rr=Math.atan((cy0(x+2,tf)-cy0(x-2,tf))/4)*57.2958,q=oe(cl((time-TSN-i*0.018)/0.3));\n');
        var KK = Math.max(W, H) * 2;
        jzAnimator(L, 'JZ Layout Flow Move', [['ADBE Text Position 3D', [KK, KK, 0]]], HG + '[(x-(FX[i]||0))*(1-q)/' + jzN(KK) + '*100,(y-(FY[i]||0))*(1-q)/' + jzN(KK) + '*100,0]');
        jzAnimator(L, 'JZ Layout Flow Turn', [['ADBE Text Rotation', 90]], HG + 'rr*(1-q)/90*100');
        jzAnimator(L, 'JZ Layout Flow Size', [['ADBE Text Scale 3D', [200, 200, 100]]], HG + 'var z=lrp(RS,1,q);(z-1)*100');
        jzAnimate(ctx, L, { mi: 0 });
        // grid lines draw in with the snap
        var lw = Math.max(1 * ctx.u, u * 0.0016), S = jzShapeLayer(ctx, 'kn flow grid', 0, 0), g = jzGrp(S, 'grid'), nL = G.nL, rh = fsz * lead, bx0 = lp[0] - ap[0] + r.left, by0 = lp[1] - ap[1] + r.top;
        var gy0 = (by0 + r.height / 2) - nL * rh / 2;
        if (!lat && jzP(ctx, 'grid', 'cells') === 'cells') {
            var cell = fsz * (1 + trk), ncol = 0; for (i = 0; i < rows.length; i++) ncol = Math.max(ncol, jzChars(rows[i]).length);
            var gx0 = bx0 - fsz * trk / 2 - (cell - fsz * (1 + trk)) / 2;
            for (i = 0; i <= nL; i++) jzAddPath(g, [[gx0, gy0 + i * rh], [gx0 + ncol * cell, gy0 + i * rh]], false);
            for (j = 0; j <= ncol; j++) jzAddPath(g, [[gx0 + j * cell, gy0], [gx0 + j * cell, gy0 + nL * rh]], false);
            jzAddStroke(g, sc.sub, lw, 55);
        } else {
            for (i = 0; i < nL; i++) {
                var lx0 = 1e9, lx1 = -1e9;
                for (j = 0; j < G.n; j++) if (G.g[j].li === i) { lx0 = Math.min(lx0, FX[j] - G.g[j].w / 2); lx1 = Math.max(lx1, FX[j] + G.g[j].w / 2); }
                if (lx1 > lx0) jzAddPath(g, [[lx0, gy0 + (i + 0.5) * rh + fsz * 0.62], [lx1, gy0 + (i + 0.5) * rh + fsz * 0.62]], false);
            }
            jzAddStroke(g, sc.sub, lw * 1.5, 70);
        }
        jzAddTrimPaths(g, HG.split('var i=textIndex')[0] + 'oe(cl((time-TSN-0.08)/0.5))*K*100');
        jzSetExpr(jzXf(S, 'ADBE Opacity'), jzTH(ctx) + 'time<' + jzN(tS + 0.08) + '?0:100');
        S.moveToEnd(); jzNoGhost(S);
        // the path the line flowed along (phase travel = sideways shift of one static wave)
        var pts = [], x;
        for (x = -W * 0.1 - lam; x <= W * 1.1 + lam * 0.2; x += W / 48) pts.push([x, H / 2 + A * Math.sin(x / lam * Math.PI * 2)]);
        var Pth = jzPathLayer(ctx, 'kn flow path', pts, sc.sub, { width: Math.max(1 * ctx.u, u * 0.0015) });
        jzSetExpr(jzXf(Pth, 'ADBE Position'), HG.split('var i=textIndex')[0] + '[-Math.min(time,TSN)*3.2*LM/6.28319,0]');
        jzSetExpr(jzXf(Pth, 'ADBE Opacity'), HG.split('var i=textIndex')[0] + '(1-cl((time-TSN)/0.25))*cl(time/0.15)*35');
        Pth.outPoint = Math.min(c.dur, tS + 0.3); Pth.moveToEnd(); jzNoGhost(Pth);
        return jzBB(L);
    }
});

/* ================================================================== 6 knSeesaw \u2014 \u30B7\u30FC\u30BD\u30FC */
jzReg('layout', 'knSeesaw', {
    plan: function (rng, cut, st) { return { font: rng.pick(jzFontsOf(st, ['display'])), order: rng.pick(['lr', 'lr', 'out']), acc: rng.chance(0.5), fulc: rng.pick(['tri', 'tri', 'round']) }; },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, port = jzPortrait(ctx), i, font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display'));
        var units = akn_units(c, 6, 2), n = units.length, F = akn_flow(ctx, units, font, W * 0.78, H * 0.2, { maxLines: 1, maxSize: H * 0.17, track: 0.03, sp: jzHasLatin(c.text) ? 0.4 : 0.22 });
        var size = F.size, th = Math.max(4 * ctx.u, size * 0.1), half = F.w / 2 + size * 0.6, px = W / 2, py = H * (port ? 0.55 : 0.6);
        var ord = []; for (i = 0; i < n; i++) ord.push(i);
        if (jzP(ctx, 'order', 'lr') === 'out') ord.sort(function (a, b) { return Math.abs(F.pos[a].x) - Math.abs(F.pos[b].x); });
        var ts0 = akn_onsets(c, n, 0.45, 0.4), fall = 0.24, tl = [], mass = [], M = 0, mx = 0;
        for (i = 0; i < n; i++) { mass.push(akn_gc(units[i])); M += mass[i]; mx = Math.max(mx, mass[i]); }
        for (i = 0; i < n; i++) tl[ord[i]] = ts0[i] + fall;
        var targ = [0], tq = 0;
        for (i = 0; i < n; i++) { tq += mass[ord[i]] * F.pos[ord[i]].x; targ.push(i === n - 1 ? 0 : jzClamp(tq / (M * half) * 30, -11, 11)); }
        var PXs = []; for (i = 0; i < n; i++) PXs.push(F.pos[i].x);
        var HD = akn_H(ctx, 'var T0=' + akn_arr(ts0) + ',TG=' + akn_arr(targ) + ',TL=' + akn_arr(tl) + ',PXA=' + akn_arr(PXs) + ',FL=0.24,SZ0=' + jzN(size) + ',TH=' + jzN(th) + ',PX=' + jzN(px) + ',PY=' + jzN(py) + ';' +
            'function ANG(t){var a=0;for(var r=0;r<T0.length;r++){var t0=T0[r]+FL;if(t<t0)break;a=TG[r+1]+(TG[r]-TG[r+1])*Math.exp(-3.6*(t-t0))*Math.cos(10*(t-t0));}return a;}' +
            'function U(I,t){var ang=ANG(t),cr=Math.cos(ang*0.0174533),sr=Math.sin(ang*0.0174533),v=-TH/2-SZ0*0.54,x=PX+PXA[I]*cr-v*sr,y=PY+PXA[I]*sr+v*cr,rot=ang,sx=1,sy=1,d=t-TL[I];' +
            'if(d<0){var k=Math.min(1,-d/FL);y-=H*0.55*k*k;rot=ang*(1-k);}else{var q=Math.exp(-d*11)*0.22;sy=1-q;sx=1+q*0.6;x+=sr*SZ0*q*0.5;y+=cr*SZ0*q*0.5;}return [x,y,sx,sy,rot];}');
        var accC = akn_accOn(sc), fh = size * 0.62;
        // fulcrum + plank
        var Fu;
        if (jzP(ctx, 'fulc', 'tri') === 'tri') { Fu = jzShapeLayer(ctx, 'kn fulcrum', px, py + th / 2); var gf = jzGrp(Fu, 'tri'); jzAddPath(gf, [[0, 0], [-fh * 0.62, fh], [fh * 0.62, fh]], true); jzAddFill(gf, accC); }
        else { Fu = jzShapeLayer(ctx, 'kn fulcrum', px, py + th / 2 + fh * 0.45); var ge = jzGrp(Fu, 'round'); jzAddEllipse(ge, fh * 0.9, fh * 0.9); jzAddFill(ge, accC); }
        jzSetExpr(jzXf(Fu, 'ADBE Scale'), HD + 'var e=oe(time/0.35);[e*100,e*100]');
        jzSetExpr(jzXf(Fu, 'ADBE Opacity'), HD + 'K*100');
        var Pk = jzRectLayer(ctx, 'kn plank', px, py, half * 2, th, sc.sub);
        jzSetExpr(jzXf(Pk, 'ADBE Rotate Z'), HD + 'ANG(time)');
        jzSetExpr(jzXf(Pk, 'ADBE Scale'), HD + '[oe(time/0.35)*100,100]');
        jzSetExpr(jzXf(Pk, 'ADBE Opacity'), HD + '90*K');
        jzNoGhost(Fu); jzNoGhost(Pk);
        var Ls = [], accOn = jzP(ctx, 'acc', false);
        for (i = 0; i < n; i++) {
            var w = akn_word(ctx, units[i], { font: font, size: size, color: accOn && mass[i] === mx ? accC : sc.fg, x: px + F.pos[i].x, y: py - th / 2 - size * 0.54, track: 0.03, mi: akn_mi(ctx, tl[i] - fall), H: HD,
                pos: 'U(' + i + ',time)', sc: '(function(){var v=U(' + i + ',time);return [v[2],v[3]];})()', rotE: 'U(' + i + ',time)[4]' });
            Ls.push(w);
        }
        return akn_bbL(Ls);
    }
});

/* ================================================================== 7 knTypeSlam \u2014 \u30BF\u30A4\u30D7\u2192\u30B9\u30E9\u30E0 */
jzReg('layout', 'knTypeSlam', {
    plan: function (rng, cut, st) { return { font: rng.pick(jzFontsOf(st, ['display'])), key: rng.pick(['long', 'long', 'last']), side: rng.pick(['below', 'below', 'above']), burst: rng.chance(0.7) }; },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), i, font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display'));
        var t0 = jzFlat(c.text), units = akn_units(c, 6, 1), ki = units.length - 1;
        if (jzP(ctx, 'key', 'long') === 'long') { var bv = -1; for (i = 0; i < units.length; i++) { var v = akn_gc(units[i]) + (/[\u4E00-\u9FFF]/.test(units[i]) ? 0.5 : 0); if (v > bv) { bv = v; ki = i; } } }
        var key = jzTrim(units[ki]), tf = jzMonoF(ctx);
        var ts = Math.min(akn_fit(ctx, t0, tf, W * 0.86, H * 0.08, { track: 0.06 }), u * 0.065);
        var ks = Math.min(akn_fit(ctx, key, font, W * 0.84, H * (port ? 0.3 : 0.44), { track: 0.01 }), H * 0.34, W * 0.5);
        var above = jzP(ctx, 'side', 'below') === 'above', ky = H / 2 + (above ? ts * 1.2 : -ts * 1.2), ty = above ? ky - ks * 0.62 - ts * 1.5 : ky + ks * 0.62 + ts * 1.5;
        var nG = jzChars(t0).length, tType = jzClamp(nG * 0.045, 0.25, Math.min(1.1, c.dur * 0.4)), tS = tType + 0.14, accC = akn_accOn(sc);
        // typed line: glyph by glyph, shaken by the impact
        var Tn = akn_null(ctx, 'kn typed rig', W / 2, ty), T = jzText(ctx, t0, { font: tf, size: ts, color: sc.fg, x: W / 2, y: ty, track: 0.06 });
        var tw = jzSize(T)[0], x0 = W / 2 - tw / 2;
        T.parent = Tn;
        var HD = akn_H(ctx, 'var TT=' + jzN(tType) + ',TSL=' + jzN(tS) + ',NG=' + nG + ';');
        jzAnimator(T, 'JZ Layout Type', [['ADBE Text Opacity', 0]], HD + 'textIndex>Math.floor(cl(time/TT)*NG+1e-6)?100:0');
        jzAnimate(ctx, T, { mi: 0 });
        jzSetExpr(jzXf(Tn, 'ADBE Position'), HD + 'var im=time-TSL-0.1,sh=im>0?Math.exp(-im*10)*' + jzN(ts * 0.35) + ':0;posterizeTime(24);seedRandom(' + (c.seed % 9973) + '+Math.floor(time*24),true);[value[0]+random(-1,1)*sh,value[1]+random(-1,1)*sh]');
        // cursor riding at the end of the typed text
        var G = akn_geo(T, ts), RE = [0], ap = jzXf(T, 'ADBE Anchor Point').value;
        for (i = 0; i < G.n; i++) RE.push(G.g[i].x + G.g[i].w / 2 - ap[0] + tw / 2);
        var Cu = jzRectLayer(ctx, 'kn cursor', 0, ty, ts * 0.55, ts, accC);
        jzSetExpr(jzXf(Cu, 'ADBE Position'), HD + 'var RE=' + akn_arr(RE) + ',k=Math.floor(cl(time/TT)*NG+1e-6),wk=k>=NG?' + jzN(tw + ts) + ':RE[k]+(k>0?' + jzN(ts * 0.06) + ':0);[' + jzN(x0) + '+wk+' + jzN(ts * 0.1 + ts * 0.275) + ',value[1]]');
        jzSetExpr(jzXf(Cu, 'ADBE Opacity'), HD + 'var k=Math.floor(cl(time/TT)*NG+1e-6);(k<NG||Math.floor(time*12)%2===0)&&time<TSL+0.6?100*K:0');
        jzNoGhost(Cu);
        // key word slams in
        var HK = HD + 'var q=cl((time-TSL)/0.16),f=1-iq(q),ld=time-TSL-0.16,sq=ld>0?Math.exp(-ld*12)*0.12:0;';
        var kw = akn_word(ctx, key, { font: font, size: ks, color: sc.fg, x: W / 2, y: ky, track: 0.01, mi: akn_mi(ctx, tS), H: HK, sc: '[(1+2.4*f)*(1+sq*0.5),(1+2.4*f)*(1-sq)]', a: 'cl(q*4)' });
        var kb = kw.bb, mw = kb.x1 - kb.x0, mh = kb.y1 - kb.y0;
        if (jzP(ctx, 'burst', false)) {
            var lw = Math.max(2 * ctx.u, u * 0.004), R0 = Math.max(mw, mh) * 0.55 + ks * 0.9 * 0.15, Rm = R0 + u * 0.25 * 0.5 + u * 0.05 + 2, syy = mh / Math.max(mw, mh) + 0.35;
            var B = jzShapeLayer(ctx, 'kn slam burst', W / 2, ky), gb = jzGrp(B, 'rays');
            for (i = 0; i < 10; i++) { var an = (i / 10) * Math.PI * 2 + akn_r(c.seed | 0, i, 5) * 0.4; jzAddPath(gb, [[0, 0], [Math.cos(an) * Rm, Math.sin(an) * Rm * syy]], false); }
            jzAddStroke(gb, accC, lw);
            var BH = HD + 'var ld=time-TSL-0.16,a=1-ld/0.5,r0=' + jzN(R0) + '+ld*' + jzN(u * 0.25) + ',r1=r0+' + jzN(u * 0.05) + '*a;';
            jzAddTrimPaths(gb, BH + 'r1/' + jzN(Rm) + '*100', BH + 'r0/' + jzN(Rm) + '*100');
            jzSetExpr(jzXf(B, 'ADBE Opacity'), BH + '(ld>0&&ld<0.5)?a*K*100:0');
            jzNoGhost(B);
        }
        // mark the key word inside the typed line
        var idx = t0.indexOf(key);
        if (idx >= 0) {
            var gi = jzChars(t0.substr(0, idx)).length, gl = gi + jzChars(key).length - 1;
            if (G.g[gi] && G.g[gl]) {
                var a0 = G.g[gi].x - G.g[gi].w / 2 - ap[0] + tw / 2, kw2 = (G.g[gl].x + G.g[gl].w / 2) - (G.g[gi].x - G.g[gi].w / 2);
                var Mk = akn_bar(ctx, 'kn key mark', x0 + a0, ty + ts * 0.62, kw2, Math.max(2 * ctx.u, ts * 0.12), accC);
                jzSetExpr(jzXf(Mk, 'ADBE Scale'), HD + 'var ld=time-TSL-0.16;[ld>0?oe(ld/0.3)*K*100:0,100]');
            }
        }
        return jzUnion({ x0: x0, x1: x0 + tw, y0: ty - ts / 2, y1: ty + ts / 2, cx: W / 2, cy: ty }, kb);
    }
});

// ================================================================ pack kinetic part 2 \u2014 kinetic typography layouts (8\u201314)

/* ================================================================== 8 knRhythmCuts \u2014 \u8A9E\u306E\u30AB\u30C3\u30C8\u5272\u308A */
var AKN_SHOTS = ['huge', 'vert', 'small', 'crop', 'band', 'tilt'];
jzReg('layout', 'knRhythmCuts', {
    plan: function (rng, cut, st) {
        var sh = AKN_SHOTS.slice(), i; for (i = sh.length - 1; i > 0; i--) { var j = rng.int(0, i), t = sh[i]; sh[i] = sh[j]; sh[j] = t; }
        return { font: rng.pick(jzFontsOf(st, ['display'])), fontB: rng.pick(jzFontsOf(st, ['display', 'serif'])), shots: sh, side: rng.pick([1, -1]), fin: rng.pick(['center', 'center', 'left']) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), i;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), fontB = jzP(ctx, 'fontB', font), shots = jzP(ctx, 'shots', AKN_SHOTS), side = jzP(ctx, 'side', 1), accC = akn_accOn(sc);
        var units = akn_units(c, 5, 2), n = units.length, ts = akn_onsets(c, n, 0.5, 0.46);
        var tF = Math.min(ts[n - 1] + 0.55, c.dur - (c.outDur || 0) - 0.45); tF = Math.max(tF, ts[n - 1] + 0.28);
        var lw = Math.max(2 * ctx.u, u * 0.003);
        for (i = 0; i < n; i++) {
            var t = units[i], shot = shots[i % 6] || 'huge', t1 = i < n - 1 ? ts[i + 1] : tF, lat = jzHasLatin(t), o = { font: font, color: sc.fg, x: W / 2, y: H / 2, track: 0.02, mi: akn_mi(ctx, ts[i]) }, str = t, extra = null;
            var HD = akn_H(ctx, 'var T0=' + jzN(ts[i]) + ',dt=time-T0;');
            if (shot === 'huge') o.size = Math.min(akn_fit(ctx, t, font, W * 0.82, H * 0.6, { track: 0.02 }), H * 0.54);
            else if (shot === 'vert' && !lat) {
                str = jzVertical(t); o.x = W / 2 + side * W * (port ? 0.18 : 0.22);
                o.size = Math.min(akn_fit(ctx, jzStrip(t), font, W * 0.4, H * 0.8, { vertical: true, lead: 1.05, track: 0.02 }), W * (port ? 0.34 : 0.26)); o.leading = o.size * 1.05;
            } else if (shot === 'small' || shot === 'vert') {
                o.font = fontB; o.track = 0.12; o.size = Math.min(akn_fit(ctx, t, fontB, W * 0.4, H * 0.12, { track: 0.12 }), H * 0.1);
                var mm = akn_m(ctx, t, fontB, { track: 0.12 }), pw = mm.w * o.size / 2 + o.size * 0.8, ph = mm.h * o.size / 2 + o.size * 0.6, La = o.size * 0.6;
                extra = jzShapeLayer(ctx, 'kn cut brackets', W / 2, H / 2);
                var gb = jzGrp(extra, 'corners'), cr = [[-1, -1], [1, -1], [1, 1], [-1, 1]];
                for (var q = 0; q < 4; q++) { var a = cr[q][0], b = cr[q][1], x = a * pw, y = b * ph; jzAddPath(gb, [[x - a * La, y], [x, y], [x, y - b * La]], false); }
                jzAddStroke(gb, accC, lw);
                jzSetExpr(jzXf(extra, 'ADBE Scale'), HD + 'var s=1.3-0.3*oe(dt/0.2);[s*100,s*100]');
                jzSetExpr(jzXf(extra, 'ADBE Opacity'), HD + 'K*100');
            } else if (shot === 'crop') {
                o.track = 0; o.align = 'left'; o.size = Math.min(akn_fit(ctx, t, font, W * 1.02, H * 0.7, { track: 0 }), H * 0.66);
                o.x = W * 0.97 - akn_em(ctx, t, font, 0) * o.size; o.y = H / 2 + H * 0.04;
            } else if (shot === 'band') {
                o.track = 0.06; o.size = Math.min(akn_fit(ctx, t, font, W * 0.8, H * 0.24, { track: 0.06 }), H * 0.22);
                extra = jzRectLayer(ctx, 'kn cut band', W / 2, H / 2, W, o.size * 1.55, sc.ink);
                jzSetExpr(jzXf(extra, 'ADBE Scale'), HD + '[100,oe(dt/0.18)*100]');
                jzSetExpr(jzXf(extra, 'ADBE Opacity'), HD + 'K*100');
                o.color = akn_onCol(sc, sc.ink);
            } else { o.size = Math.min(akn_fit(ctx, t, font, W * 0.72, H * 0.4, { track: 0.02 }), H * 0.34); o.rot = -8 * side; o.color = accC; }
            if (extra) { extra.inPoint = ts[i]; extra.outPoint = Math.min(c.dur, t1); jzNoGhost(extra); }
            o.H = HD; o.sc = '(function(){var p=1+0.1*Math.exp(-dt*14);return [p,p];})()';
            var w = akn_word(ctx, str, o);
            w.L.inPoint = ts[i]; w.L.outPoint = Math.min(c.dur, t1);
        }
        // final shot: the whole line, clean
        var F = akn_flow(ctx, units, fontB, W * 0.84, H * (port ? 0.42 : 0.34), { maxSize: H * 0.16, track: 0.04 }), left = jzP(ctx, 'fin', 'center') === 'left', ox = left ? W * 0.08 + F.w / 2 : W / 2;
        var HF = akn_H(ctx, 'var dt=time-' + jzN(tF) + ';'), Gn = akn_null(ctx, 'kn cut final', ox, H / 2), Ls = [];
        jzSetExpr(jzXf(Gn, 'ADBE Scale'), HF + 'var p=1+0.06*Math.exp(-Math.max(0,dt)*14);[p*100,p*100]');
        for (i = 0; i < n; i++) {
            var wf = akn_word(ctx, units[i], { font: fontB, size: F.size, color: sc.fg, x: ox + F.pos[i].x, y: H / 2 + F.pos[i].y, track: 0.04, mi: akn_mi(ctx, tF + i * 0.03), H: HF });
            wf.N.parent = Gn; wf.L.inPoint = tF; Ls.push(wf);
        }
        var bb = akn_bbL(Ls);
        var R = akn_bar(ctx, 'kn cut rule', left ? W * 0.08 : W / 2 - F.w / 2, bb.y1 + F.size * 0.25, F.w, lw, accC);
        R.inPoint = tF;
        jzSetExpr(jzXf(R, 'ADBE Scale'), HF + '[oe(dt/0.35)*K*100,100]');
        return bb;
    }
});

/* ================================================================== 9 knPathRide \u2014 \u30EB\u30FC\u30D7\u8ECC\u9053 */
jzReg('layout', 'knPathRide', {
    plan: function (rng, cut, st) { return { font: rng.pick(jzFontsOf(st, ['display'])), side: rng.pick([1, 1, -1]), rail: rng.pick(['dash', 'line', 'dots']), acc: rng.chance(0.5) }; },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), i, font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display'));
        var t0 = jzFlat(c.text), chars = jzChars(t0), side = jzP(ctx, 'side', 1) < 0 ? -1 : 1, TAU = Math.PI * 2;
        var size = Math.min(akn_fit(ctx, t0, font, W * (port ? 0.86 : 0.72), H * 0.2, { track: 0.04 }), H * 0.17), Lt = akn_em(ctx, t0, font, 0.04) * size * 1.04;
        var R = jzClamp(Lt / TAU * 1.15, size * 1.5, Math.min(H * 0.28, W * 0.3)), yL = H / 2 + side * R * (port ? 0.5 : 0.6), xT = Math.max(W * 0.06 + R * 0.2, W / 2 - Lt / 2 - size * 0.4);
        var rail = [[-W * 0.9, yL], [xT, yL]], N = 72;
        for (i = 1; i <= N; i++) { var a = i / N * TAU; rail.push([xT + Math.sin(a) * R, yL - side * (1 - Math.cos(a)) * R]); }
        rail.push([W * 2.2, yL]);
        // text baseline path: the rail pushed along its right-hand normal, so glyph centres ride the rail
        var bl = [], off = size * 0.36;
        for (i = 0; i < rail.length; i++) {
            var p0 = rail[Math.max(0, i - 1)], p1 = rail[Math.min(rail.length - 1, i + 1)], dx = p1[0] - p0[0], dy = p1[1] - p0[1], l = Math.sqrt(dx * dx + dy * dy) || 1;
            bl.push([rail[i][0] - dy / l * off, rail[i][1] + dx / l * off]);
        }
        var sT = xT + W * 0.9, sEnd = sT + TAU * R + (W / 2 + Lt / 2 - xT), sStart = sT - (xT + W * 0.05), T = jzClamp(c.dur * 0.55, 0.7, 1.8);
        var L = jzText(ctx, t0, { font: font, size: size, color: sc.fg, x: 0, y: 0, align: 'left', track: 0.04 });
        if (jzP(ctx, 'acc', false)) { var fl = []; for (i = 0; i < chars.length; i++) fl.push(i === chars.length - 1); jzCharColors(L, akn_accOn(sc), fl, 'JZ Layout Accent'); }
        var sh = new Shape(); sh.vertices = bl; sh.closed = false;
        // the baseline loop is longer / shorter than the rail: that difference is added as the text passes the loop
        var la = 0, lb = 0; for (i = 1; i < rail.length - 1; i++) { la += Math.sqrt(Math.pow(rail[i][0] - rail[i - 1][0], 2) + Math.pow(rail[i][1] - rail[i - 1][1], 2)); lb += Math.sqrt(Math.pow(bl[i][0] - bl[i - 1][0], 2) + Math.pow(bl[i][1] - bl[i - 1][1], 2)); }
        var HD = akn_H(ctx, 'var TT=' + jzN(T) + ',S0=' + jzN(sStart) + ',S1=' + jzN(sEnd) + ',LT=' + jzN(Lt) + ',ST=' + jzN(sT) + ',LP=' + jzN(TAU * R) + ',DLP=' + jzN(lb - la) + ';');
        jzTextOnPath(L, sh, HD + 'var q=cl(time/TT),sh=lrp(S0,S1,1-Math.pow(1-q,2.6))+ic(PO)*W*1.3;sh+DLP*cl((sh-LT/2-ST)/LP)-LT', ctx);
        jzAnimate(ctx, L, { mi: 0 });
        // the rail
        var pts = []; for (i = 0; i < rail.length; i++) if (rail[i][0] > -W * 0.1 && rail[i][0] < W * 1.1) pts.push(rail[i]);
        pts.unshift([-W * 0.1, yL]); pts.push([W * 1.1, yL]);
        var lw = Math.max(1.2 * ctx.u, u * 0.0022), style = jzP(ctx, 'rail', 'dash'), S = jzShapeLayer(ctx, 'kn rail', 0, 0), g = jzGrp(S, 'rail');
        jzAddPath(g, pts, false);
        var st = jzAddStroke(g, sc.sub, style === 'dots' ? lw * 2.6 : lw), ds = st.property('ADBE Vector Stroke Dashes');
        if (style === 'dots') { try { st.property('ADBE Vector Stroke Line Cap').setValue(2); } catch (e0) {} }
        if (style !== 'line') {
            try {
                ds.addProperty('ADBE Vector Stroke Dash 1'); ds.addProperty('ADBE Vector Stroke Gap 1');
                ds = jzVecs(g).property(2).property('ADBE Vector Stroke Dashes');
                ds.property('ADBE Vector Stroke Dash 1').setValue(style === 'dots' ? 0.01 : lw * 5);
                ds.property('ADBE Vector Stroke Gap 1').setValue(style === 'dots' ? lw * 9 : lw * 4);
            } catch (e1) { jzWarn('rail dashes: ' + e1.toString()); }
        }
        jzSetExpr(jzXf(S, 'ADBE Opacity'), HD + 'oc(time/0.3)*K*(1-0.6*smo(TT,TT+0.5,time))*' + (style === 'dots' ? 70 : 85));
        S.moveToEnd(); jzNoGhost(S);
        return { x0: W / 2 - Lt / 2, x1: W / 2 + Lt / 2, y0: yL - size * 0.6, y1: yL + size * 0.6, cx: W / 2, cy: yL };
    }
});

/* ================================================================== 10 knGearWords \u2014 \u6B6F\u8ECA */
jzReg('layout', 'knGearWords', {
    plan: function (rng, cut, st) { return { font: rng.pick(jzFontsOf(st, ['display'])), dir: rng.pick([1, -1]), fillMode: rng.pick(['alt', 'alt', 'ink', 'ring']) }; },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, port = jzPortrait(ctx), i, t, font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), TAU = Math.PI * 2;
        var nmax = port ? 4 : 5, units = akn_units(c, nmax, Math.min(nmax, Math.max(2, Math.ceil(akn_gc(c.text) / 4)))), n = units.length, ts = akn_onsets(c, n, 0.45, 0.42);
        var txt = [], rs0 = [], rs = [], tk = [];
        for (i = 0; i < n; i++) {
            t = units[i]; txt.push(!jzHasLatin(t) && akn_gc(t) > 3 ? jzSplitLines(jzStrip(t), Math.ceil(akn_gc(t) / 2)) : t);
            var m = akn_m(ctx, txt[i], font, { track: 0.02, lead: 1.05 }); rs0.push(Math.sqrt(m.w * m.w + m.h * m.h) / 2 * 1.12 + 0.18);
        }
        var srt = rs0.slice().sort(function (a, b) { return a - b; }), med = srt[Math.floor(n / 2)], tot = 0, mr = 0;
        for (i = 0; i < n; i++) { rs.push(jzClamp(rs0[i], med * 0.75, med * 1.3)); tk.push(Math.min(1, rs[i] / rs0[i])); tot += rs[i] * 2; mr = Math.max(mr, rs[i]); }
        tot += 0.1 * (n - 1);
        var k = port ? Math.min(H * 0.84 / tot, W * 0.62 / (mr * 2)) : Math.min(W * 0.9 / tot, H * 0.6 / (mr * 2), H * 0.22 / 0.7), cs = [], acc = -tot / 2;
        for (i = 0; i < n; i++) { acc += rs[i]; var z = port ? (i % 2 ? 1 : -1) * mr * 0.32 : (i % 2 ? 1 : -1) * mr * 0.12; cs.push(port ? [W / 2 + z * k, H / 2 + acc * k] : [W / 2 + acc * k, H / 2 + z * k]); acc += rs[i] + 0.1; }
        var mode = jzP(ctx, 'fillMode', 'alt'), dir = jzP(ctx, 'dir', 1);
        var HD = akn_H(ctx, 'var TS=' + akn_arr(ts) + ',RS=' + akn_arr(rs) + ',DR=' + dir + ';' +
            'function ANG(I,t){var a=0;for(var j=I;j<TS.length;j++){if(t>=TS[j])a+=360*(1-ioc(cl((t-TS[j])/0.62)))*(j===I?1:RS[j]/RS[I]*0.35);}return a*(I%2?-1:1)*DR;}' +
            'function POP(I,t){return t<TS[I]?0:ob(cl((t-TS[I])/0.3),1.8);}');
        var Ls = [];
        for (i = 0; i < n; i++) {
            var cx = cs[i][0], cy = cs[i][1], r = rs[i] * k, fill = mode === 'ink' ? sc.ink : mode === 'ring' ? null : (i % 2 ? sc.accent : sc.ink), rim = fill || akn_accOn(sc);
            var S = jzShapeLayer(ctx, 'kn gear', cx, cy);
            // groups are added top first: ring, disc, teeth
            var g1 = jzGrp(S, 'ring'); jzAddEllipse(g1, r * 1.72, r * 1.72); jzAddStroke(g1, fill ? akn_onCol(sc, fill) : rim, Math.max(1, r * 0.02), 35);
            var g2 = jzGrp(S, 'disc'); jzAddEllipse(g2, r * (fill ? 1.92 : 1.86), r * (fill ? 1.92 : 1.86)); jzAddFill(g2, fill || sc.bg);
            var nt = Math.max(10, Math.round(TAU * rs[i] * 6)), th = r * 0.12, pts = [], q;
            for (q = 0; q < nt; q++) {
                var a0 = q / nt * TAU, w = TAU / nt;
                pts.push([Math.cos(a0 - w * 0.25) * r, Math.sin(a0 - w * 0.25) * r], [Math.cos(a0 - w * 0.15) * (r + th), Math.sin(a0 - w * 0.15) * (r + th)],
                    [Math.cos(a0 + w * 0.15) * (r + th), Math.sin(a0 + w * 0.15) * (r + th)], [Math.cos(a0 + w * 0.25) * r, Math.sin(a0 + w * 0.25) * r]);
            }
            var g3 = jzGrp(S, 'teeth'); jzAddPath(g3, pts, true); jzAddFill(g3, rim);
            jzSetExpr(jzXf(S, 'ADBE Rotate Z'), HD + 'ANG(' + i + ',time)');
            jzSetExpr(jzXf(S, 'ADBE Scale'), HD + 'var p=POP(' + i + ',time)*K;[p*100,p*100]');
            jzNoGhost(S);
            var fs = k * tk[i];
            var wd = akn_word(ctx, txt[i], { font: font, size: fs, leading: fs * 1.05, color: fill ? akn_onCol(sc, fill) : sc.fg, x: cx, y: cy, track: 0.02, mi: akn_mi(ctx, ts[i]), H: HD,
                sc: '(function(){var p=POP(' + i + ',time);return [p,p];})()', rotE: 'ANG(' + i + ',time)' });
            Ls.push(wd);
        }
        return akn_bbL(Ls);
    }
});

/* ================================================================== 11 knCollide \u2014 \u6B63\u9762\u885D\u7A81 */
jzReg('layout', 'knCollide', {
    plan: function (rng, cut, st) { return { font: rng.pick(jzFontsOf(st, ['display'])), acc: rng.pick(['A', 'B', 'none']), spark: rng.chance(0.8), at: rng.range(0.28, 0.36) }; },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), i, font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display'));
        var all = akn_units(c, 8, 2), lat = jzHasLatin(c.text), best = 1, bd = 1e9, tot = 0, acc = 0;
        for (i = 0; i < all.length; i++) tot += akn_gc(all[i]);
        for (i = 1; i < all.length; i++) { acc += akn_gc(all[i - 1]); var d = Math.abs(acc - tot / 2); if (d < bd) { bd = d; best = i; } }
        var A = all.slice(0, best).join(lat ? ' ' : ''), B = all.slice(best).join(lat ? ' ' : ''), tC = jzClamp(c.dur * jzP(ctx, 'at', 0.32) * 0.65, 0.25, 0.55);
        var size, pa, pb, axis, emA = akn_em(ctx, A, font, 0.02), emB = akn_em(ctx, B, font, 0.02);
        if (!port) {
            var g = lat ? 0.35 : 0.14; size = Math.min(akn_fit(ctx, A + (lat ? ' ' : '') + B, font, W * 0.86, H * 0.3, { track: 0.02 }), H * 0.24);
            var wa = emA * size, wb = emB * size, x0 = W / 2 - (wa + wb + g * size) / 2; pa = [x0 + wa / 2, H / 2]; pb = [x0 + wa + g * size + wb / 2, H / 2]; axis = 0;
        } else {
            size = Math.min(akn_fit(ctx, A, font, W * 0.86, H * 0.2, { track: 0.02 }), akn_fit(ctx, B, font, W * 0.86, H * 0.2, { track: 0.02 }), W * 0.3);
            pa = [W / 2, H / 2 - size * 0.62]; pb = [W / 2, H / 2 + size * 0.62]; axis = 1;
        }
        var D = axis ? Math.max(pa[1], H - pb[1]) + size * 0.7 : Math.max(pa[0] + emA * size / 2, W - pb[0] + emB * size / 2) + size * 0.2, accC = akn_accOn(sc);
        var HD = akn_H(ctx, 'var TC=' + jzN(tC) + ',D=' + jzN(D) + ',SZ0=' + jzN(size) + ',d=time-TC;' +
            'function OFF(t){var dd=t-TC;return dd<0?-D*(1-Math.pow(cl(t/TC),1.6)):-SZ0*0.28*Math.exp(-dd*6)*Math.sin(dd*15);}' +
            'var sqA=d<0?1:1-0.22*Math.exp(-d*14),jit=d>0?Math.exp(-d*9)*SZ0*0.05:0;');
        var ac = jzP(ctx, 'acc', 'none'), Ls = [];
        function half(t, p, sg, col) {
            var pos = '(function(){var o=OFF(time)*' + sg + ';posterizeTime(24);seedRandom(' + ((c.seed % 9973) + sg * 7) + '+Math.floor(time*24),true);return [' + jzN(p[0]) + (axis ? '' : '+o') + '+random(-1,1)*jit,' + jzN(p[1]) + (axis ? '+o' : '') + '+random(-1,1)*jit];})()';
            Ls.push(akn_word(ctx, t, { font: font, size: size, color: col, x: p[0], y: p[1], track: 0.02, mi: 0, H: HD, pos: pos, sc: axis ? '[1,sqA]' : '[sqA,1]' }));
        }
        half(A, pa, 1, ac === 'A' ? accC : sc.fg);
        half(B, pb, -1, ac === 'B' ? accC : sc.fg);
        // speed lines behind both halves while they fly
        var lw = Math.max(1.5 * ctx.u, u * 0.0028);
        for (var s = 0; s < 2; s++) {
            var sg = s ? -1 : 1, P0 = s ? pb : pa, em = s ? emB : emA, S = jzShapeLayer(ctx, 'kn collide lines', P0[0], P0[1]), gl = jzGrp(S, 'lines');
            for (var j = 0; j < 5; j++) {
                var f = (j - 2) / 2.2 * size * 0.42, len = size * (1.2 + akn_r(c.seed | 0, j, 7) * 1.6);
                if (!axis) { var xe = -sg * (em * size / 2 + size * 0.2); jzAddPath(gl, [[xe - sg * len, f * sg], [xe, f * sg]], false); }
                else { var ye = -sg * size * 0.7; jzAddPath(gl, [[f * 1.6 * sg, ye - sg * len], [f * 1.6 * sg, ye]], false); }
            }
            jzAddStroke(gl, sc.sub, lw);
            jzSetExpr(jzXf(S, 'ADBE Position'), HD + 'var o=OFF(time)*' + sg + ';[value[0]' + (axis ? '' : '+o') + ',value[1]' + (axis ? '+o' : '') + ']');
            jzSetExpr(jzXf(S, 'ADBE Opacity'), HD + 'd<0&&time>0.02?70*cl(time/TC)*K:0');
            S.outPoint = Math.min(c.dur, tC + 0.05); S.moveToEnd(); jzNoGhost(S);
        }
        // sparks at the contact point
        if (jzP(ctx, 'spark', false)) {
            var cx = axis ? W / 2 : (pa[0] + emA * size / 2 + pb[0] - emB * size / 2) / 2, Rm = size * (0.25 + 0.45 * 2.2) + size * 0.5 + 2;
            var Sp = jzShapeLayer(ctx, 'kn collide sparks', cx, H / 2), gs = jzGrp(Sp, 'sparks');
            for (i = 0; i < 12; i++) { var an = (i / 12 + akn_r(c.seed | 0, i, 9) * 0.05) * Math.PI * 2; jzAddPath(gs, [[0, 0], [Math.cos(an) * Rm, Math.sin(an) * Rm]], false); }
            jzAddStroke(gs, accC, lw * 1.4);
            var SH = HD + 'var a=1-d/0.45,r0=SZ0*(0.25+Math.max(0,d)*2.2),r1=r0+SZ0*0.5*a;';
            jzAddTrimPaths(gs, SH + 'r1/' + jzN(Rm) + '*100', SH + 'r0/' + jzN(Rm) + '*100');
            jzSetExpr(jzXf(Sp, 'ADBE Opacity'), SH + '(d>0&&d<0.45)?a*K*100:0');
            Sp.inPoint = tC; Sp.outPoint = Math.min(c.dur, tC + 0.46); jzNoGhost(Sp);
        }
        return akn_bbL(Ls);
    }
});

/* ================================================================== 12 knTumble \u2014 \u7BB1\u8EE2\u304C\u3057 */
jzReg('layout', 'knTumble', {
    plan: function (rng, cut, st) { return { font: rng.pick(jzFontsOf(st, ['display'])), box: rng.pick(['plate', 'frame', 'none']), acc: rng.int(0, 5), floor: rng.chance(0.7) }; },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), i, font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display'));
        var units = akn_units(c, 5, 2), n = units.length, F = akn_flow(ctx, units, font, W * 0.82, H * (port ? 0.4 : 0.3), { maxSize: H * 0.2, track: 0.02, sp: jzHasLatin(c.text) ? 0.5 : 0.3, lead: 1.6 });
        var size = F.size, pad = size * 0.14, h = size + pad * 2, ts = akn_onsets(c, n, 0.42, 0.36), T = 0.5, accC = akn_accOn(sc), box = jzP(ctx, 'box', 'plate'), ac = jzP(ctx, 'acc', 0) % n;
        var lw = Math.max(1.5 * ctx.u, u * 0.0025), WS = [], BS = [], XF = [], MS = [];
        for (i = 0; i < n; i++) {
            var p = F.pos[i], w = p.w + pad * 2; WS.push(w); BS.push(H / 2 + p.y + h / 2); XF.push(W / 2 + p.x - w / 2);
            MS.push(Math.min(3, Math.max(1, Math.ceil((W - XF[i]) / (2 * (w + h))))));
        }
        var HD = akn_H(ctx, 'var TS=' + akn_arr(ts) + ',WS=' + akn_arr(WS) + ',BS=' + akn_arr(BS) + ',XF=' + akn_arr(XF) + ',MS=' + akn_arr(MS) + ',HB=' + jzN(h) + ',TT=' + T + ';' +
            'function U(I,t){var w=WS[I],h=HB,m=MS[I],st=4*m,Tm=TT*(0.7+0.3*m),q=cl((t-TS[I])/Tm)*st,j=Math.min(st-1,Math.floor(q)),f=q>=st?1:q-j,xL=XF[I]+m*2*(w+h);' +
            'for(var s2=0;s2<j;s2++)xL-=s2%2?w:h;var fw=j%2?h:w,fh=j%2?w:h,ph=-90*ios(f),v=rvx(fw/2,-fh/2,ph),ld=q>=st?t-TS[I]-Tm:-1,sq=ld>=0?Math.exp(-ld*14)*0.1:0;' +
            'return [xL+v[0],BS[I]+v[1]+sq*h*0.5,-90*j+ph,1+sq*0.5,1-sq];}');
        // floor line under each row, drawn in from the right
        if (jzP(ctx, 'floor', false)) {
            for (i = 0; i < n; i++) {
                if (i > 0 && F.pos[i - 1].li === F.pos[i].li) continue;
                var r0 = i, r1 = i; while (r1 + 1 < n && F.pos[r1 + 1].li === F.pos[i].li) r1++;
                var fx0 = W / 2 + F.pos[r0].x - F.pos[r0].w / 2 - pad * 3, fx1 = W / 2 + F.pos[r1].x + F.pos[r1].w / 2 + pad * 3;
                var Fl = jzShapeLayer(ctx, 'kn tumble floor', fx1, BS[i] + lw), gf = jzGrp(Fl, 'floor'); jzAddRect(gf, fx1 - fx0, lw, 0, -(fx1 - fx0) / 2, 0); jzAddFill(gf, sc.sub);
                jzSetExpr(jzXf(Fl, 'ADBE Scale'), HD + '[oe(time/0.4)*K*100,100]'); jzXf(Fl, 'ADBE Opacity').setValue(80);
                jzNoGhost(Fl);
            }
        }
        var Ls = [];
        for (i = 0; i < n; i++) {
            var fill = i === ac ? accC : sc.ink, Ex = 'U(' + i + ',time)';
            if (box !== 'none') {
                var S = box === 'plate' ? jzRectLayer(ctx, 'kn tumble box', XF[i] + WS[i] / 2, BS[i] - h / 2, WS[i], h, fill) : jzRectLayer(ctx, 'kn tumble box', XF[i] + WS[i] / 2, BS[i] - h / 2, WS[i], h, null, { stroke: sc.fg, strokeW: lw, round: 2 });
                jzSetExpr(jzXf(S, 'ADBE Position'), HD + 'var u=' + Ex + ';[u[0],u[1]]');
                jzSetExpr(jzXf(S, 'ADBE Rotate Z'), HD + Ex + '[2]');
                jzSetExpr(jzXf(S, 'ADBE Scale'), HD + 'var u=' + Ex + ';[u[3]*100,u[4]*100]');
                jzSetExpr(jzXf(S, 'ADBE Opacity'), HD + 'time<TS[' + i + ']?0:K*100');
                jzNoGhost(S);
            }
            Ls.push(akn_word(ctx, units[i], { font: font, size: size, color: box === 'plate' ? akn_onCol(sc, fill) : (i === ac ? accC : sc.fg), x: XF[i] + WS[i] / 2, y: BS[i] - h / 2, track: 0.02, mi: akn_mi(ctx, ts[i]), H: HD,
                pos: Ex, sc: '(function(){var u=' + Ex + ';return [u[3],u[4]];})()', rotE: Ex + '[2]' }));
        }
        return akn_bbL(Ls);
    }
});

/* ================================================================== 13 knReflow \u2014 \u7E26\u304B\u3089\u6A2A\u3078 */
jzReg('layout', 'knReflow', {
    plan: function (rng, cut, st) {
        var port = cut.H > cut.W * 1.08, lat = /[A-Za-z]/.test(cut.text);
        return { font: rng.pick(jzFontsOf(st, ['serif', 'display'])), dir: !lat && port && rng.chance(0.5) ? 'h2v' : 'v2h', swirl: rng.pick([1, -1]), at: rng.range(0.36, 0.46) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, port = jzPortrait(ctx), i, font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'serif'));
        var lat = jzHasLatin(c.text), t0 = lat ? jzFlat(c.text) : jzStrip(c.text), chars = jzChars(t0), n = chars.length;
        var vCols = n <= (port ? 7 : 5) ? 1 : n <= 12 ? 2 : 3, per = Math.ceil(n / vCols), nsp = [];
        for (i = 0; i < n; i++) if (!akn_isSp(chars[i])) nsp.push(chars[i]);
        var htext = jzSplitLines(t0, lat ? (port ? 13 : 20) : (port ? 7 : 14)), vper = Math.ceil(nsp.length / vCols);
        var sv = Math.min(W * 0.5 / ((vCols - 1) * 1.3 + 1), H * 0.8 / (vper * 1.04), W * 0.3);
        var mH = akn_m(ctx, htext, font, { lead: 1.25, track: 0.04 }), sh = Math.min(W * 0.84 / mH.w, H * 0.4 / mH.h, H * 0.24);
        var L = jzText(ctx, htext, { font: font, size: sh, leading: sh * 1.25, color: sc.fg, x: W / 2, y: H / 2, track: 0.04 });
        var G = akn_geo(L, sh), ap = jzXf(L, 'ADBE Anchor Point').value, HX = [], HY = [], VX = [], VY = [], K2 = [], k = 0;
        for (i = 0; i < G.n; i++) {
            HX.push(W / 2 + G.g[i].x - ap[0]); HY.push(H / 2 + G.g[i].y - ap[1]);
            if (G.g[i].sp) { K2.push(-1); VX.push(0); VY.push(0); continue; }
            var col = Math.floor(k / vper), row = k % vper;
            VX.push(W / 2 + ((vCols - 1) / 2 - col) * sv * 1.3); VY.push(H / 2 + (row - (vper - 1) / 2) * sv * 1.04);
            K2.push(k); k++;
        }
        var h2v = jzP(ctx, 'dir', 'v2h') === 'h2v', tR = jzClamp(c.dur * jzP(ctx, 'at', 0.4), 0.5, 1.5), m = Math.max(1, k);
        var HD = akn_H(ctx, 'var HX=' + akn_arr(HX) + ',HY=' + akn_arr(HY) + ',VX=' + akn_arr(VX) + ',VY=' + akn_arr(VY) + ',KI=' + akn_arr(K2) + ',TR=' + jzN(tR) + ',SW=' + akn_swirl(ctx) + ',RV=' + jzN(sv / sh) + ',H2V=' + (h2v ? 1 : 0) + ';' +
            'var i=textIndex-1,kk=KI[i]==null?-1:KI[i],q=kk<0?1:ioc(cl((time-TR-kk*' + jzN(Math.min(0.05, 0.5 / m)) + ')/0.5));' +
            'var ax=H2V?HX[i]:VX[i],ay=H2V?HY[i]:VY[i],bx=H2V?VX[i]:HX[i],by=H2V?VY[i]:HY[i];if(kk<0){ax=bx=HX[i];ay=by=HY[i];}' +
            'var ddx=bx-ax,ddy=by-ay,kq=SW*0.35*(kk%2?1:0.6),mx=(ax+bx)/2-ddy*kq,my=(ay+by)/2+ddx*kq,x=(1-q)*(1-q)*ax+2*(1-q)*q*mx+q*q*bx,y=(1-q)*(1-q)*ay+2*(1-q)*q*my+q*q*by;\n');
        var KK = Math.max(W, H) * 1.5;
        jzAnimator(L, 'JZ Layout Reflow Move', [['ADBE Text Position 3D', [KK, KK, 0]]], HD + '[(x-HX[i])/' + jzN(KK) + '*100,(y-HY[i])/' + jzN(KK) + '*100,0]');
        jzAnimator(L, 'JZ Layout Reflow Size', [['ADBE Text Scale 3D', [400, 400, 100]]], HD + 'var z=H2V?lrp(1,RV,q):lrp(RV,1,q);(z-1)/3*100');
        jzAnimator(L, 'JZ Layout Reflow Turn', [['ADBE Text Rotation', 90]], HD + 'SW*180*bel(q)*0.25/90*100');
        jzAnimate(ctx, L, { mi: 0 });
        if (!h2v) return jzBB(L);
        var bh = vper * sv * 1.04, bw = vCols * sv * 1.3;
        return { x0: W / 2 - bw / 2, x1: W / 2 + bw / 2, y0: H / 2 - bh / 2, y1: H / 2 + bh / 2, cx: W / 2, cy: H / 2 };
    }
});
function akn_swirl(ctx) { return jzP(ctx, 'swirl', 1) < 0 ? '(-1)' : '1'; }

/* ================================================================== 14 knPadGrid \u2014 \u30D1\u30C3\u30C9 */
jzReg('layout', 'knPadGrid', {
    plan: function (rng, cut, st) { return { font: rng.pick(jzFontsOf(st, ['display'])), round: rng.chance(0.5), flash: rng.pick(['accent', 'accent', 'ink']), beat: rng.chance(0.7) }; },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), i, font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display'));
        var units = akn_units(c, 6, Math.min(6, Math.max(2, Math.ceil(akn_gc(c.text) / 4)))), n = units.length;
        var cols = port ? (n <= 3 ? 1 : 2) : (n <= 3 ? n : n === 4 ? 2 : 3), rows = Math.ceil(n / cols);
        var gw = W * (port ? 0.84 : 0.86), gh = H * (port ? 0.62 : 0.72), g = u * 0.02, cw = (gw - g * (cols - 1)) / cols, chh = Math.min((gh - g * (rows - 1)) / rows, cw * (port ? 0.8 : 0.75));
        var x0 = W / 2 - gw / 2, y0 = H / 2 - (chh * rows + g * (rows - 1)) / 2, ts = akn_onsets(c, n, 0.45, 0.34);
        var flashC = jzP(ctx, 'flash', 'accent') === 'ink' ? sc.ink : akn_accOn(sc), lw = Math.max(1.5 * ctx.u, u * 0.003), rnd = jzP(ctx, 'round', false) ? chh * 0.12 : 0;
        var sizes = [], sz = 1e9;
        for (i = 0; i < n; i++) { sizes.push(Math.min(akn_fit(ctx, units[i], font, cw * 0.8, chh * 0.62, { track: 0.02 }), chh * 0.5)); sz = Math.min(sz, sizes[i]); }
        sz *= 1.25;
        var HD = akn_H(ctx, 'var TS=' + akn_arr(ts) + ';function PF(I,t){return t<TS[I]?0:0.1+0.9*Math.exp(-(t-TS[I])*6);}');
        // pad outlines pop in one by one; flashes behind the words
        var S = jzShapeLayer(ctx, 'kn pads', 0, 0), Fz = jzShapeLayer(ctx, 'kn pad flash', 0, 0);
        for (i = 0; i < cols * rows; i++) {
            var r = Math.floor(i / cols), q = i % cols, x = x0 + q * (cw + g) + cw / 2, y = y0 + r * (chh + g) + chh / 2;
            var gp = jzGrp(S, 'pad ' + i); jzAddRect(gp, cw, chh, rnd); jzAddStroke(gp, sc.sub, lw);
            jzGX(gp).property('ADBE Vector Position').setValue([x, y]);
            jzSetExpr(jzGX(gp).property('ADBE Vector Scale'), HD + 'var e=ob(cl((time-' + jzN(i * 0.04) + ')/0.3),1.4)*K;[e*100,e*100]');
            if (i < n) {
                var gf = jzGrp(Fz, 'flash ' + i); jzAddRect(gf, cw - lw * 2, chh - lw * 2, rnd); jzAddFill(gf, flashC);
                jzGX(gf).property('ADBE Vector Position').setValue([x, y]);
                jzSetExpr(jzGX(gf).property('ADBE Vector Group Opacity'), HD + 'PF(' + i + ',time)*K*100');
            }
        }
        jzNoGhost(S); jzNoGhost(Fz);
        var Ls = [], onC = akn_onCol(sc, flashC);
        for (i = 0; i < n; i++) {
            var rr = Math.floor(i / cols), qq = i % cols, wx = x0 + qq * (cw + g) + cw / 2, wy = y0 + rr * (chh + g) + chh / 2;
            var w = akn_word(ctx, units[i], { font: font, size: Math.min(sizes[i], sz), color: sc.fg, x: wx, y: wy, track: 0.02, mi: akn_mi(ctx, ts[i]), H: HD,
                sc: '(function(){var p=1+0.12*Math.exp(-Math.max(0,time-TS[' + i + '])*12);return [p,p];})()' });
            jzAnimator(w.L, 'JZ Layout Pad Colour', [['ADBE Text Fill Color', jzHex(onC)]], HD + 'PF(' + i + ',time)>0.5?100:0');
            Ls.push(w);
        }
        return akn_bbL(Ls);
    }
});

// ================================================================ pack kinetic part 3 \u2014 word-by-word enters, exits and holds
// Every entry is one set of text animators whose Expression Selectors run the browser's per-glyph charFn:
// word index / word boxes / glyph centres are estimated at build time and embedded in the expression.

// ---- glyph geometry in the text layer's own space (textIndex order, spaces included, line breaks excluded)
function akn_isSp(ch) { return ch === ' ' || ch === '\u3000' || ch === '\t'; }
function akn_adv(ch) {
    var c = ch.charCodeAt(0);
    if (ch === ' ') return 0.3;
    if (c >= 0xFF61 && c <= 0xFF9F) return 0.5;
    if (/[iljI.,:;'!|]/.test(ch)) return 0.3;
    if (/[mwMW]/.test(ch)) return 0.85;
    return c < 0x2000 ? 0.58 : 1;
}
function akn_geo(L, size) {
    var TP = L.property('ADBE Text Properties'), td = TP.property('ADBE Text Document').value, r = jzRect(L), i, j;
    var fs = td.fontSize || size || 100, text = String(td.text), trk = (td.tracking || 0) / 1000 * fs;
    var lines = text.split(/\r\n|\r|\n|\u0003/), nL = lines.length, lead = fs * 1.2;
    try { if (!td.autoLeading && td.leading > 0) lead = td.leading; } catch (e0) {}
    var just = 'c';
    try { if (td.justification === ParagraphJustification.LEFT_JUSTIFY) just = 'l'; else if (td.justification === ParagraphJustification.RIGHT_JUSTIFY) just = 'r'; } catch (e1) {}
    var G = { fs: fs, text: text, g: [], n: 0, nk: 0, vert: nL > 1, nL: nL };
    var rows = [], maxW = 0;
    for (i = 0; i < nL; i++) {
        var cs = jzChars(lines[i]), ws = [], w = 0;
        for (j = 0; j < cs.length; j++) { var a = akn_adv(cs[j]) * fs; ws.push(a); w += a + (j < cs.length - 1 ? trk : 0); }
        if (cs.length > 1) G.vert = false;
        rows.push({ cs: cs, ws: ws, w: w }); if (w > maxW) maxW = w;
    }
    var k = maxW > 0 ? r.width / maxW : 1; if (!(k > 0.5 && k < 2)) k = 1;
    var lh = r.height - (nL - 1) * lead; if (!(lh > fs * 0.5 && lh < fs * 1.6)) lh = fs * 0.95;
    var cx = r.left + r.width / 2;
    for (i = 0; i < nL; i++) {
        var R = rows[i], x = just === 'l' ? r.left : (just === 'r' ? r.left + r.width - R.w * k : cx - R.w * k / 2), y = r.top + lh / 2 + i * lead;
        for (j = 0; j < R.cs.length; j++) {
            var sp = akn_isSp(R.cs[j]);
            G.g.push({ ch: R.cs[j], li: i, sp: sp, x: x + R.ws[j] * k / 2, y: y, w: Math.max(1, R.ws[j] * k), h: fs });
            x += (R.ws[j] + trk) * k; G.n++;
            if (!sp) G.nk++;
        }
    }
    G.cx = cx; G.cy = r.top + r.height / 2;
    return G;
}
// word of every glyph: the cut's word chunks (else chunks of the layer's own text), boxes per word
function akn_words(cut, G) {
    var cw = (cut && cut.words) || [], ws = [], i, j, s;
    for (i = 0; i < cw.length; i++) { s = jzStrip(cw[i]); if (s) ws.push(s); }
    var flat = jzStrip(G.text), off = ws.length ? ws.join('').indexOf(flat) : -1;
    if (off < 0) {
        var t = G.text.replace(/[\r\n]+/g, jzHasLatin(G.text) ? ' ' : ''), src = jzHasLatin(t) ? t.split(/\s+/) : jzChunk(t);
        ws = []; for (i = 0; i < src.length; i++) { s = jzStrip(src[i]); if (s) ws.push(s); }
        off = 0;
    }
    var bnd = [], acc = 0;
    for (i = 0; i < ws.length; i++) { acc += jzChars(ws[i]).length; bnd.push(acc); }
    var map = [], k = 0, jmin = 1e9, jmax = -1;
    for (i = 0; i < G.n; i++) {
        if (G.g[i].sp) { map.push(-1); continue; }
        var pos = off + k; j = 0;
        while (j < bnd.length && pos >= bnd[j]) j++;
        if (j >= bnd.length) j = Math.max(0, bnd.length - 1);
        map.push(j); if (j < jmin) jmin = j; if (j > jmax) jmax = j; k++;
    }
    if (jmax < 0) { jmin = 0; jmax = 0; }
    var nW = jmax - jmin + 1, W = [];
    for (j = 0; j < nW; j++) W.push({ x0: 1e9, x1: -1e9, y0: 1e9, y1: -1e9, n: 0, li: 0 });
    var inW = [];
    for (i = 0; i < G.n; i++) {
        if (map[i] < 0) { inW.push(0); continue; }
        map[i] -= jmin;
        var b = W[map[i]], g = G.g[i];
        if (!b.n) b.li = g.li;
        inW.push(b.n); b.n++;
        b.x0 = Math.min(b.x0, g.x - g.w / 2); b.x1 = Math.max(b.x1, g.x + g.w / 2); b.y0 = Math.min(b.y0, g.y - g.h / 2); b.y1 = Math.max(b.y1, g.y + g.h / 2);
    }
    var X0 = 1e9, X1 = -1e9, Y0 = 1e9, Y1 = -1e9;
    for (j = 0; j < nW; j++) { b = W[j]; if (!b.n) { b.x0 = b.x1 = G.cx; b.y0 = b.y1 = G.cy; continue; } X0 = Math.min(X0, b.x0); X1 = Math.max(X1, b.x1); Y0 = Math.min(Y0, b.y0); Y1 = Math.max(Y1, b.y1); }
    if (X0 > X1) { X0 = X1 = G.cx; Y0 = Y1 = G.cy; }
    return { map: map, inW: inW, nW: nW, W: W, box: { x0: X0, x1: X1, y0: Y0, y1: Y1 } };
}
function akn_arr(a) { var s = [], i; for (i = 0; i < a.length; i++) s.push(jzN(a[i])); return '[' + s.join(',') + ']'; }
// expression data: per glyph WI (word), GX GY (centre), NI (index in word); per word X0 X1 Y0 Y1 BN (glyphs) WL (line); NW; box BX0..BY1
function akn_gdata(G, Wd) {
    var gx = [], gy = [], i, x0 = [], x1 = [], y0 = [], y1 = [], bn = [], wl = [];
    for (i = 0; i < G.n; i++) { gx.push(G.g[i].x); gy.push(G.g[i].y); }
    for (i = 0; i < Wd.nW; i++) { var b = Wd.W[i]; x0.push(b.x0); x1.push(b.x1); y0.push(b.y0); y1.push(b.y1); bn.push(b.n); wl.push(b.li); }
    return 'var WI=' + akn_arr(Wd.map) + ',GX=' + akn_arr(gx) + ',GY=' + akn_arr(gy) + ',NI=' + akn_arr(Wd.inW) + ',X0=' + akn_arr(x0) + ',X1=' + akn_arr(x1) +
        ',Y0=' + akn_arr(y0) + ',Y1=' + akn_arr(y1) + ',BN=' + akn_arr(bn) + ',WL=' + akn_arr(wl) + ',NW=' + Wd.nW + ',BX0=' + jzN(Wd.box.x0) + ',BX1=' + jzN(Wd.box.x1) +
        ',BY0=' + jzN(Wd.box.y0) + ',BY1=' + jzN(Wd.box.y1) + ',GV=' + (G.vert ? 1 : 0) + ',FS=' + jzN(G.fs) + ';\n';
}
// the browser's numeric hash J.h (seeded directions match the web version)
function akn_h(a, b, c, d, e) {
    var h = 0x9e3779b9 ^ (a | 0);
    h = jzImul(h ^ (h >>> 16), 0x85ebca6b);
    h = (h + jzImul((b | 0) + 0x632be5ab, 0xc2b2ae35)) | 0;
    h = jzImul(h ^ (h >>> 13), 0xc2b2ae35);
    h = (h + jzImul((c | 0) + 0x5bd1e995, 0x27d4eb2f)) | 0;
    h = jzImul(h ^ (h >>> 15), 0x165667b1);
    h = (h + jzImul((d | 0) + 0x1b873593, 0x85ebca6b)) | 0;
    h = jzImul(h ^ (h >>> 16), 0x27d4eb2f);
    h = (h + jzImul((e | 0) + 0x68e31da4, 0x9e3779b1)) | 0;
    h ^= h >>> 15; h = jzImul(h, 0x2c1b3c6d); h ^= h >>> 12; h = jzImul(h, 0x297a2d39); h ^= h >>> 15;
    return h >>> 0;
}
function akn_r(a, b, c, d, e) { return akn_h(a, b, c, d, e) / 4294967296; }
function akn_rs(a, b, c, d, e) { return akn_r(a, b, c, d, e) * 2 - 1; }

// easing / helpers for expressions (names distinct from JZ_FNS and the motion header)
var AKN_FNS = 'function ioc(x){x=cl(x);return x<0.5?4*x*x*x:1-Math.pow(-2*x+2,3)/2;}function bel(x){return Math.sin(Math.PI*cl(x));}' +
    'function sprg(t,k,f){return t<=0?1:Math.exp(-k*t)*Math.cos(f*t);}function kck(t,k,f){return t<=0?0:Math.exp(-k*t)*Math.sin(f*t);}' +
    'function smo(a,b,x){x=cl((x-a)/(b-a));return x*x*(3-2*x);}function stg(p,k,n,s){return cl((p-(n>1?k/(n-1):0)*s)/(1-s));}' +
    'function oel(x){if(x<=0)return 0;if(x>=1)return 1;return Math.pow(2,-10*x)*Math.sin((x*10-0.75)*2.0944)+1;}function ios(x){x=cl(x);return -(Math.cos(Math.PI*x)-1)/2;}' +
    'function lrp(a,b,t){return a+(b-a)*t;}function rvx(x,y,a){var c=Math.cos(a*0.0174533),s=Math.sin(a*0.0174533);return [x*c-y*s,x*s+y*c];}\n';

// the browser's charFn as animators. `code` runs inside F() per glyph and may set dx dy s sx sy r a hd (hide) cc (accent colour 0..1) sk (skew deg);
// variables i (0-based glyph), j (word), gx gy (glyph centre), bcx bcy (word centre) are ready. use: { p, s, r, a, col, sk } + K (px range) / S (scale range).
function akn_fn(m, tag, pre, code, use, o) {
    o = o || {};
    var G = akn_geo(m.L, m.size), Wd = akn_words(m.c, G);
    var K = o.K || Math.max(m.W, m.H) * 2, S = o.S || 7, RR = o.R || 360;
    var head = m.HD + AKN_FNS + akn_gdata(G, Wd) + 'var W=' + jzN(m.W) + ',H=' + jzN(m.H) + ',NG=' + G.n + ';' + (pre || '') + '\n' +
        'var i=textIndex-1,j=i<NG?WI[i]:-1,gx=GX[i]||0,gy=GY[i]||0,bcx=j>=0?(X0[j]+X1[j])/2:0,bcy=j>=0?(Y0[j]+Y1[j])/2:0;' +
        'var dx=0,dy=0,s=1,sx=1,sy=1,r=0,a=1,hd=0,cc=0,sk=0;function F(){' + code + '}F();\n';
    var nm = 'JZ ' + tag + ' ';
    if (use.p) jzAnimator(m.L, nm + 'Move', [['ADBE Text Position 3D', [K, K, 0]]], head + '[dx/' + jzN(K) + '*100,dy/' + jzN(K) + '*100,0]');
    if (use.s) jzAnimator(m.L, nm + 'Scale', [['ADBE Text Scale 3D', [S * 100, S * 100, 100]]], head + 'var u=' + jzN(S - 1) + ';[(Math.max(0,s*sx)-1)/u*100,(Math.max(0,s*sy)-1)/u*100,0]');
    if (use.r) jzAnimator(m.L, nm + 'Turn', [['ADBE Text Rotation', RR]], head + 'r/' + jzN(RR) + '*100');
    if (use.sk) jzAnimator(m.L, nm + 'Skew', [['ADBE Text Skew', 60]], head + 'Math.max(-100,Math.min(100,sk/60*100))');
    if (use.col) jzAnimator(m.L, nm + 'Colour', [['ADBE Text Fill Color', jzHex(use.col)]], head + 'cc*100');
    if (use.a) jzAnimator(m.L, nm + 'Fade', [['ADBE Text Opacity', 0]], head + 'hd?100:(1-cl(a))*100');
    return { G: G, Wd: Wd };
}
function akn_dir(m, salt) { return (akn_h((m.c.seed | 0), (m.o.mi | 0), salt) & 1) ? 1 : -1; }

/* ================================================================ ENTRANCES */
jzReg('enter', 'knWordSlam', { selfHide: true, apply: function (m) {
    // words slam down from a huge scale one after another; landed words take a knock at each impact
    akn_fn(m, 'In Slam', 'var sp=NW>1?0.62:0,secs=IN*(1-sp);', 'if(j<0)return;var q=stg(P,j,NW,sp);if(q<=0){hd=1;return;}' +
        'var e=oe(q*1.35);s=1+2.1*(1-e);var kn=0;for(var j2=NW-1;j2>j;j2--){var q2=stg(P,j2,NW,sp);if(q2>=0.74){kn=SZ*0.09*kck((q2-0.74)*secs,11,30);break;}}kn*=cl((1-P)*10);' +
        'dx=(gx-bcx)*(s-1);dy=(gy-bcy)*(s-1)+kn;a=cl(q*7);r=(1-e)*(j%2?7:-7);', { p: 1, s: 1, r: 1, a: 1 });
} });
jzReg('enter', 'knTypeToSlam', { selfHide: true, apply: function (m) {
    // each word is typed small at its start, then snaps up to full size
    akn_fn(m, 'In TypeSlam', 'var sp=NW>1?0.55:0,tq=0.55;', 'if(j<0)return;var q=stg(P,j,NW,sp);if(q<=0){hd=1;return;}' +
        'if(q<tq&&NI[i]>=Math.floor(q/tq*BN[j]+1e-6)+1){hd=1;return;}var e=q<tq?0:ob(cl((q-tq)/(1-tq)),2.2);s=lrp(0.42,1,e);' +
        'var ax=GV?bcx:X0[j],ay=GV?Y0[j]:bcy;dx=(gx-ax)*(s-1);dy=(gy-ay)*(s-1);a=q<tq?0.85:1;', { p: 1, s: 1, a: 1 });
} });
jzReg('enter', 'knReplaceIn', { selfHide: true, apply: function (m) {
    // the words flash big in the middle one by one, replacing each other, then all fly to their places
    akn_fn(m, 'In Replace', 'var T=NW>1?0.7:0.2,Bcx=(BX0+BX1)/2,Bcy=(BY0+BY1)/2,bw=Math.max(0.001,GV?BY1-BY0:BX1-BX0),fly=oe(cl((P-T)/(1-T)));',
        'if(j<0)return;var ww=Math.max(0.001,GV?Y1[j]-Y0[j]:X1[j]-X0[j]),big=Math.min(2.4,Math.max(1,bw/ww*0.8)),w0=j/NW*T,w1=(j+1)/NW*T,cx,cy;' +
        'if(P<T){if(P<w0||P>=w1){hd=1;return;}var q=(P-w0)/(w1-w0);s=big*(1+0.35*Math.exp(-q*9));cx=Bcx;cy=Bcy;a=cl(q*8);}' +
        'else{var last=j===NW-1;s=lrp(last?big:0.3,1,fly);cx=lrp(Bcx,bcx,fly);cy=lrp(Bcy,bcy,fly);a=last?1:cl(fly*3);}' +
        'dx=cx+(gx-bcx)*s-gx;dy=cy+(gy-bcy)*s-gy;', { p: 1, s: 1, a: 1 });
} });
jzReg('enter', 'knHingeDrop', { selfHide: true, apply: function (m) {
    // each word swings down from upright on a hinge at its bottom corner and bounces level
    akn_fn(m, 'In Hinge', 'var sp=NW>1?0.5:0,dr=' + akn_dir(m, 61) + ';', 'if(j<0)return;var q=stg(P,j,NW,sp);if(q<=0){hd=1;return;}' +
        'var th=-88*dr*(1-bo(q)),px=dr>0?X0[j]:X1[j],py=Y1[j],v=rvx(gx-px,gy-py,th);dx=px+v[0]-gx;dy=py+v[1]-gy;r=th;a=cl(q*6);', { p: 1, r: 1, a: 1 });
} });
jzReg('enter', 'knLoopIn', { selfHide: true, apply: function (m) {
    // every glyph rides the same looping track into its place, one after another like a train
    var D = Math.min(m.W, m.H) * 0.55;
    akn_fn(m, 'In Loop', 'var dr=' + akn_dir(m, 63) + ',D=' + jzN(D) + ',R=' + jzN(D / (Math.PI * 2) * 1.5) + ',n=textTotal;',
        'var q=stg(P,dr>0?i:n-1-i,n,0.5);if(q<=0){hd=1;return;}var v=1-oc(q),ph=6.28319*v,a0=v*D-R*Math.sin(ph),b0=-R*(1-Math.cos(ph)),' +
        'ta=D-6.28319*R*Math.cos(ph),tb=-6.28319*R*Math.sin(ph);r=Math.atan2(tb,ta*dr)*57.2958*Math.min(1,v*4);a=cl(q*6);' +
        'if(GV){dx=b0;dy=-a0*dr;}else{dx=a0*dr;dy=b0;}', { p: 1, r: 1, a: 1 });
} });
jzReg('enter', 'knPushIn', { selfHide: true, apply: function (m) {
    // words arrive at the end of the line one by one and push the ones before them into place
    akn_fn(m, 'In Push', 'var f=P*NW,k0=Math.min(NW-1,Math.floor(f)),e=ob(cl((f-k0)/0.75),1.5);' +
        'function lo(q){return GV?Y0[q]:X0[q];}function hi(q){return GV?Y1[q]:X1[q];}',
        'if(j<0)return;if(j>k0){hd=1;return;}var li=WL[j],end=-1e9,last=-1,prev=-1,q;for(q=0;q<NW;q++)if(WL[q]===li){end=hi(q);if(q<=k0){prev=last;last=q;}}' +
        'var v=end-hi(last);if(last===k0)v+=prev>=0?((end-hi(prev))-(end-hi(last)))*(1-e):(end-lo(last))*0.6*(1-e);' +
        'a=j===k0?cl(e*3):1;if(GV)dy=v;else dx=v;', { p: 1, a: 1 });
} });
jzReg('enter', 'knInertia', { apply: function (m) {
    // the line brakes into place: the front stops first, the rest bunch up behind it and spring apart
    var dr = akn_dir(m, 67);
    akn_fn(m, 'In Brake', 'var dr=' + dr + ',D=' + jzN((0) + m.W * 0.6) + ',DV=' + jzN(m.H * 0.6) + ',n=textTotal;',
        'var bk=n>1?(dr>0?(n-1-i)/(n-1):i/(n-1)):0,q=cl((P-bk*0.3)/0.7),e=ob(q,2.1),v=(ob(Math.min(1,q+0.02),2.1)-e)/0.02,' +
        'st=1+Math.min(0.5,Math.abs(v)*0.08),sq=q>0.55?1-0.18*bk*bel((q-0.55)/0.45):1;' +
        'if(GV){dy=-dr*DV*(1-e);sy=st*sq;sx=1/st;}else{dx=-dr*D*(1-e);sx=st*sq;sy=1/st;}', { p: 1, s: 1 });
} });
jzReg('enter', 'knWordSpin', { selfHide: true, apply: function (m) {
    // each word spins in as one rigid piece, neighbours turning the opposite way
    akn_fn(m, 'In Spin', 'var sp=NW>1?0.5:0;', 'if(j<0)return;var q=stg(P,j,NW,sp);if(q<=0){hd=1;return;}' +
        'var e=ob(q,1.6),th=(1-oc(q))*200*(j%2?-1:1);s=lrp(0.15,1,e);var v=rvx((gx-bcx)*s,(gy-bcy)*s,th);dx=bcx+v[0]-gx;dy=bcy+v[1]-gy;r=th;a=cl(q*5);', { p: 1, s: 1, r: 1, a: 1 });
} });
jzReg('enter', 'knDiveIn', { selfHide: true, apply: function (m) {
    // words fly in from behind the camera one after another, huge and soft, landing sharp
    akn_fn(m, 'In Dive', 'var sp=NW>1?0.55:0,Bcx=(BX0+BX1)/2;', 'if(j<0)return;var q=stg(P,j,NW,sp);if(q<=0){hd=1;return;}' +
        'var e=oc(q);s=lrp(5.5,1,e);dx=(gx-bcx)*(s-1)+(bcx-Bcx)*(s-1)*0.6;dy=(gy-bcy)*(s-1);a=cl(q*2.2)*lrp(0.35,1,e);', { p: 1, s: 1, a: 1 });
} });
jzReg('enter', 'knStretchOut', { selfHide: true, apply: function (m) {
    // each word shoots out from its first letter like a tape measure and snaps back to length
    akn_fn(m, 'In Stretch', 'var sp=NW>1?0.55:0;', 'if(j<0)return;var q=stg(P,j,NW,sp);if(q<=0){hd=1;return;}' +
        'var k=q>=1?1:Math.max(0.02,oel(q));a=cl(q*8);if(GV){dy=(gy-Y0[j])*(k-1);sy=k;}else{dx=(gx-X0[j])*(k-1);sx=k;}', { p: 1, s: 1, a: 1 });
} });

/* ================================================================ EXITS */
jzReg('exit', 'knWordKick', { apply: function (m) {
    // the words are kicked out one after another, up and down in turn, tumbling
    akn_fn(m, 'Out Kick', 'var sp=NW>1?0.4:0;', 'if(j<0)return;var q=stg(PO,j,NW,sp);if(q<=0)return;if(q>=1){hd=1;return;}' +
        'var up=j%2?1:-1,e=ic(q),th=up*70*e,v=rvx(gx-bcx,gy-bcy,th);dx=bcx+v[0]-gx+up*SZ*0.6*e;dy=bcy+v[1]-gy+up*H*0.9*e-up*SZ*0.25*bel(q*2);r=th;' +
        'a=1-iq(cl((q-0.45)/0.55));', { p: 1, r: 1, a: 1 });
} });
jzReg('exit', 'knPushOut', { apply: function (m) {
    // the line is shunted along in word-sized steps; each word fades as it passes the line's start
    akn_fn(m, 'Out Push', 'function lo(q){return GV?Y0[q]:X0[q];}function hi(q){return GV?Y1[q]:X1[q];}',
        'if(j<0)return;var li=WL[j],js=[],q;for(q=0;q<NW;q++)if(WL[q]===li)js.push(q);var mm=js.length,s0=lo(js[0]),f=PO*mm,k0=Math.min(mm-1,Math.floor(f)),e=ob(cl((f-k0)/0.8),1.6);' +
        'function to(z){return z>=mm?hi(js[mm-1])-s0+0.2*FS:lo(js[z])-s0;}var sh=lrp(to(k0),to(k0+1),e),ov=s0-((GV?gy:gx)-sh);' +
        'a=(1-cl(ov/(0.45*FS)))*(1-smo(0.72,1,PO))*(PO>0.97?1-cl((PO-0.97)/0.03):1);if(a<=0.01){hd=1;return;}if(GV)dy=-sh;else dx=-sh;', { p: 1, a: 1 });
} });
jzReg('exit', 'knDiveGlyph', { apply: function (m) {
    // the camera dives into one letter: the line blows up around it and the rest flies past
    var G = akn_geo(m.L, m.size), fi = -1, i, nk = 0, ks = [];
    for (i = 0; i < G.n; i++) if (!G.g[i].sp) { ks.push(i); if (fi < 0 && jzIsKanji(G.g[i].ch)) fi = i; }
    if (!ks.length) return;
    if (fi < 0) fi = ks[Math.floor(ks.length / 2)];
    var one = ks.length <= 1, f = G.g[fi];
    if (one) { m.parts.op.push('f*=1-(function(x){x=cl((x-0.3)/0.7);return x*x*(3-2*x);})(PO);'); return; }
    // the chosen letter drifts to the middle of the frame (static transform chain, unrotated layers only)
    var L = m.L, tr = L.property('ADBE Transform Group'), rot = tr.property('ADBE Rotate Z').value, ap = tr.property('ADBE Anchor Point').value, sc = tr.property('ADBE Scale').value;
    var px = tr.property('ADBE Position').value[0] + (f.x - ap[0]) * sc[0] / 100, py = tr.property('ADBE Position').value[1] + (f.y - ap[1]) * sc[1] / 100, P0 = L.parent, g = 0, ok = !rot;
    while (P0 && g++ < 6) {
        var t2 = P0.property('ADBE Transform Group'), a2 = t2.property('ADBE Anchor Point').value, s2 = t2.property('ADBE Scale').value;
        if (t2.property('ADBE Rotate Z').value) ok = false;
        px = t2.property('ADBE Position').value[0] + (px - a2[0]) * s2[0] / 100; py = t2.property('ADBE Position').value[1] + (py - a2[1]) * s2[1] / 100;
        P0 = P0.parent;
    }
    if (ok) m.parts.pos.push('var de=(function(x){x=cl(x);return x<0.5?4*x*x*x:1-Math.pow(-2*x+2,3)/2;})(PO);d=[d[0]+' + jzN(m.W / 2 - px) + '*de,d[1]+' + jzN(m.H / 2 - py) + '*de];');
    akn_fn(m, 'Out DiveGlyph', 'var FI=' + fi + ',SS=Math.exp(Math.pow(PO,1.7)*Math.log(34));',
        'if(GX[i]==null)return;var fx=GX[FI],fy=GY[FI];a=i===FI?1-smo(0.6,1,PO):1-smo(0.25,0.7,PO);if(a<=0.01){hd=1;return;}s=SS;dx=(gx-fx)*(s-1);dy=(gy-fy)*(s-1);',
        { p: 1, s: 1, a: 1 }, { S: 36, K: Math.max(m.W, m.H) * 40 });
} });
jzReg('exit', 'knLaunch', { apply: function (m) {
    // the line pulls away: the front glyph goes first, the rest follow on a stretching chain
    akn_fn(m, 'Out Launch', 'var dr=' + akn_dir(m, 71) + ',D=' + jzN(m.W * 1.25) + ',DV=' + jzN(m.H * 1.25) + ',n=textTotal;',
        'var bk=n>1?(dr>0?(n-1-i)/(n-1):i/(n-1)):0,q=cl((PO-bk*0.35)/0.65),an=-0.06*bel(cl(PO/0.18)),e=ic(q)+an,st=1+Math.min(1.2,3*q*q*0.35);' +
        'if(q>=1){hd=1;return;}if(GV){dy=dr*DV*e;sy=st;sx=1/Math.sqrt(st);}else{dx=dr*D*e;sx=st;sy=1/Math.sqrt(st);}', { p: 1, s: 1, a: 1 });
} });
jzReg('exit', 'knWordBlink', { apply: function (m) {
    // one word at a time: a punch in the accent colour, then gone
    var rev = (akn_h(m.c.seed | 0, 73) & 1) === 1;
    akn_fn(m, 'Out Blink', 'var RV=' + (rev ? 1 : 0) + ',R1=' + jzN(akn_r(m.c.seed | 0, m.o.mi | 0, 74) * 0.6) + ';',
        'if(j<0)return;var jj=RV?NW-1-j:j,w0=NW>1?jj/NW:R1,w1=NW>1?(jj+1)/NW:R1+0.4;if(PO>=w1||PO>=0.999){hd=1;return;}if(PO<w0)return;' +
        'var q=(PO-w0)/(w1-w0);s=1+0.16*bel(q*1.4);dx=(gx-bcx)*(s-1);dy=(gy-bcy)*(s-1);cc=q>0.25?1:0;a=q>0.7?0.35:1;', { p: 1, s: 1, a: 1, col: m.ctx.sc.accent });
} });
jzReg('exit', 'knCloseGap', { apply: function (m) {
    // words drop out one by one and the rest slide together, re-centring, until the last one pops
    akn_fn(m, 'Out Close', 'function lo(q){return GV?Y0[q]:X0[q];}function hi(q){return GV?Y1[q]:X1[q];}function rk(q){var a2=0,b2=NW-1,r2=0;while(a2<=b2){if(a2===q)return r2;r2++;if(b2!==a2){if(b2===q)return r2;r2++;}a2++;b2--;}return 0;}' +
        'function cc2(q){return ioc(PO*NW-rk(q));}',
        'if(j<0)return;var cj=cc2(j);if(cj>=0.999){hd=1;return;}var li=WL[j],js=[],q;for(q=0;q<NW;q++)if(WL[q]===li)js.push(q);var mm=js.length,tot=0,z,ctr=0;' +
        'function cz(z){return z<mm-1?(lo(js[z+1])-hi(js[z]))*(1-Math.max(cc2(js[z]),cc2(js[z+1]))):0;}' +
        'for(z=0;z<mm;z++)tot+=(hi(js[z])-lo(js[z]))*(1-cc2(js[z]))+cz(z);var x=(lo(js[0])+hi(js[mm-1]))/2-tot/2;' +
        'for(z=0;z<mm;z++){var w2=(hi(js[z])-lo(js[z]))*(1-cc2(js[z]));if(js[z]===j)ctr=x+w2/2;x+=w2+cz(z);}' +
        's=Math.max(0.01,1-cj);var wc=(lo(j)+hi(j))/2,al=GV?gy:gx,cr=GV?gx:gy,cm=GV?bcx:bcy,na=ctr+(al-wc)*s,nc=cm+(cr-cm)*s;' +
        'if(GV){dx=nc-gx;dy=na-gy;}else{dx=na-gx;dy=nc-gy;}', { p: 1, s: 1, a: 1 });
} });
jzReg('exit', 'knJumpCutOut', { apply: function (m) {
    // three hard jump cuts (closer, wider, tilted) and the line is gone
    var side = (akn_h(m.c.seed | 0, m.o.mi | 0, 79) & 1) ? 1 : -1;
    var T = 'var jst=Math.floor(cl(PO)*4),JST=[[1.22,' + jzN(0.05 * side) + ',-0.02,0],[0.8,' + jzN(-0.07 * side) + ',0.03,0],[1.5,' + jzN(0.02 * side) + ',0,' + (3 * side) + '],[1,0,0,0]],JSJ=PO>0?JST[Math.min(3,jst)]:[1,0,0,0];';
    m.parts.sc.push(T + 'f=[f[0]*JSJ[0],f[1]*JSJ[0]];');
    m.parts.pos.push(T + 'd=[d[0]+JSJ[1]*thisComp.width,d[1]+JSJ[2]*thisComp.height];');
    m.parts.rot.push(T + 'r+=JSJ[3];');
    m.parts.op.push(T + 'f*=PO>0&&jst>=3?0:1;');
} });
jzReg('exit', 'knStackAway', { apply: function (m) {
    // the words hop into a tower one by one, then the whole tower drops out of frame
    akn_fn(m, 'Out Stack', 'var T=0.62,Bcx=(BX0+BX1)/2,Bcy=(BY0+BY1)/2,lh=BY1-BY0;',
        'if(j<0)return;var q=ioc(cl((PO/T-j/Math.max(1,NW)*0.7)/0.3)),h=GV?X1[j]-X0[j]:Math.max(0.001,Y1[j]-Y0[j]);' +
        'var tx=GV?Bcx-(j-(NW-1)/2)*h*1.05:Bcx,ty=GV?Bcy:Bcy-(j-(NW-1)/2)*h*1.05,hop=-bel(q)*(GV?h:lh)*0.8;' +
        'dx=(tx-bcx)*q;dy=(ty-bcy)*q+hop;var fa=cl((PO-T)/(1-T));dy+=iq(fa)*H*1.2;r=fa*(j%2?8:-8);if(fa>=1){hd=1;return;}a=1-smo(0.8,1,fa);', { p: 1, r: 1, a: 1 });
} });

/* ================================================================ HOLDS (no beat grid inside the content comp: fixed periods) */
jzReg('hold', 'knWordPulse', { apply: function (m) {
    // one word at a time swells on the beat, cycling through the line
    akn_fn(m, 'Hold Pulse', 'var PER=0.52,s0=((time%PER)+PER)%PER,cur=((Math.floor(time/PER)%NW)+NW)%NW,pu=Math.exp(-s0*4)*0.14*AMT*(0.4+M);',
        'if(j!==cur||pu<0.002)return;s=1+pu;dx=(gx-bcx)*(s-1);dy=(gy-bcy)*(s-1);', { p: 1, s: 1 });
} });
jzReg('hold', 'knCounterRock', { apply: function (m) {
    // neighbouring words rock the opposite way, like meshed gears
    akn_fn(m, 'Hold Rock', 'var A=4*AMT*(0.4+M)*Math.sin(time*6.28319*0.55);',
        'if(j<0)return;var th=A*(j%2?-1:1)*(NW===1?0.6:1),v=rvx(gx-bcx,gy-bcy,th);dx=bcx+v[0]-gx;dy=bcy+v[1]-gy;r=th;', { p: 1, r: 1 });
} });
jzReg('hold', 'knWordRide', { apply: function (m) {
    // a slow swell travels along the line; each word rides it as one piece, tilting with the slope
    akn_fn(m, 'Hold Ride', 'var A=SZ*0.07*AMT*(0.4+M);',
        'if(j<0)return;var ph=time*3.1-j*1.25,off=A*Math.sin(ph),th=Math.cos(ph)*3.5*AMT,v=rvx(gx-bcx,gy-bcy,th);dx=bcx+v[0]-gx;dy=bcy+v[1]-gy;r=th;if(GV)dx+=off;else dy+=off;', { p: 1, r: 1 });
} });
jzReg('hold', 'knTickShift', { apply: function (m) {
    // the line ticks sideways on every beat like a second hand: snap, tiny overshoot, hold
    var G = akn_geo(m.L, m.size);
    m.parts.pos.push('var PER=0.5,ts0=((time%PER)+PER)%PER,tix=Math.floor(time/PER),tdd=SZ*0.05*AMT*(0.4+M),tfr=tix%2?1:-1,tx=(-tfr+2*tfr*ob(cl(ts0/0.09),2.6))*tdd;' +
        (G.vert ? 'd=[d[0],d[1]+tx];' : 'd=[d[0]+tx,d[1]];'));
} });
jzReg('hold', 'knBeatLean', { apply: function (m) {
    // on each beat the words lean over, alternately forward and back, and spring upright
    akn_fn(m, 'Hold Lean', 'var PER=0.55,s0=((time%PER)+PER)%PER,ix=Math.floor(time/PER),A=18*AMT*(0.4+M)*Math.exp(-s0*4.5)*Math.cos(s0*15);',
        'if(j<0||Math.abs(A)<0.1)return;sk=A*((j+ix)%2?1:-1);', { sk: 1 });
} });
jzReg('hold', 'knGapBreath', { apply: function (m) {
    // the spaces between words breathe in and out; the words themselves keep still
    akn_fn(m, 'Hold Gap', 'var A=SZ*0.16*AMT*(0.4+M)*(0.5-0.5*Math.cos(time*6.28319*0.4));',
        'if(j<0||NW<2)return;var o=(j-(NW-1)/2)*A;if(GV)dy=o;else dx=o;', { p: 1 });
} });

// ================================================================ pack kinetic part 4 \u2014 word-timed cameras, transitions, treatments and decor

// word clock of a cut (same rule as the browser: onsets spread over the first half; no beat grid in AE)
function akn_wordTimes(cut, nMax) {
    var n = Math.max(1, Math.min(nMax || 6, (cut.words || []).length || 1)), dur = cut.dur || 2, i, t = [];
    if (n < 2 && dur > 1.2) n = 2;
    var last = Math.max(0, Math.min(dur * 0.5, (n - 1) * 0.38));
    for (i = 0; i < n; i++) t.push(n > 1 ? last * i / (n - 1) : 0);
    return t;
}
var AKN_CI = 'function ci(t){var k=-1;for(var q=0;q<TS.length;q++)if(t>=TS[q])k=q;return k;}';

/* ================================================================ CAMERA */
// browser design px -> comp px (blur radii)
function akn_px(cam) { var W = cam.W || 1920, H = cam.H || 1080, r = W / H; return Math.min(W, H) / ((Math.abs(r - 1) < 0.02 || Math.abs(r - 0.8) < 0.02) ? 1440 : 1080); }
// CAM(t) -> [x, y, sx, sy, rot, skx, blur]; snippets see t, KM, K (= min(1, KM)), DUR, W, H, TS (word onsets), NT, ci(t) + easings
function akn_camfn(cam, o) {
    return jzCamHead(cam) + AKN_FNS + 'var W=' + jzN(cam.W || 1920) + ',H=' + jzN(cam.H || 1080) + ',TS=' + akn_arr(akn_wordTimes(cam.cut, 6)) + ',NT=TS.length;' + AKN_CI + '\n' +
        'function CAM(t){t=Math.max(0,t);var K=Math.min(1,KM),x=0,y=0,s=1,sx=1,sy=1,rot=0,skx=0,blur=0;\n' + o.code + '\nreturn [x,y,s*sx,s*sy,rot,skx,blur];}\n';
}
// null gets x / y / scale / rotation; skew (Transform effect) and blur go on every content layer. Ghosts (time-shifted copies)
// get a Transform effect that re-plays the camera at their own lag, like the browser's per-pass camera.
function akn_cam(cam, o) {
    var F = akn_camfn(cam, o), i, e, C = cam.content || [], lag, LG;
    if (o.xy) jzSetExpr(jzXf(cam.nul, 'ADBE Position'), F + 'var c=CAM(time);[value[0]+c[0],value[1]+c[1]]');
    if (o.s) jzSetExpr(jzXf(cam.nul, 'ADBE Scale'), F + 'var c=CAM(time);[value[0]*c[2],value[1]*c[3]]');
    if (o.rot) jzSetExpr(jzXf(cam.nul, 'ADBE Rotate Z'), F + 'value+CAM(time)[4]');
    for (i = 0; i < C.length; i++) {
        lag = 0; try { lag = C[i].startTime || 0; } catch (err) { lag = 0; }
        LG = 'var LAG=' + jzN(Math.max(0, lag)) + ';';
        if (o.skx) { e = jzEffect(C[i], 'ADBE Geometry2', 'JZ Cam Skew'); jzEX(e, 6, F + LG + '-CAM(time-LAG)[5]'); }
        if (lag > 0.001) {
            e = jzEffect(C[i], 'ADBE Geometry2', 'JZ Cam Lag'); jzEP(e, 3, 0);
            jzEX(e, 2, F + LG + 'var a=CAM(time),b=CAM(time-LAG),dx=b[0]-a[0],dy=b[1]-a[1],r=-a[4]*Math.PI/180,cs=Math.cos(r),sn=Math.sin(r);' +
                '[value[0]+(dx*cs-dy*sn)/a[2],value[1]+(dx*sn+dy*cs)/a[3]]');
            if (o.s) { jzEX(e, 4, F + LG + 'CAM(time-LAG)[3]/CAM(time)[3]*100'); jzEX(e, 5, F + LG + 'CAM(time-LAG)[2]/CAM(time)[2]*100'); }
            if (o.rot) jzEX(e, 8, F + LG + 'CAM(time-LAG)[4]-CAM(time)[4]');
        }
        if (o.blur) { e = jzEffect(C[i], 'ADBE Gaussian Blur 2', 'JZ Cam Blur'); jzEX(e, 1, F + 'var B=CAM(time)[6];B>0.4?B*' + jzN(2 * akn_px(cam)) + ':0'); }
    }
}
function akn_n(v) { return '(' + jzN(v) + ')'; }

jzReg('cam', 'knReadPan', {
    // one snap per word, then it settles back to centre (the whip blur is left out: skew only)
    plan: function (rng, st) { return { a: rng.range(0.026, 0.036) }; },
    apply: function (cam, P) {
        akn_cam(cam, { xy: 1, s: 1, skx: 1, code: 'var A=W*' + akn_n(P.a || 0.03) + '*KM;s=1.035;' +
            'if(NT<2){x=A*(1-2*ios(t/Math.max(0.4,DUR)))*0.5;}else{function pz(k){return A*(1-2*k/(NT-1));}' +
            'var k=Math.max(0,ci(t)),e=k>0?ob(cl((t-TS[k])/0.2),1.8):1;x=k>0?lrp(pz(k-1),pz(k),e):pz(0);x*=1-ioc(cl((t-TS[NT-1]-0.55)/0.5));' +
            'var wh=k>0?bel(cl((t-TS[k])/0.14)):0;skx=-(A<0?-1:1)*2.5*wh*KM;}' });
    }
});
jzReg('cam', 'knTiltKick', {
    // every new word kicks the frame into a lean, alternating sides; the last one sets it level
    plan: function (rng, st) { return { dir: rng.pick([1, -1]), a: rng.range(2.4, 3.4) }; },
    apply: function (cam, P) {
        akn_cam(cam, { rot: 1, s: 1, code: 'var A=' + akn_n(P.a || 3) + '*K*' + akn_n(P.dir || 1) + ';function ag(k){return k>=NT-1?0:(k%2?-A:A);}' +
            'var k=Math.max(0,ci(t)),d=t-TS[k],fr=k>0?ag(k-1):0,to=ag(k),r=to+(fr-to)*Math.exp(-d*7)*Math.cos(d*17);rot=Math.max(-5,Math.min(5,r));s=1.03+0.012*Math.abs(r)/3;' });
    }
});
jzReg('cam', 'knCardFlip', {
    // the frame flips over like a card to show the cut, and pinches on each new word
    plan: function (rng, st) { return { vert: rng.chance(0.3), dir: rng.pick([1, -1]) }; },
    apply: function (cam, P) {
        var code = 'var d=' + akn_n(P.dir || 1) + ',q=cl(t/0.42),th=90*(1-ob(q,1.7)),f=Math.max(0.04,Math.abs(Math.cos(th*0.0174533)));' +
            'for(var i=1;i<NT;i++){var dd=t-TS[i];if(dd>0&&dd<0.24)f*=1-0.1*K*bel(dd/0.24);}var sn=Math.sin(th*0.0174533);s=1.01;';
        if (P.vert) akn_cam(cam, { xy: 1, s: 1, code: code + 'sy=f;y=-H*0.02*sn;' });
        else akn_cam(cam, { xy: 1, s: 1, skx: 1, code: code + 'sx=f;skx=7*sn*d*K*0.4;x=W*0.02*sn*d;' });
    }
});
jzReg('cam', 'knShearKick', {
    // a sideways shear kick on every word that springs back upright
    plan: function (rng, st) { return { a: rng.range(5, 8) }; },
    apply: function (cam, P) {
        akn_cam(cam, { xy: 1, s: 1, skx: 1, code: 'var KK=Math.min(1.1,KM),ix=Math.max(0,ci(t)),sn=t-TS[ix],w=Math.exp(-sn*8)*Math.cos(sn*22),sg=ix%2?1:-1;' +
            'skx=' + akn_n(P.a || 6.5) + '*KK*w*sg;x=W*0.006*KK*w*sg;s=1.02;' });
    }
});
jzReg('cam', 'knJumpCut', {
    // hard reframes on every word (tighter, wider, off-centre) with no in-betweens, then back to centre
    plan: function (rng, st) { return { s0: rng.int(0, 99) }; },
    apply: function (cam, P) {
        var sd = ((cam.cut.seed | 0) + (P.s0 | 0)), rx = [], ry = [], k;
        for (k = 0; k < 6; k++) { rx.push(0.5 + 0.5 * akn_r(sd, k, 1)); ry.push(akn_rs(sd, k, 2)); }
        akn_cam(cam, { xy: 1, s: 1, code: 'var RX=' + akn_arr(rx) + ',RY=' + akn_arr(ry) + ',SS=[1,1.12,1.05,1.14,1.08,1.13],k=Math.max(0,ci(t));' +
            'if(t>TS[NT-1]+0.5||NT<2){s=1.02;}else{var S0=SS[(k+' + (((sd % 3) + 3) % 3) + ')%6],sg=k%2?1:-1;s=1+(S0-1)*K;x=sg*RX[k%6]*W*0.04*K*(k?1:0);y=RY[k%6]*H*0.035*K;}' });
    }
});
jzReg('cam', 'knRushIn', {
    // the whole frame rushes up from far away, overshoots a touch and locks
    plan: function (rng, st) { return { z: rng.range(0.66, 0.76), r: rng.range(-4, 4) }; },
    apply: function (cam, P) {
        akn_cam(cam, { s: 1, rot: 1, blur: 1, code: 'var q=cl(t/0.34);if(q>=1){s=1+0.01*cl((t-0.34)/Math.max(0.3,DUR));}else{var e=ob(q,1.9);' +
            's=lrp(1-(1-' + akn_n(P.z || 0.7) + ')*K,1,e);rot=' + akn_n(P.r || 0) + '*(1-oc(q))*K;blur=9*K*(1-oc(q));}' });
    }
});

/* ================================================================ TRANSITIONS */
function akn_tacc(t) { var sc = t.sc; return jzContrast(sc.accent, sc.bg) >= 1.6 ? sc.accent : sc.fg; }
function akn_thd(t) { return jzEvHead(t.t0, t.dur) + AKN_FNS; }
// Transform effect on a wrapper: identity outside the window
function akn_xf(L, name) { var e = jzEffect(L, 'ADBE Geometry2', name); jzEP(e, 3, 1); return L.property('ADBE Effect Parade').property(name); }

jzReg('trans', 'knCornerSwing', {
    // the old frame swings away round a corner, the new one swings in behind it
    plan: function (rng, st) { return { c: rng.int(0, 3) }; },
    build: function (t) {
        var W = t.W, H = t.H, c = (t.P.c | 0) % 4, px = (c === 1 || c === 2) ? W : 0, py = c >= 2 ? 0 : H, sg = (c === 0 || c === 2) ? 1 : -1, HD = akn_thd(t) + 'var e=ioc(p);';
        var bgS = jzEvSolid(t.comp, 'JZ Trans swing bg', t.sc.bg, t.t0, t.dur); bgS.moveAfter(t.A);
        var xa = akn_xf(t.A, 'JZ Trans swing A');
        jzEP(xa, 1, [px, py]); jzEP(xa, 2, [px, py]); jzEX(xa, 8, HD + 'p<=0||p>=1?0:' + (90 * sg) + '*e');
        var xb = akn_xf(t.B, 'JZ Trans swing B');
        jzEP(xb, 1, [px, py]); jzEP(xb, 2, [px, py]); jzEX(xb, 8, HD + 'p>=1?0:' + (-90 * sg) + '*(1-e)');
        // thin accent edge along the swinging seam
        var lw = Math.max(2, Math.min(W, H) * 0.006), S = jzEvShape(t.comp, 'JZ Trans swing edge', t.t0, t.dur), g = jzGrp(S, 'edge');
        var left = (c === 0 || c === 3), y0 = -(py ? H : 0);
        jzAddRect(g, lw, H * 2, 0, left ? -lw / 2 : lw / 2, y0 + H);
        jzAddFill(g, akn_tacc(t));
        jzXf(S, 'ADBE Position').setValue([px, py]);
        jzSetExpr(jzXf(S, 'ADBE Rotate Z'), HD + (90 * sg) + '*e');
        jzSetExpr(jzXf(S, 'ADBE Opacity'), HD + '100*bel(p)');
    }
});
jzReg('trans', 'knStutterCut', {
    // rhythm cut: old and new frames trade places in hard cuts before the new one holds
    plan: function (rng, st) { return { z: rng.range(1.05, 1.1), o: rng.range(0.02, 0.035) * rng.pick([1, -1]) }; },
    build: function (t) {
        var W = t.W, H = t.H, z = t.P.z || 1.07, o = (t.P.o || 0.03) * W;
        var HD = akn_thd(t) + 'var CU=[0.18,0.36,0.52,0.7],k=0;while(k<4&&p>=CU[k])k++;var SQ=[[0,1,0],[1,' + jzN(z) + ',' + jzN(o) + '],[0,' + jzN(1 / z) + ',' + jzN(-o * 0.6) + '],[1,' + jzN(z * 1.03) + ',' + jzN(-o) + '],[1,1,0]],Q=SQ[k];';
        var bgS = jzEvSolid(t.comp, 'JZ Trans stutter bg', (t.scPrev || t.sc).bg, t.t0, t.dur); bgS.moveAfter(t.A);
        var xa = akn_xf(t.A, 'JZ Trans stutter A');
        jzEP(xa, 1, [W / 2, H / 2]); jzEX(xa, 2, HD + '(p>0&&p<1&&Q[0]===0)?[' + jzN(W / 2) + '+Q[2],' + jzN(H / 2) + ']:[' + jzN(W / 2) + ',' + jzN(H / 2) + ']');
        jzEX(xa, 3, HD + '(p>0&&p<1&&Q[0]===0)?Q[1]*100:100');
        var xb = akn_xf(t.B, 'JZ Trans stutter B');
        jzEP(xb, 1, [W / 2, H / 2]); jzEX(xb, 2, HD + '(p>0&&p<1&&Q[0]===1)?[' + jzN(W / 2) + '+Q[2],' + jzN(H / 2) + ']:[' + jzN(W / 2) + ',' + jzN(H / 2) + ']');
        jzEX(xb, 3, HD + '(p>0&&p<1&&Q[0]===1)?Q[1]*100:100');
        jzEX(xb, 9, HD + 'p>=1?100:(p<=0?0:Q[0]*100)');
        // a one-frame accent bar on each cut
        var h = Math.max(3, H * 0.012), S = jzEvShape(t.comp, 'JZ Trans stutter bar', t.t0, t.dur), g = jzGrp(S, 'bar');
        jzAddRect(g, W, h, 0, W / 2, 0); jzAddFill(g, akn_tacc(t));
        jzSetExpr(jzXf(S, 'ADBE Position'), HD + '[0,(k%2?0.3:0.68)*' + jzN(H) + '+' + jzN(h / 2) + ']');
        jzSetExpr(jzXf(S, 'ADBE Opacity'), HD + 'var sn=k>0?p-CU[k-1]:1;sn<0.06&&p<1?90:0');
    }
});
jzReg('trans', 'knStripSlam', {
    // the new frame drops in as tall strips, one after another, each landing with a small bounce
    plan: function (rng, st) { return { n: rng.int(3, 5), rev: rng.chance(0.5), up: rng.chance(0.25) }; },
    build: function (t) {
        var W = t.W, H = t.H, n = Math.max(2, Math.min(6, t.P.n || 4)), up = !!t.P.up, i;
        var HD = akn_thd(t) + 'function yq(q){var tl=0.62,y;if(q<tl){var f=q/tl;y=-' + jzN(H) + '*(1-f*f);}else{var u=(q-tl)/(1-tl);y=-' + jzN(H) + '*0.06*Math.abs(Math.sin(u*Math.PI*2))*(1-u);}return ' + (up ? '-y' : 'y') + ';}';
        // darken the old frame
        var dk = jzEvSolid(t.comp, 'JZ Trans strip shade', '#000000', t.t0, t.dur); dk.moveBefore(t.A);
        jzSetExpr(jzXf(dk, 'ADBE Opacity'), HD + '30*p');
        var top = t.B;
        for (i = 0; i < n; i++) {
            var k = t.P.rev ? n - 1 - i : i, x0 = Math.round(i * W / n), x1 = Math.round((i + 1) * W / n), D = t.B.duplicate();
            try { D.name = 'JZ Trans strip ' + i; } catch (e0) {}
            D.moveBefore(top); top = D;
            D.inPoint = Math.max(0, t.t0); D.outPoint = t.t0 + t.dur;
            jzMaskRect(D, x0, -H, x1, H * 2);
            var xe = akn_xf(D, 'JZ Trans strip drop');
            jzEP(xe, 1, [W / 2, H / 2]);
            jzEX(xe, 2, HD + 'var q=cl((p-' + jzN(k / n * 0.45) + ')/0.55);[' + jzN(W / 2) + ',' + jzN(H / 2) + '+yq(q)]');
            jzEX(xe, 9, HD + 'var q=cl((p-' + jzN(k / n * 0.45) + ')/0.55);q>0?100:0');
        }
        var xb = akn_xf(t.B, 'JZ Trans strip hide');
        jzEX(xb, 9, HD + 'p>0&&p<1?0:100');
        // accent edge on each falling strip
        var S = jzEvShape(t.comp, 'JZ Trans strip edges', t.t0, t.dur), eh = Math.max(3, H * 0.008), acc = akn_tacc(t);
        S.moveBefore(top);
        for (i = 0; i < n; i++) {
            var k2 = t.P.rev ? n - 1 - i : i, a0 = Math.round(i * W / n), a1 = Math.round((i + 1) * W / n), g = jzGrp(S, 'edge ' + i);
            jzAddRect(g, a1 - a0, eh, 0, (a0 + a1) / 2, up ? 0 : H - eh / 2);
            jzAddFill(g, acc);
            var q = 'var q=cl((p-' + jzN(k2 / n * 0.45) + ')/0.55);';
            jzSetExpr(jzGX(g).property('ADBE Vector Position'), HD + q + '[0,yq(q)]');
            jzSetExpr(jzGX(g).property('ADBE Vector Group Opacity'), HD + q + '(q>0&&q<1)?80*(1-q):0');
        }
    }
});

/* ================================================================ TREATMENTS */
function akn_alive(L) {
    var td; try { td = L.property('ADBE Text Properties').property('ADBE Text Document').value; } catch (e) { return false; }
    if (!td || td.applyFill === false || !jzTrim(String(td.text || '')) || !(td.fontSize > 1)) return false;
    var op = 100; try { op = jzXf(L, 'ADBE Opacity').valueAtTime(0, true); } catch (e2) { op = jzXf(L, 'ADBE Opacity').value; }
    return op >= 89.9;
}
jzReg('treat', 'knWordScale', {
    // one key word is set big, the rest small \u2014 the line re-flows around the contrast
    plan: function (rng, st) { return { big: rng.range(1.28, 1.42), small: rng.range(0.78, 0.86), pick: rng.pick(['long', 'long', 'last', 'first']) }; },
    apply: function (ctx, L, P, o) {
        if (!akn_alive(L)) return;
        var G = akn_geo(L), Wd = akn_words(ctx.cut, G), nW = Wd.nW, i, j;
        if (nW < 2) return;
        var big = P.big || 1.35, small = P.small || 0.82, cnt = [], kan = [], kw = 0;
        for (j = 0; j < nW; j++) { cnt.push(0); kan.push(0); }
        for (i = 0; i < G.n; i++) { j = Wd.map[i]; if (j >= 0) { cnt[j]++; if (jzIsKanji(G.g[i].ch)) kan[j]++; } }
        if (P.pick === 'last') kw = nW - 1; else if (P.pick === 'first') kw = 0;
        else for (j = 1; j < nW; j++) if (cnt[j] + kan[j] * 0.5 > cnt[kw] + kan[kw] * 0.5) kw = j;
        var vert = G.vert, tb = 0, ts = 0;
        for (i = 0; i < G.n; i++) { j = Wd.map[i]; if (j < 0) continue; var ad = vert ? G.g[i].h : G.g[i].w; if (j === kw) tb += ad; else ts += ad; }
        var norm = Math.min(1, (tb + ts) / Math.max(1e-6, tb * big + ts * small));
        var offs = [], scs = [], lines = {}, li;
        for (i = 0; i < G.n; i++) { offs.push([0, 0]); scs.push(1); li = vert ? 0 : G.g[i].li; (lines[li] = lines[li] || []).push(i); }
        var td = L.property('ADBE Text Properties').property('ADBE Text Document').value, trk = (td.tracking || 0) / 1000 * G.fs;
        var just = 'c'; try { if (td.justification === ParagraphJustification.LEFT_JUSTIFY) just = 'l'; else if (td.justification === ParagraphJustification.RIGHT_JUSTIFY) just = 'r'; } catch (e1) {}
        for (li in lines) if (lines.hasOwnProperty(li)) {
            var ln = lines[li], g0 = G.g[ln[0]], gl = G.g[ln[ln.length - 1]];
            var a0 = vert ? g0.y - g0.h / 2 : g0.x - g0.w / 2, a1 = vert ? gl.y + gl.h / 2 : gl.x + gl.w / 2, pos = 0, np = [];
            for (i = 0; i < ln.length; i++) { j = Wd.map[ln[i]]; var k = (j === kw ? big : small) * norm, g = G.g[ln[i]], adv = (vert ? g.h : g.w) * k; np.push(pos + adv / 2); pos += adv + trk; scs[ln[i]] = k; }
            var len = pos - trk, start = just === 'l' ? a0 : just === 'r' ? a1 - len : a0 + (a1 - a0 - len) / 2;
            for (i = 0; i < ln.length; i++) {
                var g2 = G.g[ln[i]], na = start + np[i];
                offs[ln[i]] = vert ? [0, na - g2.y] : [na - g2.x, (1 - scs[ln[i]]) * 0.36 * G.fs];
            }
        }
        jzCharScales(L, scs, 'JZ Treat WordScale Size');
        jzCharOffsets(L, offs, 'JZ Treat WordScale Place');
    }
});
jzReg('treat', 'knWordPlate', {
    // every other word is knocked out of a solid plate
    plan: function (rng, st) { return { first: rng.chance(0.5), pad: rng.range(0.08, 0.14), tilt: rng.chance(0.4) ? rng.range(1.5, 3) : 0 }; },
    apply: function (ctx, L, P, o) {
        if (!akn_alive(L)) return;
        var G = akn_geo(L), Wd = akn_words(ctx.cut, G), nW = Wd.nW, i, j, sc = ctx.sc;
        if (nW < 2) return;
        var plate = jzContrast(sc.ink, sc.bg) >= 2 ? sc.ink : sc.fg, tc = null, bv = 0, cs = [sc.bg, sc.fg, sc.ink, sc.accent];
        for (i = 0; i < cs.length; i++) { if (!cs[i] || cs[i] === plate) continue; var k = jzContrast(cs[i], plate); if (k > bv) { bv = k; tc = cs[i]; } }
        if (bv < 2.6) tc = jzLum(plate) > 0.5 ? '#111111' : '#FFFFFF';
        var flags = [];
        for (i = 0; i < G.n; i++) { j = Wd.map[i]; flags.push(j >= 0 && ((j % 2 === 0) === !!P.first)); }
        jzCharColors(L, tc, flags, 'JZ Treat WordPlate Colour');
        // plates: one per on-word and line, in the text layer's own space (parented)
        var boxes = [], key = {};
        for (i = 0; i < G.n; i++) {
            if (!flags[i]) continue;
            var g = G.g[i], kk = Wd.map[i] * 100 + g.li, B = key[kk];
            if (!B) { B = key[kk] = { x0: 1e9, y0: 1e9, x1: -1e9, y1: -1e9 }; boxes.push(B); }
            B.x0 = Math.min(B.x0, g.x - g.w / 2); B.x1 = Math.max(B.x1, g.x + g.w / 2); B.y0 = Math.min(B.y0, g.y - g.h / 2); B.y1 = Math.max(B.y1, g.y + g.h / 2);
        }
        if (!boxes.length) return;
        var S = ctx.comp.layers.addShape(); S.name = 'JZ Treat WordPlate';
        S.moveAfter(L);
        var pd = G.fs * (P.pad || 0.1);
        for (i = 0; i < boxes.length; i++) {
            var b = boxes[i], gp = jzGrp(S, 'plate ' + i);
            jzAddRect(gp, b.x1 - b.x0 + pd * 2, b.y1 - b.y0 + pd * 1.4, 0, 0, 0);
            jzAddFill(gp, plate);
            jzGX(gp).property('ADBE Vector Position').setValue([(b.x0 + b.x1) / 2, (b.y0 + b.y1) / 2]);
            if (P.tilt) jzGX(gp).property('ADBE Vector Rotation').setValue(i % 2 ? P.tilt : -P.tilt);
        }
        S.setParentWithJump(L);
        jzXf(S, 'ADBE Anchor Point').setValue([0, 0]); jzXf(S, 'ADBE Position').setValue([0, 0]);
        var HD = jzHead(ctx, { mi: (o && o.mi) || 0, size: G.fs });
        jzSetExpr(jzXf(S, 'ADBE Opacity'), HD + 'var po=100;try{po=parent.transform.opacity;}catch(e){}time<DL?0:po*oc((time-DL)/IN)*(1-PO)');
    }
});

/* ================================================================ DECOR */
jzReg('decor', 'knSpeedTrail', { back: false, build: function (ctx, bb, d) {
    // speed lines stream off the back of the lyric (on its roomier side)
    var W = ctx.W, H = ctx.H, sc = ctx.sc, u = Math.min(W, H), i;
    if (!bb) bb = { x0: W * 0.35, x1: W * 0.65, y0: H * 0.4, y1: H * 0.6 };
    var vert = (bb.y1 - bb.y0) > (bb.x1 - bb.x0) * 1.3, roomA = vert ? bb.y0 : bb.x0, roomB = vert ? H - bb.y1 : W - bb.x1;
    var side = Math.abs(roomA - roomB) < u * 0.05 ? (d.right ? 1 : -1) : (roomB > roomA ? 1 : -1);
    var N = 8 + (d.n | 0) * 3, col = jzContrast(sc.sub, sc.bg) >= 1.4 ? sc.sub : sc.fg;
    var span = vert ? bb.x1 - bb.x0 : bb.y1 - bb.y0, len = vert ? bb.y1 - bb.y0 : bb.x1 - bb.x0, tight = Math.max(roomA, roomB) < u * 0.14;
    var e0 = side < 0 ? (vert ? bb.y0 : bb.x0) : (vert ? bb.y1 : bb.x1);
    var S = jzShapeLayer(ctx, 'kn speed trail', 0, 0), TH = jzTH(ctx) + 'var o=oc(time/0.3)*K;';
    for (i = 0; i < N; i++) {
        var r = function (k) { return akn_r(d.seed | 0, i, k); };
        var f = (i + 0.5) / N, lw = Math.max(1.5, u * (0.003 + r(1) * 0.006)), st = tight ? len * (0.2 + r(3) * 0.3) : -u * (0.015 + 0.02 * r(3));
        var c = tight ? (i % 2 ? (vert ? bb.x1 : bb.y1) + span * (0.05 + 0.3 * f) : (vert ? bb.x0 : bb.y0) - span * (0.05 + 0.3 * f))
            : jzLerp(vert ? bb.x0 : bb.y0, vert ? bb.x1 : bb.y1, 0.08 + 0.84 * f) + (r(7) - 0.5) * span * 0.04;
        var g = jzGrp(S, 'streak ' + i);
        jzAddRect(g, vert ? lw : 1, vert ? 1 : lw, 0, 0, 0);
        jzAddFill(g, col);
        var q = TH + 'var L0=' + jzN(u * (0.18 + r(2) * 0.3)) + '*o,ph=((time*' + jzN(1.3 + r(4) * 1.5) + '+' + jzN(r(5)) + ')%1+1)%1,s0=' + jzN(-st) + '+ph*L0*0.6,s1=s0+L0*(0.35+0.65*(1-ph)),' +
            'p0=' + jzN(e0) + '+' + side + '*s0,p1=' + jzN(e0) + '+' + side + '*s1;';
        jzSetExpr(jzGX(g).property('ADBE Vector Position'), q + (vert ? '[' + jzN(c) + ',(p0+p1)/2]' : '[(p0+p1)/2,' + jzN(c) + ']'));
        jzSetExpr(jzGX(g).property('ADBE Vector Scale'), q + (vert ? '[100,Math.abs(p1-p0)*100]' : '[Math.abs(p1-p0)*100,100]'));
        jzSetExpr(jzGX(g).property('ADBE Vector Group Opacity'), q + jzN((0.35 + 0.35 * r(6)) * 100) + '*o*(1-ph*0.5)');
    }
    jzNoGhost(S);
} });
jzReg('decor', 'knWordTicks', { back: false, build: function (ctx, bb, d) {
    // a segmented bar that fills one segment per word as the words arrive, with a running count
    var W = ctx.W, H = ctx.H, sc = ctx.sc, u = Math.min(W, H), ts = akn_wordTimes(ctx.cut, 6), n = ts.length, i;
    if (!bb) bb = { x0: W * 0.35, x1: W * 0.65, y0: H * 0.4, y1: H * 0.6 };
    var segW = u * 0.06, segH = Math.max(4, u * 0.011), gp = u * 0.014, tot = n * segW + (n - 1) * gp;
    var below = bb.y1 + u * 0.08 < H * 0.93, y = below ? bb.y1 + u * 0.06 : bb.y0 - u * 0.06;
    var cx = jzClamp((bb.x0 + bb.x1) / 2, tot / 2 + W * 0.06, W * 0.94 - tot / 2), x0 = cx - tot / 2, acc = jzContrast(sc.accent, sc.bg) >= 1.6 ? sc.accent : sc.fg;
    var S = jzShapeLayer(ctx, 'kn word ticks', x0, y), TH = jzTH(ctx) + AKN_FNS + 'var TS=' + akn_arr(ts) + ',o=oc(time/0.35)*K;' + AKN_CI + 'var k=ci(time);';
    for (i = 0; i < n; i++) {
        var g = jzGrp(S, 'slot ' + i);
        jzAddRect(g, segW, segH, 0, i * (segW + gp) + segW / 2, 0);
        jzAddFill(g, sc.sub, 35);
    }
    for (i = 0; i < n; i++) {
        var g2 = jzGrp(S, 'fill ' + i);
        jzAddRect(g2, segW, segH, 0, segW / 2, 0);
        jzAddFill(g2, acc);
        var x = i * (segW + gp), F = TH + 'var f=' + i + '<=k?oe((time-TS[' + i + '])/0.18):0,cur=' + i + '===k;';
        jzGX(g2).property('ADBE Vector Position').setValue([x, 0]);
        jzSetExpr(jzGX(g2).property('ADBE Vector Position'), F + '[' + jzN(x) + ',cur?' + jzN(-segH * 0.6) + '*(1-f):0]');
        jzSetExpr(jzGX(g2).property('ADBE Vector Scale'), F + '[f*100,cur?(1+1.2*(1-f))*100:100]');
        jzSetExpr(jzGX(g2).property('ADBE Vector Group Opacity'), F + 'f>0?100:0');
    }
    jzSetExpr(jzXf(S, 'ADBE Opacity'), TH + 'o*100');
    jzNoGhost(S);
    // running count: one small text per word, shown from its onset to the next one
    var fs = jzClamp(u * 0.028, 14 * ctx.u, 32 * ctx.u), mono = jzMonoF(ctx);
    for (i = 0; i < n; i++) {
        var T = jzText(ctx, jzPad(i + 1, 2) + ' / ' + jzPad(n, 2), { font: mono, size: fs, color: sc.sub, x: x0 + tot + u * 0.02, y: y, align: 'left' });
        if (i > 0) T.inPoint = ts[i];
        if (i < n - 1) T.outPoint = ts[i + 1];
        jzSetExpr(jzXf(T, 'ADBE Opacity'), TH + 'o*100');
        jzNoGhost(T);
    }
} });

// ================================================================ pack layoutsA part 1 (AE port of src/11p_layoutsA.js)
// Worked example for porting: lowerThird. Keep the browser's parameter keys in plan() \u2014 the panel gets the
// browser's params when a JSON plan is imported and uses this plan() when it plans by itself.

/* ---------------------------------------------------------------- 1 lowerThird \u2014 \u4E0B\u90E8\u30C6\u30ED\u30C3\u30D7 */
jzReg('layout', 'lowerThird', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'display', 'serif'])), side: rng.pick(['left', 'left', 'right']), bar: rng.pick(['line', 'tab', 'line']), label: rng.pick(['romaji', 'no', 'copy']), lift: rng.range(0, 0.04) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx);
        var t0 = jzTrim(c.text), n = jzCount(t0);
        var text = port ? jzSplitLines(t0, 6) : (n > 13 ? jzSplitLines(t0, Math.ceil(n / 2)) : t0);
        var left = jzP(ctx, 'side', 'left') !== 'right', mx = W * 0.07, x = left ? mx : W - mx;
        var barY = H * ((port ? 0.8 : 0.83) - jzP(ctx, 'lift', 0));
        // the lyric: fit, then sit it on the bar
        var L = jzText(ctx, text, { font: jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), size: 200, color: sc.fg, x: x, y: barY, align: left ? 'left' : 'right', track: 0.03,
            maxW: W * 0.84, maxH: H * (port ? 0.24 : 0.3), maxSize: u * (port ? 0.17 : 0.14) });
        var size = jzFontSize(L), sz = jzSize(L), cy = barY - size * 0.24 - sz[1] / 2;
        jzXf(L, 'ADBE Position').setValue([x, cy]);
        jzAnimate(ctx, L, { mi: 0 });
        // accent bar from the screen edge to just past the text (grows in, retracts on exit) + end square
        var th = Math.max(3 * ctx.u, size * 0.055), reach = mx + sz[0] + size * 0.45, sq = th * 2.6;
        var B = jzShapeLayer(ctx, 'bar', left ? 0 : W, barY + th / 2), g = jzGrp(B, 'bar');
        jzAddRect(g, reach, th, 0, left ? reach / 2 : -reach / 2, 0); jzAddFill(g, sc.accent);
        var g2 = jzGrp(B, 'tip'); jzAddRect(g2, sq, sq, 0, left ? reach : -reach, 0); jzAddFill(g2, sc.accent);
        jzSetExpr(jzXf(B, 'ADBE Scale'), jzTH(ctx) + 'var e=oe(time/0.55)*K;[value[0]*e,value[1]]');
        // label above the lyric: tag (LINE no.) + copy, slides in from the side
        var ls = jzSmallSize(ctx), ly = cy - sz[1] / 2 - size * 0.22 - ls * 0.7, tag = jzPad((c.line || 0) + 1, 2), lab = jzP(ctx, 'label', 'romaji');
        var copy = lab === 'romaji' ? (jzRomajiOf(ctx) || jzAltCopy(ctx)) : lab === 'no' ? 'LINE ' + tag : jzAltCopy(ctx);
        if (/^No\./.test(copy)) copy = jzFmtTime(c.start);
        var cx = x, dir = left ? 1 : -1, items = [];
        if (jzP(ctx, 'bar', 'line') === 'tab') {
            var T = jzText(ctx, tag, { font: jzMonoF(ctx), size: ls, color: jzOnCol(sc, sc.ink), x: 0, y: ly, track: 0.1 });
            var tw = jzSize(T)[0] + ls * 1.1, th2 = ls * 1.6;
            var R = jzRectLayer(ctx, 'tab', cx + dir * tw / 2, ly, tw, th2, sc.ink);
            jzXf(T, 'ADBE Position').setValue([cx + dir * tw / 2, ly]);
            T.moveBefore(R); items.push(R, T);
            cx += (tw + ls * 0.7) * dir;
        } else {
            var q = ls * 0.55, Q = jzRectLayer(ctx, 'mark', cx + dir * q / 2, ly, q, q, sc.accent);
            cx += (q + ls * 0.6) * dir;
            var T2 = jzText(ctx, tag, { font: jzMonoF(ctx), size: ls, color: sc.fg, x: cx, y: ly, align: left ? 'left' : 'right', track: 0.1 });
            items.push(Q, T2);
            cx += (jzSize(T2)[0] + ls * 0.8) * dir;
        }
        items.push(jzText(ctx, copy, { font: lab === 'copy' ? jzBodyF(ctx) : jzMonoF(ctx), size: ls, color: sc.sub, x: cx, y: ly, align: left ? 'left' : 'right', track: 0.12 }));
        for (var i = 0; i < items.length; i++) { jzSlideIn(ctx, items[i], -dir * ls * 2, 0, 0.12, 0.4); jzNoGhost(items[i]); }   // label: main pass only (the bar is ghosted)
        return jzBB(L);
    }
});

/* ================================================================ la1 helpers (ports of the browser pack's shared helpers) */
// extra easing for expressions (in-out cubic) \u2014 append after jzTH(ctx)
var la1_FNS = 'function ioc(x){x=cl(x);return x<0.5?4*x*x*x:1-Math.pow(-2*x+2,3)/2;}\n';
function la1_H(ctx) { return jzTH(ctx) + la1_FNS; }
function la1_isSmall(c) { return '\u3041\u3043\u3045\u3047\u3049\u3063\u3083\u3085\u3087\u308E\u30A1\u30A3\u30A5\u30A7\u30A9\u30C3\u30E3\u30E5\u30E7\u30EE\u30F5\u30F6'.indexOf(c) >= 0 && c.length > 0; }
function la1_isPunct(c) { return /[\u3001\u3002\uFF0C\uFF0E,.!?\uFF01\uFF1F\u2026\u2025\u30FB\u300C\u300D\u300E\u300F\uFF08\uFF09()\u3010\u3011\u3008\u3009\u300A\u300B\u3014\u3015\uFF3B\uFF3D\[\]'"\u201C\u201D\u2018\u2019\u30FC\u301C\uFF5E:\uFF1A;\uFF1B\-\u2014\u2015]/.test(c); }
function la1_isBad(c) { return la1_isSmall(c) || la1_isPunct(c) || c === '\u30FC' || c === ' '; }
var la1_VROT = '\u30FC\u301C\uFF5E\u2026\u2025\u2015\u2014-()\uFF08\uFF09\u300C\u300D\u300E\u300F\u3010\u3011\u3008\u3009\u300A\u300B\u3014\u3015[]\uFF3B\uFF3D\u2192\u2190:\uFF1A;\uFF1B=\uFF1D';
// chunk boundaries (glyph offsets) of a word \u2014 stands in for the browser's Intl word segments
function la1_bounds(word) {
    var out = {}, ch = jzChunk(word), acc = 0;
    for (var i = 0; i < ch.length; i++) { acc += jzChars(ch[i]).length; out[acc] = 1; }
    return out;
}
function la1_split2(word, force) {
    var ch = jzChars(word), n = ch.length, sb = la1_bounds(word), best = Math.max(1, Math.floor(n / 2)), bs = -1e9;
    for (var c = 1; c < n; c++) {
        var a = ch[c - 1], b = ch[c];
        if (!force && ((c === 1 && !jzIsKanji(a)) || (c === n - 1 && !jzIsKanji(b)))) continue;
        var s = -Math.abs(c - n / 2) * 0.9;
        if (a === ' ' || a === '\u3000' || (la1_isPunct(a) && a !== '\u30FC')) s += 5;
        if (jzIsHira(a) && !jzIsHira(b) && !la1_isBad(b)) s += 3;
        if (sb[c]) s += 2;
        if (la1_isBad(b)) s -= 6;
        if (jzIsKanji(a) && jzIsKanji(b)) s -= 2;
        if (jzIsKanji(a) && jzIsHira(b)) s -= sb[c] ? 0.5 : 2.5;
        if (jzIsHira(a) && jzIsHira(b) && !sb[c]) s -= 2.5;
        if (s > bs) { bs = s; best = c; }
    }
    if (!force && bs < -1.5 && !jzHasLatin(word)) return [word];
    var out = [], p1 = jzTrim(ch.slice(0, best).join('')), p2 = jzTrim(ch.slice(best).join(''));
    if (p1) out.push(p1);
    if (p2) out.push(p2);
    return out;
}
// split text into k balanced chunks (browser splitK): word chunks first, long chunks cut at the most natural boundary
function la1_splitK(text, k, force) {
    var t = jzTrim(String(text || '')), i;
    if (!t) return [''];
    // words (latin: by spaces; Japanese: chunks of each space-separated phrase); sp[i] = a space follows word i
    var latin = jzHasLatin(t), pcs = t.split(/\s+/), words = [], sp = [];
    for (var pi = 0; pi < pcs.length; pi++) {
        var raw = latin ? [pcs[pi]] : jzChunk(pcs[pi]);
        for (i = 0; i < raw.length; i++) { var w0 = jzTrim(raw[i]); if (w0) { words.push(w0); sp.push(false); } }
        if (!latin && sp.length) sp[sp.length - 1] = pi < pcs.length - 1;
    }
    if (!words.length) { words = [t]; sp = [false]; }
    k = Math.max(1, Math.min(k, jzCount(t)));
    var hard = {}, parts;
    for (var guard = 0; words.length < k && guard < 16; guard++) {
        var bi = -1, bl = 1;
        for (i = 0; i < words.length; i++) { var l = jzCount(words[i]); if (l > bl && !hard[words[i]]) { bl = l; bi = i; } }
        if (bi < 0) break;
        parts = latin ? [words[bi]] : la1_split2(words[bi]);
        if (parts.length < 2 && force && words.length < force) parts = la1_split2(words[bi], true);
        if (parts.length < 2) { hard[words[bi]] = 1; continue; }
        sp.splice(bi, 1, false, sp[bi]); words.splice(bi, 1, parts[0], parts[1]);
    }
    var sep = latin ? ' ' : '';
    if (k <= 1) return [words.join(sep)];
    if (words.length <= k) return words;
    function part(ws) {
        var pre = [0], n = ws.length, j;
        for (j = 0; j < n; j++) pre.push(pre[j] + jzCount(ws[j]) + 0.5);
        var tgt = pre[n] / k, best = null, bestS = 1e18, cnt = 0;
        function rec(start, g, cuts) {
            if (++cnt > 5000) return;
            if (g === k - 1) {
                var s = 0, prev = 0, all = cuts.concat([n]);
                for (var q = 0; q < all.length; q++) { var d = pre[all[q]] - pre[prev] - tgt; s += d * d; prev = all[q]; if (q < all.length - 1 && sp[all[q] - 1]) s -= tgt * tgt * 0.25; }
                if (s < bestS) { bestS = s; best = all; }
                return;
            }
            for (var c = start + 1; c <= n - (k - 1 - g); c++) rec(c, g + 1, cuts.concat([c]));
        }
        rec(0, 0, []);
        var out = [], prv = 0;
        if (!best) { for (j = 0; j < n; j++) out.push([ws[j]]); return out; }
        for (j = 0; j < best.length; j++) { out.push(ws.slice(prv, best[j])); prv = best[j]; }
        return out;
    }
    var groups = part(words);
    for (var it = 0; it < 3; it++) {
        var gl = [], mx = -1, mn = 1e9, gi = 0;
        for (i = 0; i < groups.length; i++) { var sum = 0; for (var q2 = 0; q2 < groups[i].length; q2++) sum += jzCount(groups[i][q2]); gl.push(sum); if (sum > mx) { mx = sum; gi = i; } if (sum < mn) mn = sum; }
        if (mx <= mn * 1.7 + 1) break;
        var g0 = groups[gi], bw = null;
        for (i = 0; i < g0.length; i++) { if (!hard[g0[i]] && jzCount(g0[i]) >= 3 && (!bw || jzCount(g0[i]) > jzCount(bw))) bw = g0[i]; }
        if (!bw) break;
        parts = latin ? [bw] : la1_split2(bw);
        if (parts.length < 2) { hard[bw] = 1; continue; }
        var wi = jzIndexOf(words, bw);
        sp.splice(wi, 1, false, sp[wi]); words.splice(wi, 1, parts[0], parts[1]);
        groups = part(words);
    }
    var res = [];
    for (i = 0; i < groups.length; i++) res.push(groups[i].join(sep));
    return res;
}
// single glyphs, small kana / punctuation / \u30FC kept on the preceding glyph
function la1_charUnits(text) {
    var a = jzChars(jzStrip(text)), out = [];
    for (var i = 0; i < a.length; i++) { if (out.length && la1_isBad(a[i])) out[out.length - 1] += a[i]; else out.push(a[i]); }
    return out;
}
// balanced display line breaks at chunk boundaries (AE line break = \r)
function la1_brk(text, maxPer) {
    var t = jzTrim(String(text || '')), n = jzCount(t);
    if (n <= maxPer) return t;
    return la1_splitK(t, Math.ceil(n / maxPer)).join('\r');
}
function la1_box(x0, y0, x1, y1) { return { x0: x0, y0: y0, x1: x1, y1: y1, cx: (x0 + x1) / 2, cy: (y0 + y1) / 2 }; }
// true comp-space bbox of an unrotated layer (text rect, anchor, position, scale)
function la1_bb(L) {
    var r = jzRect(L), a = jzXf(L, 'ADBE Anchor Point').value, p = jzXf(L, 'ADBE Position').value, s = jzXf(L, 'ADBE Scale').value;
    var x0 = p[0] + (r.left - a[0]) * s[0] / 100, y0 = p[1] + (r.top - a[1]) * s[1] / 100;
    return la1_box(x0, y0, x0 + r.width * s[0] / 100, y0 + r.height * s[1] / 100);
}
// { w, h } of a string at font size 100 (temporary layer)
function la1_meas(ctx, str, font, track, lead) {
    var L = jzText(ctx, str, { font: font, size: 100, color: '#FFFFFF', x: -9999, y: -9999, track: track || 0, leading: /\r/.test(str) ? 100 * (lead || 1.2) : null });
    var r = jzRect(L); L.remove();
    return { w: Math.max(1, r.width), h: Math.max(1, r.height) };
}
function la1_fit(m, maxW, maxH) { return 100 * Math.min(maxW / m.w, maxH / m.h); }
// text layer at a given size: o { align, x, y, color, track, lead, fill, stroke, strokeColor, opacity, name }
function la1_T(ctx, str, font, size, o) {
    o = o || {};
    return jzText(ctx, str, { font: font, size: size, color: o.color || ctx.sc.fg, x: o.x || 0, y: o.y || 0, align: o.align, track: o.track || 0,
        leading: /\r/.test(str) ? size * (o.lead || 1.2) : null, fill: o.fill, stroke: o.stroke, strokeColor: o.strokeColor, opacity: o.opacity, name: o.name });
}
// move plates / guides directly under layer L, keeping their creation order
function la1_behind(list, L) { for (var i = 0; i < list.length; i++) list[i].moveAfter(L); }
// keep helper layers out of the tinted ghosts (the browser draws them with ghost off / main pass only)
function la1_ng(list) { for (var i = 0; i < list.length; i++) if (list[i]) jzNoGhost(list[i]); return list; }
function la1_opx(ctx, L, alphaExpr) { jzSetExpr(jzXf(L, 'ADBE Opacity'), la1_H(ctx) + 'value*(' + alphaExpr + ')'); }
function la1_shape(g, sh) { var p = jzVecs(g).addProperty('ADBE Vector Shape - Group'); p.property('ADBE Vector Shape').setValue(sh); return p; }
function la1_len(a, b) { return Math.sqrt((b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1])); }
// circular arc (degrees, screen angles, a0 -> a1) as an open bezier Shape
function la1_arc(cx, cy, r, a0, a1) {
    var segs = Math.max(1, Math.ceil(Math.abs(a1 - a0) / 90)), da = (a1 - a0) / segs, k = 4 / 3 * Math.tan(da * Math.PI / 720) * r;
    var V = [], I = [], O = [];
    for (var i = 0; i <= segs; i++) {
        var a = (a0 + da * i) * Math.PI / 180, c = Math.cos(a), s = Math.sin(a);
        V.push([cx + c * r, cy + s * r]); I.push(i ? [s * k, -c * k] : [0, 0]); O.push(i < segs ? [-s * k, c * k] : [0, 0]);
    }
    var sh = new Shape(); sh.vertices = V; sh.inTangents = I; sh.outTangents = O; sh.closed = false;
    return sh;
}
// ONE text layer holding glyphs placed anywhere (keeps the lyric one editable text): one glyph per line, centre-justified,
// moved to its target centre by a per-glyph Position animator; o: { size, font, color, rots[], scales[], name, fill, stroke, strokeColor }
function la1_glyphLayer(ctx, gl, pts, o) {
    var size = o.size, lead = size * 1.2, bo = size * 0.38, n = gl.length, i, x0 = 1e9, x1 = -1e9, y0 = 1e9, y1 = -1e9;
    for (i = 0; i < n; i++) { x0 = Math.min(x0, pts[i][0]); x1 = Math.max(x1, pts[i][0]); y0 = Math.min(y0, pts[i][1]); y1 = Math.max(y1, pts[i][1]); }
    var cx = (x0 + x1) / 2, cy = (y0 + y1) / 2;
    var L = jzText(ctx, gl.join('\r'), { font: o.font, size: size, color: o.color || ctx.sc.fg, x: cx, y: cy, leading: lead, fill: o.fill, stroke: o.stroke, strokeColor: o.strokeColor, name: o.name || gl.join('') });
    jzXf(L, 'ADBE Anchor Point').setValue([0, 0]);
    jzXf(L, 'ADBE Position').setValue([cx, cy]);
    var offs = [];
    for (i = 0; i < n; i++) offs.push([pts[i][0] - cx, pts[i][1] + bo - cy - i * lead]);
    jzCharOffsets(L, offs, 'JZ Place');
    if (o.rots) jzCharRotations(L, o.rots, 'JZ Turn');
    if (o.scales) jzCharScales(L, o.scales, 'JZ Size');
    return L;
}
// the lyric as separate glyph items (the browser draws these glyphs as items with their own motion index): one centre-justified
// text layer per glyph, anchored on its em-box centre, placed / turned / sized per glyph; o: { size, font, color, colors[], rots[], scales[] }
function la1_glyphItems(ctx, gl, pts, o) {
    var out = [];
    for (var i = 0; i < gl.length; i++) {
        var sz = o.size * (o.scales ? o.scales[i] : 1);
        var L = jzText(ctx, gl[i], { font: o.font, size: sz, color: (o.colors && o.colors[i]) || o.color || ctx.sc.fg, x: pts[i][0], y: pts[i][1] });
        jzXf(L, 'ADBE Anchor Point').setValue([0, -sz * 0.38]);
        jzXf(L, 'ADBE Position').setValue([pts[i][0], pts[i][1]]);
        if (o.rots && o.rots[i]) jzXf(L, 'ADBE Rotate Z').setValue(o.rots[i]);
        out.push(L);
    }
    return out;
}
function la1_ptsBox(pts, h) {
    var x0 = 1e9, x1 = -1e9, y0 = 1e9, y1 = -1e9;
    for (var i = 0; i < pts.length; i++) { x0 = Math.min(x0, pts[i][0]); x1 = Math.max(x1, pts[i][0]); y0 = Math.min(y0, pts[i][1]); y1 = Math.max(y1, pts[i][1]); }
    return la1_box(x0 - h, y0 - h, x1 + h, y1 + h);
}

/* ---------------------------------------------------------------- 2 corners \u2014 \u5BFE\u89D2\u914D\u7F6E */
jzReg('layout', 'corners', {
    plan: function (rng, cut, st) {
        return { chunks: la1_splitK(cut.text, 2, 2), font: rng.pick(jzFontsOf(st, ['display', 'serif'])), diag: rng.pick(['main', 'main', 'anti']), link: rng.pick(['elbow', 'straight', 'elbow']), ratio: rng.pick([1, 1, 0.76]) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), i;
        var ch = jzP(ctx, 'chunks', null);
        if (!ch || ch.length < 2) ch = la1_splitK(c.text, 2, 2);
        var A0 = ch[0], B0 = ch.length > 1 ? ch.slice(1).join(jzHasLatin(ch[0]) ? ' ' : '') : '';
        var maxPer = port ? 5 : 9, A = la1_brk(A0, maxPer), B = B0 ? la1_brk(B0, maxPer) : '';
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), ratio = jzP(ctx, 'ratio', 1) || 1;
        var bw = W * (port ? 0.84 : 0.58), bh = H * (port ? 0.24 : 0.3);
        var mA = la1_meas(ctx, A, font, 0.02, 1.1), mB = B ? la1_meas(ctx, B, font, 0.02, 1.1) : null;
        var s = Math.min(la1_fit(mA, bw, bh), mB ? la1_fit(mB, bw, bh) / ratio : 1e9, u * 0.22), sA = s, sB = s * ratio;
        var mx = W * 0.075, my = H * (port ? 0.15 : 0.13), aL = jzP(ctx, 'diag', 'main') !== 'anti';
        var ax = aL ? mx : W - mx, bx = aL ? W - mx : mx;
        // the two halves in opposite corners
        var LA = la1_T(ctx, A, font, sA, { align: aL ? 'left' : 'right', x: ax, y: my + mA.h * sA / 200, track: 0.02, lead: 1.1 });
        var Ab = la1_bb(LA), LB = null, Bb;
        if (B) { LB = la1_T(ctx, B, font, sB, { align: aL ? 'right' : 'left', x: bx, y: H - my - mB.h * sB / 200, track: 0.02, lead: 1.1 }); Bb = la1_bb(LB); }
        else Bb = la1_box(bx, H - my, bx, H - my);
        // connector (draws itself after half the entrance), end dots, small caption on its longest segment
        var g = u * 0.028, link = jzP(ctx, 'link', 'elbow'), pts;
        if (link === 'elbow') {
            var xb = aL ? Bb.x0 + sB * 0.5 : Bb.x1 - sB * 0.5, clear = aL ? xb > Ab.x1 + g * 2 : xb < Ab.x0 - g * 2;
            if (clear) pts = [[aL ? Ab.x1 + g : Ab.x0 - g, Ab.cy], [xb, Ab.cy], [xb, Bb.y0 - g]];
            else { var xm = aL ? Math.max(Ab.x0, Bb.x0) + sB * 0.5 : Math.min(Ab.x1, Bb.x1) - sB * 0.5; pts = [[xm, Ab.y1 + g], [xm, Bb.y0 - g]]; }
        } else pts = [[aL ? Ab.x1 : Ab.x0, Ab.y1 + g], [aL ? Bb.x0 : Bb.x1, Bb.y0 - g]];
        var HD = la1_H(ctx), E = 'var e=ioc((time-' + jzN((c.inDur || 0.3) * 0.5) + ')/0.55)*K;';
        var lw = Math.max(1.5 * ctx.u, u * 0.0022), r0 = u * 0.007, deco = [];
        deco.push(jzPathLayer(ctx, 'link', pts, sc.sub, { width: lw, trim: HD + E + '100*e' }));
        var D0 = jzEllipseLayer(ctx, 'link start', pts[0][0], pts[0][1], r0 * 2, r0 * 2, sc.bg, { stroke: sc.sub, strokeW: lw });
        jzSetExpr(jzXf(D0, 'ADBE Opacity'), HD + E + 'value*Math.min(1,e*4)');
        var z = pts[pts.length - 1], D1 = jzEllipseLayer(ctx, 'link end', z[0], z[1], r0 * 2, r0 * 2, sc.accent);
        jzSetExpr(jzXf(D1, 'ADBE Opacity'), HD + E + 'e>0.97?value:0');
        deco.push(D0, D1);
        var bi = 1, bl = 0;
        for (i = 1; i < pts.length; i++) { var l = la1_len(pts[i - 1], pts[i]); if (l > bl) { bl = l; bi = i; } }
        var q0 = pts[bi - 1], q1 = pts[bi], ls = jzSmallSize(ctx) * 0.9;
        var horiz = Math.abs(q1[1] - q0[1]) < Math.abs(q1[0] - q0[0]) * 0.3, mxp = (q0[0] + q1[0]) / 2, myp = (q0[1] + q1[1]) / 2;
        var cap = jzTrim('No.' + jzLineNo(ctx) + '  ' + (jzRomajiOf(ctx) || ''));
        if (bl > ls * 8) {
            var C = horiz ? jzText(ctx, cap, { font: jzMonoF(ctx), size: ls, color: sc.sub, x: mxp, y: myp - ls * 1.1, track: 0.1 })
                : jzText(ctx, cap, { font: jzMonoF(ctx), size: ls, color: sc.sub, x: mxp + (aL ? ls * 0.8 : -ls * 0.8) * (link === 'straight' ? -1 : 1), y: myp, align: aL ? 'left' : 'right', track: 0.1 });
            jzSetExpr(jzXf(C, 'ADBE Opacity'), HD + E + 'value*cl((e-0.5)*3)');
            deco.push(C);
        }
        la1_behind(la1_ng(deco), LA);
        jzAnimate(ctx, LA, { mi: 0 });
        if (LB) jzAnimate(ctx, LB, { mi: Math.min(8, jzCount(A0) + 2) });
        return LB ? jzUnion(Ab, Bb) : Ab;
    }
});

/* ---------------------------------------------------------------- 3 staircase \u2014 \u968E\u6BB5 */
jzReg('layout', 'staircase', {
    plan: function (rng, cut, st) {
        var port = cut.H > cut.W * 1.08, n = cut.n, latin = /[A-Za-z]/.test(cut.text);
        var units = latin ? la1_splitK(cut.text, port ? 6 : 4)
            : port ? (n <= 9 ? la1_charUnits(cut.text) : la1_splitK(cut.text, Math.min(6, Math.ceil(n / 2.6))))
            : (n <= 6 ? la1_charUnits(cut.text) : la1_splitK(cut.text, n <= 10 ? 3 : 4));
        return { units: units, dir: port ? 'down' : rng.pick(['down', 'down', 'up']), flip: !port && rng.chance(0.22), shrink: rng.range(0.82, 0.9), font: rng.pick(jzFontsOf(st, ['display', 'display', 'serif'])), tread: rng.pick(['line', 'line', 'none']), nums: rng.chance(0.55) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), i;
        var units = jzP(ctx, 'units', null);
        if (!units || !units.length) units = [jzStrip(c.text)];
        var k = units.length, font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display'));
        var r = k > 1 ? Math.max(jzP(ctx, 'shrink', 0.86) || 0.86, Math.pow(0.5, 1 / (k - 1))) : 1;
        var up = jzP(ctx, 'dir', 'down') === 'up' && !port, fx = up ? 1 : port ? 0.5 : 0.92, flip = !!jzP(ctx, 'flip', false);
        // step geometry in design units (size 100 for the first step), then fitted to the frame
        var Q = [], x0 = 1e9, x1 = -1e9, y0 = 1e9, y1 = -1e9;
        for (i = 0; i < k; i++) {
            var s = 100 * Math.pow(r, i), w = la1_meas(ctx, units[i], font, 0.02).w * s / 100, x = 0, y = 0;
            if (i > 0) { var q = Q[i - 1]; x = q.x + q.w * fx + q.s * 0.14; y = q.y + (q.s + s) / 2 * 1.05 * (up ? -1 : 1); }
            Q.push({ t: units[i], s: s, w: w, x: x, y: y });
            x0 = Math.min(x0, x); x1 = Math.max(x1, x + w); y0 = Math.min(y0, y - s / 2); y1 = Math.max(y1, y + s * 0.62);
        }
        var k2 = Math.min(W * 0.86 / (x1 - x0), H * 0.78 / (y1 - y0), u * 0.3 / 100);
        var ox = W / 2 - (x0 + x1) / 2 * k2, oy = H / 2 - (y0 + y1) / 2 * k2;
        function X(q) { return ox + q.x * k2; }
        function Y(q) { return oy + q.y * k2; }
        function mir(xx) { return flip ? W - xx : xx; }
        // the steps (one text item per step, like the browser)
        var Ls = [], bb = null;
        for (i = 0; i < k; i++) {
            var qq = Q[i], xl = X(qq);
            var L = la1_T(ctx, qq.t, font, qq.s * k2, { align: 'left', x: flip ? W - xl - qq.w * k2 : xl, y: Y(qq), track: 0.02 });
            Ls.push(L); bb = jzUnion(bb, la1_bb(L));
        }
        // tread / riser guide drawing itself along the steps + step numbers
        var tread = jzP(ctx, 'tread', 'line') !== 'none', nums = !!jzP(ctx, 'nums', false), deco = [];
        if (tread || nums) {
            var HD = la1_H(ctx), E = 'var e=ioc(time/' + jzN(Math.max(0.5, (c.inDur || 0.3) * 1.6)) + ')*K;';
            var lw = Math.max(2 * ctx.u, 100 * k2 * 0.022), pts = [];
            for (i = 0; i < k; i++) {
                var q3 = Q[i], yb = Y(q3) + q3.s * k2 * 0.58, gap = q3.s * k2 * 0.14;
                if (i === 0) pts.push([mir(X(q3) - gap), yb]);
                if (i < k - 1) { var nx = Q[i + 1], xr = X(nx) - gap / 2; pts.push([mir(xr), yb], [mir(xr), Y(nx) + nx.s * k2 * 0.58]); }
                else pts.push([mir(X(q3) + q3.w * k2 + gap), yb]);
            }
            if (tread) deco.push(jzPathLayer(ctx, 'tread', pts, sc.accent, { width: lw, trim: HD + E + '100*e' }));
            if (nums) {
                var ls = jzSmallSize(ctx) * 0.85;
                for (i = 0; i < k; i++) {
                    var q4 = Q[i], yb2 = Y(q4) + q4.s * k2 * 0.58;
                    var T = jzText(ctx, jzPad(i + 1, 2), { font: jzMonoF(ctx), size: ls, color: sc.sub, x: mir(X(q4)), y: yb2 + ls * 1.1, align: flip ? 'right' : 'left' });
                    jzSetExpr(jzXf(T, 'ADBE Opacity'), HD + E + 'value*cl((e*' + k + '-' + i + ')*1.5)');
                    deco.push(T);
                }
            }
        }
        la1_behind(la1_ng(deco), Ls[0]);
        for (i = 0; i < k; i++) jzAnimate(ctx, Ls[i], { mi: i * 2 });
        return bb;
    }
});

/* ---------------------------------------------------------------- 4 zigzag \u2014 \u30B8\u30B0\u30B6\u30B0 */
jzReg('layout', 'zigzag', {
    plan: function (rng, cut, st) {
        var port = cut.H > cut.W * 1.08;
        return { font: rng.pick(jzFontsOf(st, ['display'])), orient: port && cut.n > 5 ? 'v' : 'h', amp: rng.range(0.24, 0.34), phase: rng.pick([1, -1]), rails: rng.pick(['under', 'both', 'under', 'over']), tilt: rng.chance(0.35) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), i;
        var t = jzHasLatin(c.text) ? jzTrim(c.text).replace(/\s+/g, ' ') : jzStrip(c.text), chars = jzChars(t), n = Math.max(1, chars.length);
        var v = jzP(ctx, 'orient', 'h') === 'v', amp = jzP(ctx, 'amp', 0.3), ph = jzP(ctx, 'phase', 1) < 0 ? -1 : 1, tilt = !!jzP(ctx, 'tilt', false);
        var size = Math.min((v ? H * 0.78 : W * 0.8) / n / 1.08, u * 0.2), step = size * 1.08, A = size * amp;
        function pos(j) { var s = (j % 2 ? 1 : -1) * ph, kk = j - (n - 1) / 2; return v ? [W / 2 + s * A, H / 2 + kk * step] : [W / 2 + kk * step, H / 2 + s * A]; }
        // the lyric: every glyph on the zigzag (one text layer, per-glyph placement)
        var gl = [], gp = [], rots = [], mis = [];
        for (i = 0; i < chars.length; i++) {
            var ch = chars[i];
            if (ch === ' ' || ch === '\u3000') continue;
            gl.push(ch); gp.push(pos(i)); mis.push(i);
            rots.push((tilt ? (i % 2 ? 1 : -1) * ph * 6 * (v ? -1 : 1) : 0) + (v && (la1_VROT.indexOf(ch) >= 0 || jzIsLatin(ch)) ? 90 : 0));
        }
        var GL = la1_glyphItems(ctx, gl, gp, { size: size, font: jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), rots: rots });
        // zigzag rails riding just outside the glyphs, extended to the frame margins
        var off = size * (0.52 + amp * 0.9), lw = Math.max(2 * ctx.u, size * 0.03), lim = (v ? H : W) * 0.04;
        var ext = Math.ceil(((v ? H : W) / 2 - n * step / 2 - lim) / step);
        var HD = la1_H(ctx), E = 'var e=ioc(time/' + jzN(Math.max(0.45, (c.inDur || 0.3) * 1.5)) + ')*K;';
        var rails = jzP(ctx, 'rails', 'under'), sides = rails === 'both' ? [1, -1] : rails === 'over' ? [-1] : [1], deco = [];
        for (var sI = 0; sI < sides.length; sI++) {
            var sg = sides[sI], pts = [];
            for (i = -ext; i <= n - 1 + ext; i++) {
                var q = pos(i), pt = v ? [q[0] + sg * off, q[1]] : [q[0], q[1] + sg * off], cc = v ? pt[1] : pt[0];
                if (cc < lim || cc > (v ? H : W) - lim) continue;
                pts.push(pt);
            }
            if (pts.length < 2) continue;
            var col = sI === 0 ? sc.accent : sc.sub;
            var R = jzPathLayer(ctx, 'rail', pts, col, { width: lw, trim: HD + E + '100*e' });
            var g0 = jzGrp(R, 'start'); jzAddEllipse(g0, lw * 4.4, lw * 4.4, pts[0][0], pts[0][1]); jzAddFill(g0, col);
            jzSetExpr(jzGX(g0).property('ADBE Vector Group Opacity'), HD + E + 'e>0?100:0');
            var z = pts[pts.length - 1], g1 = jzGrp(R, 'end'); jzAddEllipse(g1, lw * 4.4, lw * 4.4, z[0], z[1]); jzAddFill(g1, col);
            jzSetExpr(jzGX(g1).property('ADBE Vector Group Opacity'), HD + E + 'e>0.98?100:0');
            deco.push(R);
        }
        la1_ng(deco);
        if (GL.length) la1_behind(deco, GL[0]);
        for (i = 0; i < GL.length; i++) jzAnimate(ctx, GL[i], { mi: mis[i] });
        return gp.length ? la1_ptsBox(gp, size / 2) : la1_box(W / 2 - step, H / 2 - step, W / 2 + step, H / 2 + step);
    }
});

/* ---------------------------------------------------------------- 5 arcTop \u2014 \u8679\u306E\u5F27 */
jzReg('layout', 'arcTop', {
    plan: function (rng, cut, st) {
        var port = cut.H > cut.W * 1.08;
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif', 'display'])), span: port ? rng.range(150, 190) : rng.range(105, 145), guide: rng.pick(['double', 'ticks', 'double']), under: rng.pick(['copy', 'romaji', 'no']) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), i, DEG = Math.PI / 180;
        var chars = jzChars(jzTrim(c.text)), n = Math.max(1, chars.length);
        var spanDeg = Math.min(jzP(ctx, 'span', 125), Math.max(50, n * 30));
        var half = spanDeg / 2 * DEG, dth = spanDeg * DEG / n, k = dth / 1.1;
        var wid = half < Math.PI / 2 ? 2 * Math.sin(half) : 2, hollow = half >= 70 * DEG;
        var R = Math.min(W * 0.86 / (wid + k), H * 0.66 / (1 - Math.cos(half) + k * 1.3 + (hollow ? 0 : 0.18)), u * 0.21 / k);
        var size = R * k, ls = jzSmallSize(ctx);
        var topOff = -(R + size * 0.8), endY = -R * Math.cos(half) + size * 0.55;
        var capY = hollow ? Math.max(0, -R * Math.cos(half) - R * 0.1) : endY + ls * 1.6;
        var botOff = Math.max(endY, capY + ls * 2.2), cx = W / 2, cy = H / 2 - (topOff + botOff) / 2;
        // the lyric along the arc, tops outward
        var gl = [], gp = [], rots = [], mis = [];
        for (i = 0; i < chars.length; i++) {
            if (chars[i] === ' ' || chars[i] === '\u3000') continue;
            var th = -90 + (i - (n - 1) / 2) * spanDeg / n;
            gl.push(chars[i]); gp.push([cx + Math.cos(th * DEG) * R, cy + Math.sin(th * DEG) * R]); rots.push(th + 90); mis.push(i);
        }
        var GL = la1_glyphItems(ctx, gl, gp, { size: size, font: jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), rots: rots });
        // guide arcs open symmetrically from the top; accent dots ride the outer arc's ends
        var HD = la1_H(ctx), E = 'var e=ioc(time/0.7)*K;', lw = Math.max(1.2 * ctx.u, size * 0.018);
        var a0 = -90 - spanDeg / 2 - 3, a1 = -90 + spanDeg / 2 + 3, hs = (a1 - a0) / 2, Ro = R + size * 0.74, Ri = R - size * 0.72;
        var G = jzShapeLayer(ctx, 'arc guides', cx, cy), deco = [G];
        var trimS = HD + E + '50*(1-e)', trimE = HD + E + '50*(1+e)';
        function arcG(name, r, op) { var g = jzGrp(G, name); la1_shape(g, la1_arc(0, 0, r, a0, a1)); jzAddStroke(g, sc.sub, lw, op); jzAddTrimPaths(g, trimE, trimS); }
        if (jzP(ctx, 'guide', 'double') === 'double') { arcG('outer', Ro, 90); arcG('inner', Ri, 60); }
        else {
            arcG('inner', Ri, 80);
            for (i = 0; i <= n; i++) {
                var tt = -90 + (i - n / 2) * spanDeg / n, co = Math.cos(tt * DEG), si = Math.sin(tt * DEG), r1 = Ri - size * (i % 2 ? 0.12 : 0.22);
                var gt = jzGrp(G, 'tick ' + i); jzAddPath(gt, [[co * Ri, si * Ri], [co * r1, si * r1]], false); jzAddStroke(gt, sc.sub, lw, 80);
                jzSetExpr(jzGX(gt).property('ADBE Vector Group Opacity'), HD + E + 'e>0&&' + jzN(Math.abs(tt + 90)) + '<=' + jzN(hs) + '*e+0.01?100:0');
            }
        }
        for (var d = -1; d <= 1; d += 2) {
            var gd = jzGrp(G, d < 0 ? 'dot L' : 'dot R'); jzAddEllipse(gd, size * 0.1, size * 0.1); jzAddFill(gd, sc.accent);
            jzSetExpr(jzGX(gd).property('ADBE Vector Position'), HD + E + 'var a=(-90+(' + d + ')*' + jzN(hs) + '*e)*Math.PI/180;[Math.cos(a)*' + jzN(Ro) + ',Math.sin(a)*' + jzN(Ro) + ']');
            jzSetExpr(jzGX(gd).property('ADBE Vector Group Opacity'), HD + E + 'e>0?100:0');
        }
        // caption under the arc + accent rule
        var under = jzP(ctx, 'under', 'copy');
        var copy = under === 'romaji' ? (jzRomajiOf(ctx) || jzAltCopy(ctx)) : under === 'no' ? 'No.' + jzLineNo(ctx) + '  \u2014  ' + jzFmtTime(c.start || 0) : jzAltCopy(ctx);
        var maxW = hollow ? 2 * (R - size) * 0.8 : W * 0.7, bf = jzBodyF(ctx);
        var cs = Math.min(ls * 1.15, la1_fit(la1_meas(ctx, copy, bf, 0.12), maxW, ls * 2));
        var CA = 'var ca=oc((time-0.25)/0.4)*K;';
        var C = jzText(ctx, copy, { font: bf, size: cs, color: sc.sub, x: cx, y: cy + capY, track: 0.12 });
        jzSetExpr(jzXf(C, 'ADBE Opacity'), HD + CA + 'value*ca');
        var rw = Math.min(maxW * 0.5, size * 1.4), Rl = jzRectLayer(ctx, 'caption rule', cx, cy + capY + cs * 1.1, rw, Math.max(2 * ctx.u, lw * 1.4), sc.accent);
        jzSetExpr(jzXf(Rl, 'ADBE Scale'), HD + CA + '[value[0]*ca,value[1]]');
        jzSetExpr(jzXf(Rl, 'ADBE Opacity'), HD + CA + 'value*ca');
        deco.push(C, Rl);
        la1_ng(deco);
        if (GL.length) la1_behind(deco, GL[0]);
        for (i = 0; i < GL.length; i++) jzAnimate(ctx, GL[i], { mi: mis[i] });
        return gp.length ? la1_ptsBox(gp, size / 2) : la1_box(cx - R, cy - R, cx + R, cy);
    }
});

/* ---------------------------------------------------------------- 6 spiral \u2014 \u87BA\u65CB */
jzReg('layout', 'spiral', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), dir: rng.pick([1, -1]), speed: rng.range(4, 9), guide: rng.chance(0.7), fill: cut.n <= 7 ? 'repeat' : cut.n <= 11 ? rng.pick(['repeat', 'single']) : 'single' };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), i, j, DEG = Math.PI / 180;
        var t = jzHasLatin(c.text) ? jzTrim(c.text).replace(/\s+/g, ' ') : jzStrip(c.text), chars = jzChars(t), n = Math.max(1, chars.length);
        var dir = jzP(ctx, 'dir', 1) < 0 ? -1 : 1, speed = jzP(ctx, 'speed', 6), fill = jzP(ctx, 'fill', 'single'), font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display'));
        var seq = [];
        for (i = 0; i < chars.length; i++) seq.push({ c: chars[i], main: true });
        if (fill === 'repeat') {
            var reps = Math.max(1, Math.ceil(20 / (n + 1)) - 1);
            for (var rr = 0; rr < reps && seq.length < 40; rr++) { seq.push({ c: '\u30FB', main: false }); for (i = 0; i < chars.length; i++) seq.push({ c: chars[i], main: false }); }
        }
        var N = seq.length, f = fill === 'repeat' ? 0.8 : 0.58, cc = Math.min(0.42, 0.92 * Math.sqrt(5.82 * f / N)), dth = cc * 1.08, TH = N * dth;
        var R0 = u * 0.45 / (1 + cc * 0.55);
        function rAt(th) { return R0 * (1 - f * th / TH); }
        var mid = dir > 0 ? -90 : 90, base = mid - dir * Math.min((n - 1) * dth / 2 / DEG, 70);
        // centre the glyph cloud (at rest = mid-cut) on screen
        var bx0 = 1e9, bx1 = -1e9, by0 = 1e9, by1 = -1e9;
        for (j = 0; j < N; j++) {
            var th0 = j * dth, r0 = rAt(th0), sz0 = cc * r0 * 0.5, p0 = (base + dir * th0 / DEG) * DEG, x0 = Math.cos(p0) * r0, y0 = Math.sin(p0) * r0;
            bx0 = Math.min(bx0, x0 - sz0); bx1 = Math.max(bx1, x0 + sz0); by0 = Math.min(by0, y0 - sz0); by1 = Math.max(by1, y0 + sz0);
        }
        var cx = W / 2 - (bx0 + bx1) / 2, cy = H / 2 - (by0 + by1) / 2;
        // pivot layer at the spiral centre: guide + centre dot; it turns slowly and carries the glyph layers (parented)
        var HD = la1_H(ctx), Pv = jzNoGhost(jzShapeLayer(ctx, 'spiral', cx, cy));   // guide + dot: main pass only (still the glyphs' parent in the ghosts)
        if (jzP(ctx, 'guide', true)) {
            var gg = jzGrp(Pv, 'guide'), gpts = [];
            for (var m = 0; m <= 100; m++) {
                var tg = (m / 100) * (TH - dth * 0.5), rg = rAt(tg) * (1 + cc * 0.62), pg = (base + dir * tg / DEG) * DEG;
                gpts.push([Math.cos(pg) * rg, Math.sin(pg) * rg]);
            }
            jzAddPath(gg, gpts, false); jzAddStroke(gg, sc.sub, Math.max(1.2 * ctx.u, u * 0.0016), 55);
            jzAddTrimPaths(gg, HD + '100*ioc(time/0.9)*K');
        }
        var gdot = jzGrp(Pv, 'centre'); jzAddEllipse(gdot, u * 0.016, u * 0.016); jzAddFill(gdot, sc.accent);
        jzSetExpr(jzGX(gdot).property('ADBE Vector Scale'), HD + 'var q=cl(time/0.35);var s=(q<=0?0:ob(q,2))*K*100;[s,s]');
        // glyphs: main = the lyric (one text layer); the repeats = faint copies running on towards the centre
        var sMax = cc * R0, mg = [], mp = [], mr = [], ms = [], mis = [], rg2 = [], rp = [], rro = [], rs = [], rj = [], ra = [];
        for (j = 0; j < N; j++) {
            var q = seq[j];
            if (q.c === ' ' || q.c === '\u3000') continue;
            var th = j * dth, r = rAt(th), ph = base + dir * th / DEG;
            var pt = [cx + Math.cos(ph * DEG) * r, cy + Math.sin(ph * DEG) * r], rot = dir > 0 ? ph + 90 : ph - 90;
            if (q.main) { mg.push(q.c); mp.push(pt); mr.push(rot); ms.push(r / R0); mis.push(j); }
            else { rg2.push(q.c); rp.push(pt); rro.push(rot); rs.push(r / R0); rj.push(j); ra.push(jzLerp(0.22, 0.7, r / R0)); }
        }
        var RL = null;
        if (rg2.length) {
            RL = la1_glyphLayer(ctx, rg2, rp, { size: sMax, font: font, color: sc.sub, rots: rro, scales: rs, name: 'spiral repeats' });
            jzAnimator(RL, 'JZ Fade', [['ADBE Text Opacity', 0]], HD + 'var J=' + jzArrExpr(rj) + ',A=' + jzArrExpr(ra) + ';var k=textIndex-1;var a=cl((time-0.15-((J[k]||0)-' + n + ')*0.025)/0.25)*K*(A[k]||0);100*(1-a)');
            RL.parent = Pv; jzNoGhost(RL);
        }
        var GL = la1_glyphItems(ctx, mg, mp, { size: sMax, font: font, rots: mr, scales: ms });
        for (i = 0; i < GL.length; i++) GL[i].parent = Pv;
        jzSetExpr(jzXf(Pv, 'ADBE Rotate Z'), 'value+(time-' + jzN(c.dur / 2) + ')*' + jzN(speed * dir));   // set after parenting (no parenting jump)
        for (i = 0; i < GL.length; i++) jzAnimate(ctx, GL[i], { mi: mis[i], noHold: true });
        return mp.length ? la1_ptsBox(mp, sMax / 2) : la1_box(cx - R0, cy - R0, cx + R0, cy + R0);
    }
});

// bar that grows from (x, y) to the right (dir 1) or left (dir -1): shape layer anchored at its growth origin
// alpha track matte M (a layer directly above L): AE 23+ API when present, else the legacy property
function la1_matte(L, M) {
    try { if (L.setTrackMatte) { L.setTrackMatte(M, TrackMatteType.ALPHA); return; } } catch (e) {}
    L.trackMatteType = TrackMatteType.ALPHA;
}
function la1_bar(ctx, name, x, y, w, h, col, dir) {
    var S = jzShapeLayer(ctx, name, x, y), g = jzGrp(S, name);
    jzAddRect(g, w, h, 0, (dir < 0 ? -1 : 1) * w / 2, 0); jzAddFill(g, col);
    return S;
}
// several polylines drawn one after another as progress e (an expression variable) runs 0..1 (browser segsPartial)
function la1_segs(S, segs, col, lw, op, HD, E) {
    var lens = [], tot = 0, i, j;
    for (i = 0; i < segs.length; i++) { var l = 0; for (j = 1; j < segs[i].length; j++) l += la1_len(segs[i][j - 1], segs[i][j]); lens.push(Math.max(1e-3, l)); tot += lens[i]; }
    var acc = 0;
    for (i = 0; i < segs.length; i++) {
        var g = jzGrp(S, 'seg ' + (i + 1)); jzAddPath(g, segs[i], false); jzAddStroke(g, col, lw, op);
        jzAddTrimPaths(g, HD + E + '100*cl((e*' + jzN(tot) + '-' + jzN(acc) + ')/' + jzN(lens[i]) + ')');
        acc += lens[i];
    }
}
// rough advance of a string in em (for choosing line breaks before anything is measured)
function la1_estW(t, tr) {
    var a = jzChars(t), w = 0;
    for (var i = 0; i < a.length; i++) w += /[A-Za-z0-9]/.test(a[i]) ? 0.58 : (a[i] === ' ' ? 0.3 : 1);
    return w + Math.max(0, a.length - 1) * (tr || 0);
}

/* ---------------------------------------------------------------- 7 gridCells \u2014 \u5347\u76EE */
jzReg('layout', 'gridCells', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), gap: rng.pick([0, 0, 0.1, 0.16]), acc: rng.int(0, 99), fill: rng.pick(['outline', 'outline', 'ink']), nums: rng.chance(0.6) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), i;
        var chars = jzChars(jzStrip(c.text)), n = Math.max(1, chars.length);
        var maxCols = port ? (n <= 8 ? 4 : 5) : (n <= 10 ? 10 : 8);
        var rows = Math.ceil(n / Math.min(n, maxCols)), cols = Math.ceil(n / rows), g = jzP(ctx, 'gap', 0) || 0;
        var cell = Math.min(W * 0.86 / (cols + (cols - 1) * g), H * 0.72 / (rows + (rows - 1) * g), u * 0.3), stp = cell * (1 + g);
        var gw = cols * cell + (cols - 1) * cell * g, gh = rows * cell + (rows - 1) * cell * g, x0 = W / 2 - gw / 2, y0 = H / 2 - gh / 2;
        var kan = [], ok = [];
        for (i = 0; i < chars.length; i++) { if (jzIsKanji(chars[i])) kan.push(i); if (!la1_isPunct(chars[i]) && !la1_isSmall(chars[i])) ok.push(i); }
        var acc = Math.abs(jzP(ctx, 'acc', 0) | 0), ai = kan.length ? kan[acc % kan.length] : ok.length ? ok[acc % ok.length] : acc % n;
        var plate = jzP(ctx, 'fill', 'outline') === 'ink', lw = Math.max(1.5 * ctx.u, cell * 0.012), HD = la1_H(ctx), total = rows * cols;
        function cx(j) { return x0 + (j % cols) * stp; }
        function cy(j) { return y0 + Math.floor(j / cols) * stp; }
        // plates (accent cell always; every lyric cell when fill = ink) pop in from their centres
        // (the accent plate is ghosted in the browser, the ink plates are not: separate layers)
        var PA = jzShapeLayer(ctx, 'accent plate', 0, 0), PL = plate ? jzNoGhost(jzShapeLayer(ctx, 'cell plates', 0, 0)) : null;
        for (i = 0; i < total; i++) {
            if (!(i === ai || (plate && i < n))) continue;
            var gp = jzGrp(i === ai ? PA : PL, 'plate ' + (i + 1)); jzAddRect(gp, cell, cell); jzAddFill(gp, i === ai ? sc.accent : sc.ink);
            jzGX(gp).property('ADBE Vector Position').setValue([cx(i) + cell / 2, cy(i) + cell / 2]);
            jzSetExpr(jzGX(gp).property('ADBE Vector Scale'), HD + 'var q=cl((time-' + jzN(i * 0.03 + 0.08) + ')/0.28);var s=(q<=0?0:ob(q,1.6))*K*100;[s,s]');
        }
        // cell outlines draw themselves one after another (empty cells faint)
        var OL = jzNoGhost(jzShapeLayer(ctx, 'cells', 0, 0));
        for (i = 0; i < total; i++) {
            var X = cx(i), Y = cy(i), go = jzGrp(OL, 'cell ' + (i + 1)), empty = i >= n;
            jzAddPath(go, [[X, Y], [X + cell, Y], [X + cell, Y + cell], [X, Y + cell], [X, Y]], false);
            jzAddStroke(go, empty ? sc.sub : sc.fg, lw, empty ? 35 : 90);
            jzAddTrimPaths(go, HD + '100*ioc((time-' + jzN(i * 0.03) + ')/0.35)*K');
        }
        // cell numbers: one text layer, a line per number, each moved to its cell's corner
        if (jzP(ctx, 'nums', false)) {
            var ns = Math.max(10 * ctx.u, cell * 0.1), lead = ns * 1.2, nl = [], offs = [], flags = [];
            for (i = 0; i < chars.length; i++) {
                var tg = jzPad(i + 1, 2), tgc = jzChars(tg);
                nl.push(tg);
                for (var q = 0; q < tgc.length; q++) { offs.push([cx(i) + ns * 0.6, cy(i) + ns * 1.1 + ns * 0.36 - i * lead]); flags.push(i === ai); }
            }
            var NL = jzNoGhost(jzText(ctx, nl.join('\r'), { font: jzMonoF(ctx), size: ns, color: plate ? jzOnCol(sc, sc.ink) : sc.sub, x: 0, y: 0, align: 'left', leading: lead, opacity: 0.85, name: 'cell numbers' }));
            jzXf(NL, 'ADBE Anchor Point').setValue([0, 0]); jzXf(NL, 'ADBE Position').setValue([0, 0]);
            jzCharOffsets(NL, offs, 'JZ Place');
            jzCharColors(NL, jzOnCol(sc, sc.accent), flags, 'JZ Accent');
            jzAnimator(NL, 'JZ Fade', [['ADBE Text Opacity', 0]], HD + 'var i=Math.floor((textIndex-1)/2);var e=ioc((time-i*0.03)/0.35)*K;100*(1-cl(e*1.5-0.5))');
        }
        // the lyric: a glyph centred in each cell
        var gl = [], gpts = [], cols2 = [];
        for (i = 0; i < chars.length; i++) { gl.push(chars[i]); gpts.push([cx(i) + cell / 2, cy(i) + cell / 2 + cell * 0.02]); cols2.push(i === ai ? jzOnCol(sc, sc.accent) : plate ? jzOnCol(sc, sc.ink) : sc.fg); }
        var GL = la1_glyphItems(ctx, gl, gpts, { size: cell * 0.64, font: jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), colors: cols2 });
        for (i = 0; i < GL.length; i++) jzAnimate(ctx, GL[i], { mi: i });
        return la1_box(x0, y0, x0 + gw, y0 + gh);
    }
});

/* ---------------------------------------------------------------- 8 dropCap \u2014 \u5927\u304D\u306A\u982D\u6587\u5B57 */
jzReg('layout', 'dropCap', {
    plan: function (rng, cut, st) {
        return { capFont: rng.pick(jzFontsOf(st, ['serif', 'display'])), font: rng.pick(jzFontsOf(st, ['serif', 'body', 'display'])), cap: rng.pick(['fill', 'accent', 'outline']), rules: rng.chance(0.65), meta: rng.chance(0.7) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), i, j;
        var arr = jzChars(jzTrim(c.text)), cap = arr[0] || '', rest = jzTrim(arr.slice(1).join('')), nR = jzCount(rest);
        var font = jzP(ctx, 'font', jzSerifF(ctx)), capFont = jzP(ctx, 'capFont', jzSerifF(ctx));
        var colW = W * (port ? 0.84 : 0.66), lead = 1.3, tr = 0.04;
        var capAdv = Math.max(la1_estW(cap || '\u3000'), la1_meas(ctx, cap || '\u3000', capFont).w / 100);
        // choose line breaks + size: every candidate split gets its largest fitting size (browser algorithm, estimated advances)
        var nat = {}, ck = jzChunk(rest), accn = 0;
        for (i = 0; i < ck.length; i++) { accn += jzCount(ck[i]); nat[accn] = 1; }
        var best = null, bs = -1, seen = {};
        for (var m = 1; m <= Math.max(1, nR); m++) {
            var tt = jzSplitLines(rest, m);
            if (seen[tt]) continue;
            seen[tt] = 1;
            var raw = tt.split('\r'), lines = [];
            for (i = 0; i < raw.length; i++) if (jzTrim(raw[i])) lines.push(jzTrim(raw[i]));
            if (!lines.length) continue;
            var qq = 1, cum = 0, few = false;
            for (i = 0; i < lines.length; i++) { cum += jzCount(lines[i]); if (i < lines.length - 1 && !nat[cum] && !jzHasLatin(lines[i])) qq *= 0.82; if (jzCount(lines[i]) < 2) few = true; }
            if (nR >= 4 && lines.length < 2) continue;
            if (nR >= 4 && few) continue;
            if (nR <= 3 && lines.length > 1) continue;
            var cls = nR <= 10 ? [2] : [3, 2];
            for (j = 0; j < cls.length; j++) {
                var cl = cls[j], kc = ((cl - 1) * lead + 1) / 0.86, nl0 = Math.max(lines.length, cl);
                var sm = Math.min(u * 0.12, H * 0.62 / ((nl0 - 1) * lead + 1));
                for (i = 0; i < lines.length; i++) sm = Math.min(sm, i < cl ? colW / (la1_estW(lines[i], tr) + capAdv * kc + 0.5) : colW / la1_estW(lines[i], tr));
                var score = sm * qq * (1 - 0.1 * Math.max(0, lines.length - cl));
                if (score > bs) { bs = score; best = { s: sm, cl: cl, lines: lines, capS: sm * kc, gap: sm * 0.5 }; }
            }
        }
        if (!best) best = { s: u * 0.06, cl: 2, lines: rest ? [rest] : [], capS: u * 0.06 * 2.67, gap: u * 0.03 };
        var s = best.s, capLines = best.cl, capS = best.capS, gap = best.gap, LN = best.lines, nl = LN.length;
        // the lines (measured for real), shrunk if the estimate was optimistic
        var LL = [], wl = [];
        for (i = 0; i < nl; i++) { var Li = la1_T(ctx, LN[i], font, s, { align: 'left', track: tr }); LL.push(Li); wl.push(jzSize(Li)[0]); }
        var capW = capAdv * capS, usedW = capW + gap + s;
        for (i = 0; i < nl; i++) usedW = Math.max(usedW, (i < capLines ? capW + gap : 0) + wl[i]);
        if (usedW > colW * 1.02) {
            var kk = colW / usedW;
            s *= kk; capS *= kk; capW *= kk; gap *= kk; usedW *= kk;
            for (i = 0; i < nl; i++) { (function (L0) { jzTextDoc(L0, function (td) { td.fontSize = s; }); jzAnchor(L0, 'left'); })(LL[i]); wl[i] *= kk; }
        }
        var besideOnly = nl <= capLines, hBlock = (Math.max(nl, capLines) - 1) * lead * s + s, meta = !!jzP(ctx, 'meta', false);
        var x0 = W / 2 - usedW / 2, top = H / 2 - hBlock / 2 - (meta ? s * 0.3 : 0);
        var capBand = ((capLines - 1) * lead + 1) * s, capCy = top + capBand / 2;
        function lineY(k) { return besideOnly ? capCy + (k - (nl - 1) / 2) * lead * s * (nl < capLines ? 1.15 : 1) : top + s / 2 + k * lead * s; }
        for (i = 0; i < nl; i++) jzXf(LL[i], 'ADBE Position').setValue([i < capLines ? x0 + capW + gap : x0, lineY(i)]);
        // the initial
        var cst = jzP(ctx, 'cap', 'fill'), co = { font: capFont, size: capS, color: cst === 'accent' ? sc.accent : sc.fg, x: x0 + capW / 2, y: capCy + capS * 0.02 };
        if (cst === 'outline') { co.fill = false; co.stroke = Math.max(2 * ctx.u, capS * 0.014); co.strokeColor = sc.fg; }
        var CL = cap ? jzText(ctx, cap, co) : null;
        // rules above / below (draw in from opposite ends) + meta line
        var HD = la1_H(ctx), yT = top - s * 0.45, yB = top + hBlock + s * 0.45, deco = [];
        if (jzP(ctx, 'rules', false)) {
            var lw = Math.max(1.5 * ctx.u, s * 0.02), E = 'var e=ioc((time-0.05)/0.6)*K;';
            var R1 = la1_bar(ctx, 'rule top', x0, yT, usedW, lw, sc.fg, 1), R2 = la1_bar(ctx, 'rule bottom', x0 + usedW, yB, usedW, lw, sc.fg, -1);
            jzSetExpr(jzXf(R1, 'ADBE Scale'), HD + E + '[value[0]*e,value[1]]'); jzSetExpr(jzXf(R2, 'ADBE Scale'), HD + E + '[value[0]*e,value[1]]');
            jzXf(R1, 'ADBE Opacity').setValue(90); jzXf(R2, 'ADBE Opacity').setValue(90);
            deco.push(R1, R2);
        }
        if (meta) {
            var ls = jzSmallSize(ctx) * 0.9, yM = yB + ls * 1.3, A = 'oc((time-0.3)/0.4)*K';
            var M1 = jzText(ctx, 'No.' + jzLineNo(ctx), { font: jzMonoF(ctx), size: ls, color: sc.accent, x: x0, y: yM, align: 'left', track: 0.1 });
            var M2 = jzText(ctx, jzRomajiOf(ctx) || jzAltCopy(ctx), { font: jzMonoF(ctx), size: ls, color: sc.sub, x: x0 + usedW, y: yM, align: 'right', track: 0.1 });
            la1_opx(ctx, M1, A); la1_opx(ctx, M2, A);
            deco.push(M1, M2);
        }
        if (deco.length) la1_behind(la1_ng(deco), CL || LL[0]);
        var bb = null;
        if (CL) { jzAnimate(ctx, CL, { mi: 0 }); bb = la1_bb(CL); }
        for (i = 0; i < nl; i++) { jzAnimate(ctx, LL[i], { mi: 2 + i * 2 }); bb = jzUnion(bb, la1_bb(LL[i])); }
        return bb || la1_box(x0, top, x0 + usedW, top + hBlock);
    }
});

/* ---------------------------------------------------------------- 9 justified \u2014 \u7248\u9762 */
jzReg('layout', 'justified', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), fillFont: rng.pick(jzFontsOf(st, ['body', 'serif'])), mark: rng.pick(['band', 'under', 'bracket']), pos: rng.pick([0.28, 0.5, 0.66]), dens: rng.range(0.062, 0.078) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), i;
        var text = jzTrim(c.text), n = jzCount(text), font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), fillFont = jzP(ctx, 'fillFont', jzBodyF(ctx));
        var bx0 = W * 0.08, bw = W * 0.84, by0 = H * 0.09, bh = H * 0.82;
        var rowsN = Math.max(8, Math.round(bh / (u * (jzP(ctx, 'dens', 0.07) || 0.07)))), rowH = bh / rowsN, fs = rowH * 0.6;
        // lyric: one line set to the full measure, or two lines (left / right)
        var capH = rowH * (n <= 5 ? 3.4 : 2.6), mT = la1_meas(ctx, text, font, 0.04);
        function fitL(m0, hmax) { return Math.min(hmax, la1_fit(m0, bw, hmax)); }
        var lines = [text];
        if (fitL(mT, capH) < rowH * 1.6 && n >= 4) lines = la1_splitK(text, 2);
        var two = lines.length > 1, sizes = [], lyH = 0;
        if (two) { var s2 = 1e9; for (i = 0; i < lines.length; i++) s2 = Math.min(s2, fitL(la1_meas(ctx, lines[i], font, 0.04), rowH * 2.4)); for (i = 0; i < lines.length; i++) sizes.push(s2); }
        else sizes.push(fitL(mT, capH));
        for (i = 0; i < sizes.length; i++) lyH += sizes[i];
        lyH += (lines.length - 1) * rowH * 0.25;
        var span = Math.ceil(lyH / rowH + 0.8), r0 = Math.round(jzP(ctx, 'pos', 0.5) * (rowsN - span)), HD = la1_H(ctx);
        // filler: the whole line set solid, running on through the rows (one text layer, rows fade in top to bottom)
        var src = jzChars(jzFlat(c.lineText || text) + (jzHasLatin(text) ? ' / ' : '\u3002'));
        var cpr = Math.max(4, Math.floor(bw / (fs * 1.06))), off = 0, rowsT = [], row0 = null;
        for (var r = 0; r < rowsN; r++) {
            if (r >= r0 && r < r0 + span) { rowsT.push(''); continue; }
            var row = '';
            for (var j = 0; j < cpr; j++) row += src[(off + j) % src.length];
            off += cpr; rowsT.push(row);
            if (row0 === null) row0 = row;
        }
        if (row0 !== null) {
            var wr = la1_meas(ctx, row0, fillFont, 0).w * fs / 100, sp = (bw - wr) / Math.max(1, cpr - 1);
            var F = jzNoGhost(jzText(ctx, rowsT.join('\r'), { font: fillFont, size: fs, color: sc.sub, x: 0, y: 0, align: 'left', track: sp / fs, leading: rowH, name: 'filler' }));
            jzXf(F, 'ADBE Anchor Point').setValue([0, 0]); jzXf(F, 'ADBE Position').setValue([bx0, by0 + rowH * 0.5 + fs * 0.36]);
            jzAnimator(F, 'JZ Rows', [['ADBE Text Opacity', 0]], HD + 'var ri=Math.floor((textIndex-1)/' + cpr + ');var r=ri<' + r0 + '?ri:ri+' + span + ';100*(1-cl((time-r*0.018)/0.2))');
            jzXf(F, 'ADBE Opacity').setValue(34);
            la1_opx(ctx, F, 'K');
        }
        // mark around the lyric
        var yc = by0 + (r0 + span / 2) * rowH, mx = sizes[0], mark = jzP(ctx, 'mark', 'band'), plate = mark === 'band', E = 'var e=ioe((time-0.05)/0.5)*K;';
        if (plate) {
            var B = jzNoGhost(la1_bar(ctx, 'band', bx0 - mx * 0.2, yc, bw + mx * 0.4, lyH + mx * 0.4, sc.accent, 1));
            jzSetExpr(jzXf(B, 'ADBE Scale'), HD + E + '[value[0]*e,value[1]]');
        } else if (mark === 'under') {
            var lw = Math.max(3 * ctx.u, mx * 0.05);
            var U1 = la1_bar(ctx, 'rule under', bx0, yc + lyH / 2 + mx * 0.2 + lw / 2, bw, lw, sc.accent, 1);
            var U2 = la1_bar(ctx, 'rule over', bx0 + bw, yc - lyH / 2 - mx * 0.2 - lw / 2, bw, lw, sc.accent, -1);
            jzSetExpr(jzXf(U1, 'ADBE Scale'), HD + E + '[value[0]*e,value[1]]'); jzSetExpr(jzXf(U2, 'ADBE Scale'), HD + E + '[value[0]*e,value[1]]');
        } else {
            var aL = mx * 0.45, bl = Math.max(2 * ctx.u, mx * 0.04), yt = yc - lyH / 2 - mx * 0.16, yb = yc + lyH / 2 + mx * 0.16, g = mx * 0.14;
            var BR = jzNoGhost(jzShapeLayer(ctx, 'brackets', 0, 0));
            for (var sd = -1; sd <= 1; sd += 2) {
                var Xb = sd < 0 ? bx0 - g : bx0 + bw + g, ins = -sd;
                var gv = jzGrp(BR, 'bracket stem'); jzAddPath(gv, [[Xb, yt], [Xb, yb]], false); jzAddStroke(gv, sc.accent, bl);
                jzSetExpr(jzGX(gv).property('ADBE Vector Group Opacity'), HD + E + 'e>0?100:0');
                var arms = [yt, yb];
                for (var a = 0; a < 2; a++) {
                    var ga = jzGrp(BR, 'bracket arm'); jzAddPath(ga, [[Xb, arms[a]], [Xb + ins * aL, arms[a]]], false); jzAddStroke(ga, sc.accent, bl);
                    jzAddTrimPaths(ga, HD + E + '100*e');
                }
            }
        }
        // small print: line number / grid size
        var ms = Math.max(11 * ctx.u, rowH * 0.28), FA = 'oc((time-0.1)/0.4)*K';
        la1_opx(ctx, jzNoGhost(jzText(ctx, 'No.' + jzLineNo(ctx), { font: jzMonoF(ctx), size: ms, color: sc.sub, x: bx0, y: by0 - rowH * 0.45, align: 'left' })), FA);
        la1_opx(ctx, jzNoGhost(jzText(ctx, rowsN + ' \u00D7 ' + cpr, { font: jzMonoF(ctx), size: ms, color: sc.sub, x: bx0 + bw, y: by0 + bh + rowH * 0.45, align: 'right' })), FA);
        // the lyric
        var col = plate ? jzOnCol(sc, sc.accent) : sc.fg, y = yc - lyH / 2, bb = null;
        for (i = 0; i < lines.length; i++) {
            var ls = sizes[i], nG = jzChars(lines[i]).length, L;
            y += ls / 2;
            if (two) L = la1_T(ctx, lines[i], font, ls, { align: i === 0 ? 'left' : 'right', x: i === 0 ? bx0 : bx0 + bw, y: y, track: 0.04, color: col });
            else if (nG > 1) {
                var w0 = la1_meas(ctx, lines[i], font, 0).w * ls / 100;
                L = la1_T(ctx, lines[i], font, ls, { align: 'left', x: bx0, y: y, track: (bw - w0) / (ls * (nG - 1)), color: col });
            } else L = la1_T(ctx, lines[i], font, ls, { x: bx0 + bw / 2, y: y, color: col });
            jzAnimate(ctx, L, { mi: i * 3 });
            bb = jzUnion(bb, la1_bb(L));
            y += ls / 2 + rowH * 0.25;
        }
        return bb || la1_box(bx0, yc - lyH / 2, bx0 + bw, yc + lyH / 2);
    }
});

/* ---------------------------------------------------------------- 10 frameBox \u2014 \u984D\u7E01 */
jzReg('layout', 'frameBox', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif', 'serif'])), style: rng.pick(['full', 'double', 'corners']), caps: rng.pick(['tl-br', 'top-bottom']), shape: rng.pick(['tight', 'wide']) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), i;
        var text = la1_brk(jzTrim(c.text), port ? 6 : 12), font = jzP(ctx, 'font', jzSerifF(ctx));
        var mm = la1_meas(ctx, text, font, 0.04, 1.2), size = Math.min(la1_fit(mm, W * (port ? 0.74 : 0.68), H * 0.36), u * 0.19);
        var L = la1_T(ctx, text, font, size, { x: W / 2, y: H / 2, track: 0.04, lead: 1.2 });
        var sz = jzSize(L), fw = sz[0] + size * 1.6, fh = sz[1] + size * 1.4;
        if (jzP(ctx, 'shape', 'tight') === 'wide') { fw = Math.max(fw, W * (port ? 0.82 : 0.62)); fh = Math.max(fh, fw * (port ? 0.9 : 0.5)); }
        fw = Math.min(fw, W * 0.9); fh = Math.min(fh, H * 0.86);
        var x0 = W / 2 - fw / 2, y0 = H / 2 - fh / 2, x1 = x0 + fw, y1 = y0 + fh;
        var ls = jzSmallSize(ctx) * 0.9, gp = ls * 0.7, mono = jzMonoF(ctx);
        var c1 = 'No.' + jzLineNo(ctx), c2 = jzRomajiOf(ctx) || jzFmtTime(c.start || 0);
        var w1 = la1_meas(ctx, c1, mono, 0.12).w * ls / 100, w2 = la1_meas(ctx, c2, mono, 0.12).w * ls / 100;
        var tb = jzP(ctx, 'caps', 'tl-br') === 'top-bottom', a1 = tb ? W / 2 - w1 / 2 : x0 + ls * 2.2, b2 = tb ? W / 2 + w2 / 2 : x1 - ls * 2.2;
        var HD = la1_H(ctx), E = 'var e=ioc(time/0.85)*K;', lw = Math.max(2 * ctx.u, size * 0.02), style = jzP(ctx, 'style', 'full');
        var S = jzShapeLayer(ctx, 'frame', 0, 0), deco = [S];
        if (style === 'corners') {
            var arm = Math.min(fw, fh) * 0.22, cs = [[x0, y0, 1, 1], [x1, y0, -1, 1], [x1, y1, -1, -1], [x0, y1, 1, -1]];
            for (i = 0; i < 4; i++) {
                var X = cs[i][0], Y = cs[i][1], gc = jzGrp(S, 'corner ' + (i + 1));
                jzAddPath(gc, [[X + cs[i][2] * arm, Y], [X, Y], [X, Y + cs[i][3] * arm]], false); jzAddStroke(gc, sc.fg, lw * 1.4);
                jzAddTrimPaths(gc, HD + E + '50*(1+e)', HD + E + '50*(1-e)');
            }
        } else {
            // the frame line runs round from the top-left, leaving gaps for the two captions
            la1_segs(S, [[[x0, y0], [a1 - gp, y0]], [[a1 + w1 + gp, y0], [x1, y0], [x1, y1], [b2 + gp, y1]], [[b2 - w2 - gp, y1], [x0, y1], [x0, y0]]], sc.fg, lw, 100, HD, E);
            if (style === 'double') {
                var d = size * 0.16, gi = jzGrp(S, 'inner');
                jzAddPath(gi, [[x0 + d, y0 + d], [x1 - d, y0 + d], [x1 - d, y1 - d], [x0 + d, y1 - d], [x0 + d, y0 + d]], false);
                jzAddStroke(gi, sc.sub, Math.max(1 * ctx.u, lw * 0.5), 80);
                jzAddTrimPaths(gi, HD + E + '100*cl(e*1.15-0.15)');
            }
        }
        var CA = 'cl(e*2-0.6)';
        var C1 = jzText(ctx, c1, { font: mono, size: ls, color: sc.accent, x: a1, y: y0, align: 'left', track: 0.12 });
        var C2 = jzText(ctx, c2, { font: mono, size: ls, color: sc.sub, x: b2, y: y1, align: 'right', track: 0.12 });
        jzSetExpr(jzXf(C1, 'ADBE Opacity'), HD + E + 'value*' + CA); jzSetExpr(jzXf(C2, 'ADBE Opacity'), HD + E + 'value*' + CA);
        deco.push(C1, C2);
        la1_behind(la1_ng(deco), L);
        jzAnimate(ctx, L, { mi: 0 });
        return la1_bb(L);
    }
});

/* ---------------------------------------------------------------- 11 bubble \u2014 \u5439\u304D\u51FA\u3057 */
// speech-bubble outline (browser bubblePoly): rounded box or ellipse with a tail towards tailSide; returns { pts, tip }
function la1_bubblePoly(shape, cx, cy, w, h, size, tailSide) {
    var x0 = cx - w / 2, y0 = cy - h / 2, x1 = cx + w / 2, y1 = cy + h / 2, tipX = cx + tailSide * w * 0.36, tipY = y1 + size * 0.75, pts = [], i, DEG = Math.PI / 180;
    if (shape === 'ellipse') {
        var rx = w / 2, ry = h / 2, M = 56, ta = (tailSide > 0 ? 62 : 118) * DEG, dA = 0.16, done = false;
        for (i = 0; i < M; i++) {
            var a = i / M * Math.PI * 2;
            if (Math.abs(a - ta) < dA) { if (Math.abs(a - ta) < Math.PI / M + 1e-6 || (!done && a > ta)) { pts.push([tipX, tipY]); done = true; } continue; }
            pts.push([cx + Math.cos(a) * rx, cy + Math.sin(a) * ry]);
        }
        if (!done) pts.push([tipX, tipY]);
        return { pts: pts, tip: [tipX, tipY] };
    }
    var r = Math.min(h * 0.42, size * 0.9), seg = 6;
    function corner(ccx, ccy, a0) { for (var k = 0; k <= seg; k++) { var an = (a0 + 90 * k / seg) * DEG; pts.push([ccx + Math.cos(an) * r, ccy + Math.sin(an) * r]); } }
    corner(x1 - r, y0 + r, -90); corner(x1 - r, y1 - r, 0);
    var bw = Math.min(size * 0.55, w * 0.18), bc = cx + tailSide * w * 0.2;
    pts.push([bc + bw / 2, y1], [tipX, tipY], [bc - bw / 2, y1]);
    corner(x0 + r, y1 - r, 90); corner(x0 + r, y0 + r, 180);
    return { pts: pts, tip: [tipX, tipY] };
}
// smooth closed curve through the midpoints of a polygon (browser env.blob: quadratic curves), as a bezier Shape
function la1_blob(pts) {
    var n = pts.length, V = [], I = [], O = [];
    for (var i = 0; i < n; i++) {
        var p = pts[i], q = pts[(i + 1) % n], m = [(p[0] + q[0]) / 2, (p[1] + q[1]) / 2];
        V.push(m); I.push([(p[0] - m[0]) * 2 / 3, (p[1] - m[1]) * 2 / 3]); O.push([(q[0] - m[0]) * 2 / 3, (q[1] - m[1]) * 2 / 3]);
    }
    var sh = new Shape(); sh.vertices = V; sh.inTangents = I; sh.outTangents = O; sh.closed = true;
    return sh;
}
jzReg('layout', 'bubble', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'body'])), shape: rng.pick(['round', 'ellipse', 'thought', 'round']), style: rng.pick(['fill', 'outline', 'fill']), tail: rng.pick([1, -1]), off: rng.range(-0.05, 0.05), tilt: rng.range(-3, 3), burst: rng.chance(0.6) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), i, DEG = Math.PI / 180;
        var shape = jzP(ctx, 'shape', 'round'), ell = shape !== 'round', t0 = jzTrim(c.text), n0 = jzCount(t0);
        var text = ell && n0 >= 5 && !port ? la1_brk(t0, Math.max(3, Math.ceil(n0 / 2))) : la1_brk(t0, port ? 6 : 10);
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display'));
        var mm = la1_meas(ctx, text, font, 0.02, 1.2), size = Math.min(la1_fit(mm, W * (port ? 0.62 : ell ? 0.5 : 0.6), H * 0.32), u * 0.15);
        var mw = mm.w * size / 100, mh = mm.h * size / 100;
        var w = mw * (ell ? 1.34 : 1) + size * (ell ? 1.1 : 1.2), h = mh * (ell ? 1.3 : 1) + size * (ell ? 1.0 : 0.95);
        var tail = jzP(ctx, 'tail', 1) < 0 ? -1 : 1, cx = W / 2 + jzP(ctx, 'off', 0) * W, cy = H / 2 - size * 0.3;
        var fill = jzP(ctx, 'style', 'fill') === 'fill' || shape === 'thought', plate = sc.ink, tcol = fill ? jzOnCol(sc, plate) : sc.fg;
        var B = la1_bubblePoly(shape === 'thought' ? 'ellipse' : shape, cx, cy, w, h, size, tail), pv = shape === 'thought' ? [cx, cy] : B.tip;
        // the balloon: one shape layer pivoting on the tail tip (thought: the centre); pops in with a small twist
        var S = jzNoGhost(jzShapeLayer(ctx, 'bubble', pv[0], pv[1]));   // main pass only in the browser (still the lyric's parent in the ghosts)
        jzXf(S, 'ADBE Anchor Point').setValue([pv[0], pv[1]]);
        var gb = jzGrp(S, 'balloon');
        if (shape === 'thought') {
            var bp = [];
            for (i = 0; i < 22; i++) { var a = i / 22 * Math.PI * 2, k = i % 2 ? 1.08 : 0.97; bp.push([cx + Math.cos(a) * w / 2 * k, cy + Math.sin(a) * h / 2 * k]); }
            la1_shape(gb, la1_blob(bp)); jzAddFill(gb, plate);
            var tx = cx + tail * w * 0.34, ty = cy + h / 2, dots = [[0.45, 0.2], [0.85, 0.12], [1.15, 0.07]];
            for (i = 0; i < 3; i++) { var gd = jzGrp(S, 'puff ' + (i + 1)); jzAddEllipse(gd, size * dots[i][1] * 2.8, size * dots[i][1] * 2.8, tx + tail * size * dots[i][0] * 0.8, ty + size * dots[i][0] * 0.75); jzAddFill(gd, plate); }
        } else {
            jzAddPath(gb, B.pts, true);
            if (fill) jzAddFill(gb, plate);
            else { var sk = jzAddStroke(gb, sc.fg, Math.max(3 * ctx.u, size * 0.05)); try { sk.property('ADBE Vector Stroke Line Join').setValue(2); } catch (e0) {} }
        }
        // the lyric rides inside the balloon (parented, so it pops and twists with it)
        var L = la1_T(ctx, text, font, size, { x: cx, y: cy + size * 0.02, track: 0.02, lead: 1.2, color: tcol }), bb = la1_bb(L);
        L.parent = S;
        var HD = la1_H(ctx), Q = 'var q=cl(time/0.32);q=(q<=0?0:ob(q,1.7))*(1-ic(cl(PO*1.2)));';
        jzSetExpr(jzXf(S, 'ADBE Scale'), HD + Q + '[value[0]*q,value[1]*q]');
        jzSetExpr(jzXf(S, 'ADBE Rotate Z'), HD + Q + 'value+' + jzN(jzP(ctx, 'tilt', 0)) + '*(1-q)*3');
        jzAnimate(ctx, L, { mi: 0 });
        // emphasis strokes at the top corner away from the tail
        if (jzP(ctx, 'burst', false)) {
            var bx = cx - tail * (w / 2 + size * 0.05), by = cy - h / 2 - size * 0.05, r0 = size * 0.25, Lm = size * 0.45, BS = jzNoGhost(jzShapeLayer(ctx, 'burst', 0, 0)), dd = [-30, 0, 30];
            for (i = 0; i < 3; i++) {
                var ang = (-90 - tail * 45 + dd[i] * 0.9) * DEG, r1 = r0 + Lm * (i === 1 ? 1.2 : 0.9), gs = jzGrp(BS, 'stroke ' + (i + 1));
                jzAddPath(gs, [[bx + Math.cos(ang) * r0, by + Math.sin(ang) * r0], [bx + Math.cos(ang) * r1, by + Math.sin(ang) * r1]], false);
                var st2 = jzAddStroke(gs, sc.accent, Math.max(3 * ctx.u, size * 0.05)); try { st2.property('ADBE Vector Stroke Line Cap').setValue(2); } catch (e1) {}
                jzAddTrimPaths(gs, HD + '100*oe(cl((time-0.2)/0.2)*K)');
            }
        }
        return bb;
    }
});

/* ---------------------------------------------------------------- 12 subtitleBar \u2014 \u5B57\u5E55\u5E2F */
jzReg('layout', 'subtitleBar', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['body', 'serif'])), bigFont: rng.pick(jzFontsOf(st, ['display', 'serif'])), big: rng.pick(['dim', 'outline']), bar: rng.range(0.1, 0.13), tc: rng.pick(['rec', 'scene']), place: rng.pick(['bar', 'band']), drift: rng.pick([1, -1]) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), i;
        var text = jzTrim(c.text), cand = [sc.bg, sc.fg, sc.ink, sc.dim], barC = sc.bg;
        for (i = 0; i < cand.length; i++) if (cand[i] && jzLum(cand[i]) < jzLum(barC)) barC = cand[i];
        var pic = barC === sc.bg && !!sc.dim && Math.abs(jzLum(sc.dim) - jzLum(sc.bg)) > 0.03;   // dark scheme: lift the picture area instead
        var bh = H * (port ? jzP(ctx, 'bar', 0.115) * 0.62 : jzP(ctx, 'bar', 0.115)), HD = la1_H(ctx), E = 'var e=oe(time/0.55)*K;';
        if (pic) {
            var PR = jzNoGhost(jzRectLayer(ctx, 'picture', W / 2, H / 2, W + 4, H, sc.dim));
            jzSetExpr(jzXf(PR, 'ADBE Scale'), HD + E + '[value[0],value[1]*(1-2*' + jzN(bh) + '*e/' + jzN(H) + ')]');
            jzSetExpr(jzXf(PR, 'ADBE Opacity'), HD + E + 'value*cl(e*1.4)');
        }
        // huge faint copy behind, drifting a little
        var bt = jzFlat(text), bf = jzP(ctx, 'bigFont', jzFontKeyOf(ctx.st, 'display')), bs = Math.min(la1_fit(la1_meas(ctx, bt, bf, 0), W * 0.94, H * 0.6), H * 0.6);
        var big = jzP(ctx, 'big', 'dim') === 'outline';
        var BG = jzText(ctx, bt, big ? { font: bf, size: bs, x: W / 2, y: H / 2, fill: false, stroke: Math.max(1.2 * ctx.u, bs * 0.006), strokeColor: sc.sub, opacity: 0.4, name: 'big copy' }
            : { font: bf, size: bs, x: W / 2, y: H / 2, color: pic ? sc.bg : (sc.dim || sc.sub), opacity: pic ? 0.7 : 1, name: 'big copy' });
        la1_opx(ctx, BG, 'oc((time-0.05)/0.6)*K'); jzNoGhost(BG);
        jzSetExpr(jzXf(BG, 'ADBE Position'), '[value[0]+(time/' + jzN(Math.max(1, c.dur)) + '-0.5)*' + jzN(W * 0.03 * (jzP(ctx, 'drift', 1) < 0 ? -1 : 1)) + ',value[1]]');
        // letterbox bars slide in, a hairline on each inner edge
        for (var sd = -1; sd <= 1; sd += 2) {
            var BL = jzNoGhost(jzShapeLayer(ctx, sd < 0 ? 'bar top' : 'bar bottom', W / 2, sd < 0 ? 0 : H)), g = jzGrp(BL, 'bar');
            jzAddRect(g, W + 4, bh + 2, 0, 0, sd < 0 ? -(bh + 2) / 2 : (bh + 2) / 2); jzAddFill(g, barC);
            var gl = jzGrp(BL, 'edge'); jzAddPath(gl, [[-W / 2, 0], [W / 2, 0]], false); jzAddStroke(gl, sc.sub, 1.2 * ctx.u, 35);
            jzSetExpr(jzXf(BL, 'ADBE Position'), HD + E + '[value[0],value[1]' + (sd < 0 ? '+' : '-') + jzN(bh) + '*e]');
            jzSetExpr(jzXf(BL, 'ADBE Opacity'), HD + E + 'e*' + jzN(bh) + '>0.5?value:0');
        }
        // timecode furniture in the top bar
        var ls = Math.min(jzSmallSize(ctx) * 0.9, bh * 0.3), mono = jzMonoF(ctx), TA = 'cl((e-0.6)*2.5)*K', YP = HD + E + '[value[0],' + jzN(bh) + '*e/2]';
        var tcs = [];
        if (jzP(ctx, 'tc', 'rec') === 'rec') {
            var DOT = jzNoGhost(jzEllipseLayer(ctx, 'rec dot', W * 0.05, bh / 2, ls * 0.7, ls * 0.7, sc.accent));
            jzSetExpr(jzXf(DOT, 'ADBE Opacity'), HD + E + 'value*' + TA + '*(Math.floor(time*12)%2==0?1:0)');
            jzSetExpr(jzXf(DOT, 'ADBE Position'), YP);
            tcs.push(jzText(ctx, 'REC', { font: mono, size: ls, color: jzOnCol(sc, barC), x: W * 0.05 + ls * 0.8, y: bh / 2, align: 'left', track: 0.12 }));
        } else tcs.push(jzText(ctx, 'SCENE ' + jzLineNo(ctx) + '  /  CUT ' + jzPad((c.index | 0) + 1, 2), { font: mono, size: ls, color: jzOnCol(sc, barC), x: W * 0.05, y: bh / 2, align: 'left', track: 0.12 }));
        var TC = jzText(ctx, jzFmtTime(c.start || 0), { font: mono, size: ls, color: sc.sub, x: W * 0.95, y: bh / 2, align: 'right', track: 0.08, name: 'timecode' });
        try { TC.property('ADBE Text Properties').property('ADBE Text Document').expression = 'var t=time+' + jzN(c.start || 0) + ';function p(v){return (v<10?"0":"")+v;}p(Math.floor(t/60))+":"+p(Math.floor(t%60))+":"+p(Math.floor((t%1)*24))'; } catch (e2) {}
        tcs.push(TC);
        for (i = 0; i < tcs.length; i++) { la1_opx(ctx, tcs[i], 'cl((oe(time/0.55)*K-0.6)*2.5)*K'); jzSetExpr(jzXf(tcs[i], 'ADBE Position'), YP); jzNoGhost(tcs[i]); }
        // the subtitle
        var font = jzP(ctx, 'font', jzBodyF(ctx)), place = jzP(ctx, 'place', 'bar'), maxH = place === 'bar' ? bh * 0.62 : bh * 0.8, t2 = text;
        var ss = Math.min(la1_fit(la1_meas(ctx, t2, font, 0.06, 1.25), W * 0.84, maxH), u * 0.058);
        if (ss < u * 0.04 && jzCount(text) > 8) { t2 = la1_brk(text, Math.ceil(jzCount(text) / 2)); ss = Math.min(la1_fit(la1_meas(ctx, t2, font, 0.06, 1.25), W * 0.84, maxH * 1.5), u * 0.05); }
        var scol = jzOnCol(sc, barC), L = la1_T(ctx, t2, font, ss, { x: W / 2, y: H - bh / 2, track: 0.06, lead: 1.25, color: scol }), m = jzSize(L);
        if (place !== 'bar') {
            var sy = H - bh - m[1] / 2 - ss * 0.9;
            jzXf(L, 'ADBE Position').setValue([W / 2, sy]);
            var PL = jzRectLayer(ctx, 'subtitle plate', W / 2, sy, m[0] + ss * 1.6, m[1] + ss * 0.7, barC, { opacity: 0.72 });
            la1_opx(ctx, PL, 'cl((oe(time/0.55)*K-0.3)*2)*K');
            PL.moveAfter(L); jzNoGhost(PL);
        }
        jzAnimate(ctx, L, { mi: 0 });
        return la1_bb(L);
    }
});

/* ---------------------------------------------------------------- 13 ticker \u2014 \u30C6\u30A3\u30C3\u30AB\u30FC */
jzReg('layout', 'ticker', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display'])), tag: rng.pick(['LIVE', 'NOW', 'ON AIR', 'LIVE']), speed: rng.range(0.8, 1.3), main: rng.pick(['center', 'left', 'center']), bug: rng.chance(0.6) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx);
        var text0 = jzTrim(c.text), mono = jzMonoF(ctx), body = jzBodyF(ctx), tag = String(jzP(ctx, 'tag', 'LIVE'));
        var bandH = u * 0.075, bandY = H - bandH - H * (port ? 0.12 : 0.085), bcy = bandY + bandH / 2;
        var tagC = sc.accent, bandC = sc.ink === sc.accent ? sc.fg : sc.ink, ts = bandH * 0.42, x0 = W * 0.04;
        var tagW = la1_meas(ctx, tag, mono, 0.12).w * ts / 100 + ts * 2.6, HD = la1_H(ctx), EB = 'var eB=oe(time/0.5)*K;';
        // band grows out from the left
        var BD = jzNoGhost(la1_bar(ctx, 'band', x0, bcy, W - x0, bandH, bandC, 1));
        jzSetExpr(jzXf(BD, 'ADBE Scale'), HD + EB + '[value[0]*eB,value[1]]');
        // scrolling copy inside the band (alpha matte = the band's visible part right of the tag)
        var unit = jzFlat(c.lineText || text0) + '\u3000\u25C6\u3000', fs = bandH * 0.46;
        var per = Math.max(10, (la1_meas(ctx, unit + unit, body, 0).w - la1_meas(ctx, unit, body, 0).w) * fs / 100);
        var reps = Math.min(40, Math.ceil(W * 1.2 / per) + 2), row = '';
        for (var r = 0; r < reps; r++) row += unit;
        var SR = jzText(ctx, row, { font: body, size: fs, color: jzOnCol(sc, bandC), x: x0 + tagW + ts * 0.8, y: bcy, align: 'left', name: 'ticker copy' });
        jzSetExpr(jzXf(SR, 'ADBE Position'), 'var v=' + jzN(jzP(ctx, 'speed', 1) * u * 0.22) + ',per=' + jzN(per) + ';var o=((time*v)%per+per)%per;[value[0]-o,value[1]]');
        var cw = W - x0 - tagW, MT = la1_bar(ctx, 'ticker clip', x0 + tagW, bcy, cw, bandH, '#FFFFFF', 1);
        jzSetExpr(jzXf(MT, 'ADBE Scale'), HD + EB + '[value[0]*Math.max(0,' + jzN(W - x0) + '*eB-' + jzN(tagW) + ')/' + jzN(cw) + ',value[1]]');
        la1_matte(SR, MT); la1_ng([SR, MT]);
        // tag box with a blinking dot, pops open vertically
        var TQ = 'var tq=cl((time-0.08)/0.3);tq=(tq<=0?0:ob(tq,1.6))*K;', tc = jzOnCol(sc, tagC);
        var TB = jzRectLayer(ctx, 'tag', x0 + tagW / 2, bcy, tagW, bandH * 1.24, tagC);
        jzSetExpr(jzXf(TB, 'ADBE Scale'), HD + TQ + '[value[0],value[1]*tq]');
        var TD = jzNoGhost(jzEllipseLayer(ctx, 'tag dot', x0 + ts * 0.95, bcy, ts * 0.56, ts * 0.56, tc));
        jzSetExpr(jzXf(TD, 'ADBE Scale'), HD + TQ + '[value[0]*tq,value[1]*tq]');
        jzSetExpr(jzXf(TD, 'ADBE Opacity'), 'Math.floor(time*12)%3!=0?value:0');
        var TT = jzNoGhost(jzText(ctx, tag, { font: mono, size: ts, color: tc, x: x0 + ts * 1.6, y: bcy, align: 'left', track: 0.12 }));
        jzSetExpr(jzXf(TT, 'ADBE Scale'), HD + TQ + '[value[0]*tq,value[1]*tq]');
        // small running clock above the band's right end
        var tsz = ts * 0.9, tw = la1_meas(ctx, '00:00.00', mono, 0.08).w * tsz / 100 + tsz * 1.4, CA = 'cl((oe(time/0.5)*K-0.7)*3.3)';
        var CB = jzRectLayer(ctx, 'clock box', W - W * 0.04 - tw / 2, bandY - tsz * 0.85, tw, tsz * 1.7, sc.fg);
        var CT = jzText(ctx, jzFmtTime(c.start || 0), { font: mono, size: tsz, color: jzOnCol(sc, sc.fg), x: W - W * 0.04 - tw / 2, y: bandY - tsz * 0.85, track: 0.08, name: 'clock' });
        try { CT.property('ADBE Text Properties').property('ADBE Text Document').expression = 'var t=time+' + jzN(c.start || 0) + ';function p(v){return (v<10?"0":"")+v;}p(Math.floor(t/60))+":"+p(Math.floor(t%60))+"."+p(Math.floor((t%1)*100))'; } catch (e1) {}
        la1_opx(ctx, CB, CA); la1_opx(ctx, CT, CA); la1_ng([CB, CT]);
        // channel bug
        if (jzP(ctx, 'bug', false)) {
            var bs = jzSmallSize(ctx) * 0.85, BA = 'oc((time-0.2)/0.4)*K';
            la1_opx(ctx, jzNoGhost(jzText(ctx, 'CH.' + jzLineNo(ctx), { font: mono, size: bs, color: sc.sub, x: W * 0.95, y: H * 0.07, align: 'right', track: 0.2 })), BA);
            la1_opx(ctx, jzNoGhost(jzRectLayer(ctx, 'bug mark', W * 0.95 - bs * 0.15, H * 0.07 + bs * 1.05, bs * 0.3, bs * 0.3, sc.accent)), BA);
        }
        // headline above the band (centred, or left with an accent rule)
        var left = jzP(ctx, 'main', 'center') === 'left', text = la1_brk(text0, port ? 6 : 11), font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display'));
        var avail = bandY - H * 0.1, mm = la1_meas(ctx, text, font, 0.03, 1.15), size = Math.min(la1_fit(mm, W * 0.84, avail * 0.7), u * 0.2), mh = mm.h * size / 100;
        var y = left ? bandY - bandH * 0.55 - mh / 2 - size * 0.2 : H * 0.1 + avail / 2, x = left ? x0 + size * 0.1 : W / 2;
        if (left) {
            var rh = Math.max(3 * ctx.u, size * 0.06), RL = la1_bar(ctx, 'headline rule', x0, y - mh / 2 - size * 0.28 + rh / 2, size * 1.2, rh, sc.accent, 1);
            jzSetExpr(jzXf(RL, 'ADBE Scale'), HD + '[value[0]*oe((time-0.1)/0.5)*K,value[1]]'); jzNoGhost(RL);
        }
        var L = la1_T(ctx, text, font, size, { align: left ? 'left' : null, x: x, y: y, track: 0.03, lead: 1.15 });
        jzAnimate(ctx, L, { mi: 0 });
        return la1_bb(L);
    }
});

/* ---------------------------------------------------------------- 14 splitScreen \u2014 \u4E8C\u5206\u5272 */
jzReg('layout', 'splitScreen', {
    plan: function (rng, cut, st) {
        var port = cut.H > cut.W * 1.08;
        return { font: rng.pick(jzFontsOf(st, ['display'])), split: port ? rng.pick(['h', 'diag', 'h']) : rng.pick(['v', 'diag', 'v', 'h']), plate: rng.pick(['fg', 'accent', 'fg']), side: rng.pick([1, -1]), tilt: rng.range(12, 22) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), DEG = Math.PI / 180;
        var tOff = jzContrast(sc.fg, sc.bg) > 2 ? sc.fg : jzOnCol(sc, sc.bg);
        var plateC = jzP(ctx, 'plate', 'fg') === 'accent' ? sc.accent : sc.fg;
        if (String(jzOnCol(sc, plateC)).toUpperCase() === String(tOff).toUpperCase() || jzContrast(plateC, sc.bg) < 1.6) plateC = sc.fg;   // keep the inversion readable
        var tOn = jzOnCol(sc, plateC), split = jzP(ctx, 'split', port ? 'h' : 'v'), sd = jzP(ctx, 'side', 1) < 0 ? -1 : 1;
        // plate polygon (at rest) + the sweep that brings its boundary in from the plate's outer edge
        var poly, sweep, HD = la1_H(ctx), E = 'var e=oe(time/0.5)*(1-ie(PO));';
        if (split === 'h') {
            poly = sd > 0 ? [[-W, H / 2], [2 * W, H / 2], [2 * W, 2 * H], [-W, 2 * H]] : [[-W, -H], [2 * W, -H], [2 * W, H / 2], [-W, H / 2]];
            sweep = '[value[0],value[1]+' + jzN(sd * H * 0.55) + '*(1-e)]';
        } else {
            var dx = split === 'diag' ? Math.tan(jzP(ctx, 'tilt', 16) * DEG) * H / 2 : 0;
            var top = [W / 2 + dx - 2 * dx * (-H) / H, -H], bot = [W / 2 + dx - 2 * dx * (2 * H) / H, 2 * H];
            poly = sd > 0 ? [top, [3 * W, -H], [3 * W, 2 * H], bot] : [[-2 * W, -H], top, bot, [-2 * W, 2 * H]];
            sweep = '[value[0]+' + jzN(sd * (W * 0.55 + Math.abs(dx))) + '*(1-e),value[1]]';
        }
        function plateLayer(name, col) {
            var S = jzShapeLayer(ctx, name, 0, 0), g = jzGrp(S, name);
            jzAddPath(g, poly, true); jzAddFill(g, col);
            jzSetExpr(jzXf(S, 'ADBE Position'), HD + E + sweep);
            return jzNoGhost(S);   // plate + its matte copies: main pass only
        }
        var text = la1_brk(jzTrim(c.text), port ? 5 : 10), font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display'));
        var mm = la1_meas(ctx, text, font, 0.02, 1.08), size = Math.min(la1_fit(mm, W * 0.86, H * (split === 'h' ? 0.36 : 0.42)), u * 0.3);
        var cy = split === 'h' ? H / 2 + (mm.h * size / 100 > size * 1.5 ? 0 : size * 0.04) : H / 2;
        var ls = jzSmallSize(ctx) * 0.9, mono = jzMonoF(ctx), LA = 'oc((time-0.25)/0.4)*K', yA = sd > 0 || split !== 'h' ? H * 0.07 : H * 0.93, yB = sd > 0 || split !== 'h' ? H * 0.93 : H * 0.07;
        // off the plate: lyric in the text colour + line number (the plate covers them where it lies)
        var L1 = la1_T(ctx, text, font, size, { x: W / 2, y: cy, track: 0.02, lead: 1.08, color: tOff });
        la1_opx(ctx, jzNoGhost(jzText(ctx, 'No.' + jzLineNo(ctx), { font: mono, size: ls, color: sc.sub, x: sd > 0 ? W * 0.05 : W * 0.95, y: yA, align: sd > 0 ? 'left' : 'right', track: 0.15 })), LA);
        plateLayer('plate', plateC);
        // on the plate: the same lyric inverted + romaji / time, each matted by a copy of the plate
        var L2 = la1_T(ctx, text, font, size, { x: W / 2, y: cy, track: 0.02, lead: 1.08, color: tOn, name: jzTrim(c.text) + ' (on plate)' });
        la1_matte(L2, plateLayer('plate matte', '#FFFFFF'));          // a track matte sits directly above its layer
        var T2 = jzText(ctx, jzRomajiOf(ctx) || jzFmtTime(c.start || 0), { font: mono, size: ls, color: tOn, x: sd > 0 ? W * 0.95 : W * 0.05, y: yB, align: sd > 0 ? 'right' : 'left', track: 0.15 });
        la1_opx(ctx, T2, LA); jzNoGhost(T2);
        la1_matte(T2, plateLayer('plate matte', '#FFFFFF'));
        jzAnimate(ctx, L1, { mi: 0, treat: false });
        jzAnimate(ctx, L2, { mi: 0, treat: false });
        return la1_bb(L1);
    }
});

// ================================================================ pack layoutsA part 2 (AE port of src/11p_layoutsA.js)
// mirror, sideways, edgeFrame, perspective, hanko, genkou, panels, filmstrip, quote, ruler, searchBar, chat, notification, ticket
// Helpers are prefixed la2_. Secondary graphics are shape / text layers driven by expressions on `time` (0 = cut start);
// jzTH(ctx) gives DUR, IN, OS, OD, PO and K (= the cut's exit fade), LA2_FX adds ioc (inOutCubic) and sm (smoothstep).

var LA2_FX = 'function ioc(x){x=cl(x);return x<0.5?4*x*x*x:1-Math.pow(-2*x+2,3)/2;}function sm(a,b,x){var t=cl((x-a)/(b-a));return t*t*(3-2*t);}\n';
var LA2_VROT = '\u30FC\u301C\uFF5E\u2026\u2025\u2015\u2014-()\uFF08\uFF09\u300C\u300D\u300E\u300F\u3010\u3011\u3008\u3009\u300A\u300B\u3014\u3015[]\uFF3B\uFF3D\u2192\u2190:\uFF1A;\uFF1B=\uFF1D';
var LA2_SMALL = '\u3041\u3043\u3045\u3047\u3049\u3063\u3083\u3085\u3087\u308E\u30A1\u30A3\u30A5\u30A7\u30A9\u30C3\u30E3\u30E5\u30E7\u30EE\u30F5\u30F6';
var LA2_VPUNCT = '\u3001\u3002\uFF0C\uFF0E';

function la2_hd(ctx) { return jzTH(ctx) + LA2_FX; }
function la2_isSmall(ch) { return LA2_SMALL.indexOf(ch) >= 0; }
function la2_isBad(ch) { return la2_isSmall(ch) || jzIsPunct(ch) || ch === '\u30FC' || ch === ' '; }
function la2_brk(text, maxPer) { return jzSplitLines(jzTrim(text), maxPer); }
function la2_lines(text) { return String(text).split(/\r\n|\r|\n/); }
// readable text colour on a plate, preferring the scheme's bg / fg (= onCol in the browser pack)
function la2_onCol(sc, plate) {
    var a = jzContrast(sc.bg, plate), b = jzContrast(sc.fg, plate);
    if (Math.max(a, b) >= 2.4) return a >= b ? sc.bg : sc.fg;
    return jzLum(plate) > 0.5 ? '#111111' : '#FFFFFF';
}
function la2_bb(x0, y0, x1, y1) { return { x0: x0, y0: y0, x1: x1, y1: y1, cx: (x0 + x1) / 2, cy: (y0 + y1) / 2 }; }
// keep helper layers out of the tinted ghosts (the browser draws them with ghost off / main pass only)
function la2_ng(list) { for (var i = 0; i < list.length; i++) if (list[i]) jzNoGhost(list[i]); return list; }

// ---- text with a leading multiple (o.lead, in em) that survives fitting: jzText + refit keeping leading = size * lead
function la2_refit(L, maxW, maxH, maxSize, lead) {
    var r = jzRect(L), src = L.property('ADBE Text Properties').property('ADBE Text Document'), td = src.value;
    var s = td.fontSize * Math.min(maxW / Math.max(1, r.width), maxH / Math.max(1, r.height));
    if (maxSize) s = Math.min(s, maxSize);
    td.fontSize = Math.max(1, s);
    if (lead) { try { td.autoLeading = false; td.leading = td.fontSize * lead; } catch (e) {} }
    src.setValue(td);
}
function la2_txt(ctx, str, o) {
    var q = jzCopy(o);
    q.maxW = null; q.maxH = null; q.maxSize = null;
    if (o.lead) q.leading = (o.size || 100) * o.lead;
    var L = jzText(ctx, str, q);
    if (o.maxW || o.maxH) { la2_refit(L, o.maxW || 1e6, o.maxH || 1e6, o.maxSize, o.lead); jzAnchor(L, o.align); }
    return L;
}
// font size that fits a box (probe layer, removed again)
function la2_fitSize(ctx, str, font, maxW, maxH, o) {
    o = o || {};
    var P = la2_txt(ctx, str, { font: font, size: 100, color: '#FFFFFF', x: -9999, y: -9999, track: o.track || 0, lead: o.lead, maxW: maxW, maxH: maxH });
    var s = jzFontSize(P); P.remove();
    return s;
}
// width of a text run (probe)
function la2_width(ctx, str, font, size, track) {
    var P = jzText(ctx, str, { font: font, size: size, color: '#FFFFFF', x: -9999, y: -9999, track: track || 0, align: 'left' });
    var w = jzRect(P).width; P.remove();
    return w;
}
// secondary layer: fade (and optional slide from dx, dy) in at `delay` over `len` s with an outCubic ease, follows the cut's exit
// (la2_io / la2_op are only used for secondary layers the browser draws with ghost off: they are kept out of the ghosts)
function la2_io(ctx, L, delay, len, dx, dy, alpha) {
    jzNoGhost(L);
    var hd = la2_hd(ctx) + 'var ca=oc((time-' + jzN(delay) + ')/' + jzN(len) + ');';
    if (dx || dy) jzSetExpr(jzXf(L, 'ADBE Position'), hd + '[value[0]+' + jzN(dx || 0) + '*(1-ca),value[1]+' + jzN(dy || 0) + '*(1-ca)]');
    if (alpha != null) jzXf(L, 'ADBE Opacity').setValue(alpha * 100);
    jzSetExpr(jzXf(L, 'ADBE Opacity'), hd + 'value*ca*K');
}
function la2_op(ctx, L, factorExpr, alpha) {
    jzNoGhost(L);
    if (alpha != null) jzXf(L, 'ADBE Opacity').setValue(alpha * 100);
    jzSetExpr(jzXf(L, 'ADBE Opacity'), la2_hd(ctx) + 'value*(' + factorExpr + ')');
}
function la2_line(g, pts, col, w, op) { jzAddPath(g, pts, false); var s = jzAddStroke(g, col, w, op); return s; }
function la2_sub(g, name) { var q = jzVecs(g).addProperty('ADBE Vector Group'); if (name) q.name = name; return q; }   // group inside a group
// a layer's top-level shape group by name (re-fetch: adding a sibling group invalidates the references held to the others)
function la2_top(S, name) { return S.property('ADBE Root Vectors Group').property(name); }
// dashed stroke (AE starts with an empty Dashes group; the preview model has fixed children). Each dash property is set
// right after it is added, re-fetched by matchName (adding the Gap invalidates a reference held to the Dash).
function la2_dash(st, d, gp) {
    var D = st.property('ADBE Vector Stroke Dashes'), p;
    try { D.addProperty('ADBE Vector Stroke Dash 1'); } catch (e) {}
    p = D.property('ADBE Vector Stroke Dash 1'); if (p) p.setValue(d);
    try { D.addProperty('ADBE Vector Stroke Gap 1'); } catch (e2) {}
    p = D.property('ADBE Vector Stroke Gap 1'); if (p) p.setValue(gp);
}
function la2_gxp(g, expr) { jzSetExpr(jzGX(g).property('ADBE Vector Position'), expr); }
function la2_gxo(g, expr) { jzSetExpr(jzGX(g).property('ADBE Vector Group Opacity'), expr); }
function la2_gxs(g, expr) { jzSetExpr(jzGX(g).property('ADBE Vector Scale'), expr); }

// ---- glyphs of ONE text layer placed at given centres (comp px): gl = glyph strings, cells = [[x, y, rot], ...]
// (line k of the layer holds glyph k; per-glyph offsets move it to its cell). px, py = pivot for scale / rotation.
function la2_cells(ctx, gl, cells, o) {
    var size = o.size, B = 0.38, k;
    var L = jzText(ctx, gl.join('\r'), { font: o.font, size: size, color: o.color, x: 0, y: 0, leading: size, name: o.name, fill: o.fill, stroke: o.stroke, strokeColor: o.strokeColor });
    var ox = cells[0][0], oy = cells[0][1] + B * size;           // comp position of the layer origin (baseline centre of line 1)
    var px = o.px != null ? o.px : ox, py = o.py != null ? o.py : cells[0][1];
    jzXf(L, 'ADBE Anchor Point').setValue([px - ox, py - oy]);
    jzXf(L, 'ADBE Position').setValue([px, py]);
    var offs = [], rots = [], anyO = false, anyR = false;
    for (k = 0; k < gl.length; k++) {
        var dx = cells[k][0] - ox, dy = cells[k][1] - (oy + k * size - B * size);
        offs.push([dx, dy]); rots.push(cells[k][2] || 0);
        if (Math.abs(dx) + Math.abs(dy) > 0.05) anyO = true;
        if (cells[k][2]) anyR = true;
    }
    if (anyO) jzCharOffsets(L, offs, 'JZ Cells');
    if (anyR) jzCharRotations(L, rots, 'JZ Upright');
    return L;
}
// vertical columns (right to left) in one text layer -> { L, bb }. o: font, size, color, x (block centre), y (top when o.top, else centre), lead (column pitch, em)
function la2_vcols(ctx, lines, o) {
    var size = o.size, lead = o.lead || 1.15, cols = lines.length, gl = [], cells = [], i, j, maxN = 1;
    for (i = 0; i < cols; i++) {
        var ch = jzChars(jzStrip(lines[i])), cx = o.x - (i - (cols - 1) / 2) * lead * size;
        maxN = Math.max(maxN, ch.length);
        var y0 = o.top ? o.y : o.y - ch.length * size / 2;
        for (j = 0; j < ch.length; j++) {
            var c = ch[j], vx = 0, vy = 0, rot = 0;
            if (la2_isSmall(c)) { vx = 0.11 * size; vy = -0.11 * size; }
            if (LA2_VPUNCT.indexOf(c) >= 0) { vx = 0.3 * size; vy = -0.3 * size; }
            if (LA2_VROT.indexOf(c) >= 0 || /[A-Za-z0-9]/.test(c)) rot = 90;
            gl.push(c); cells.push([cx + vx, y0 + (j + 0.5) * size + vy, rot]);
        }
    }
    var bw = size * (1 + (cols - 1) * lead), bh = maxN * size, top = o.top ? o.y : o.y - bh / 2;
    var L = la2_cells(ctx, gl, cells, { font: o.font, size: size, color: o.color, name: o.name, px: o.x, py: top + bh / 2 });
    return { L: L, bb: la2_bb(o.x - bw / 2, top, o.x + bw / 2, top + bh) };
}

// ---- balanced split into k chunks (= splitK in the browser pack; used by plan())
function la2_split2(word, force) {
    var ch = jzChars(word), n = ch.length, best = Math.max(1, Math.floor(n / 2)), bs = -1e9, c;
    for (c = 1; c < n; c++) {
        var a = ch[c - 1], b = ch[c];
        if (!force && ((c === 1 && !jzIsKanji(a)) || (c === n - 1 && !jzIsKanji(b)))) continue;
        var s = -Math.abs(c - n / 2) * 0.9;
        if (a === ' ' || a === '\u3000' || (jzIsPunct(a) && a !== '\u30FC')) s += 5;
        if (jzIsHira(a) && !jzIsHira(b) && !la2_isBad(b)) s += 3;
        if (la2_isBad(b)) s -= 6;
        if (jzIsKanji(a) && jzIsKanji(b)) s -= 2;
        if (jzIsKanji(a) && jzIsHira(b)) s -= 2;
        if (jzIsHira(a) && jzIsHira(b)) s -= 2;
        if (s > bs) { bs = s; best = c; }
    }
    if (!force && bs < -1.5 && !jzHasLatin(word)) return [word];
    var out = [], p1 = jzTrim(ch.slice(0, best).join('')), p2 = jzTrim(ch.slice(best).join(''));
    if (p1) out.push(p1); if (p2) out.push(p2);
    return out;
}
function la2_splitK(text, k, force) {
    var t = jzTrim(String(text || ''));
    if (!t) return [''];
    var latin = jzHasLatin(t), words = [], i, raw = latin ? t.split(/\s+/) : jzChunk(t);
    for (i = 0; i < raw.length; i++) if (jzTrim(raw[i])) words.push(jzTrim(raw[i]));
    if (!words.length) words = [t];
    k = Math.max(1, Math.min(k, jzCount(t)));
    var hard = {}, guard;
    for (guard = 0; words.length < k && guard < 16; guard++) {
        var bi = -1, bl = 1;
        for (i = 0; i < words.length; i++) { var l = jzCount(words[i]); if (l > bl && !hard[words[i]]) { bl = l; bi = i; } }
        if (bi < 0) break;
        var parts = latin ? [words[bi]] : la2_split2(words[bi]);
        if (parts.length < 2 && force && words.length < force) parts = la2_split2(words[bi], true);
        if (parts.length < 2) { hard[words[bi]] = 1; continue; }
        words.splice(bi, 1, parts[0], parts[1]);
    }
    var sep = latin ? ' ' : '';
    if (k <= 1) return [words.join(sep)];
    if (words.length <= k) return words;
    // partition the words into k consecutive groups with balanced glyph counts (exhaustive, small n)
    var pre = [0], n = words.length;
    for (i = 0; i < n; i++) pre.push(pre[i] + jzCount(words[i]) + 0.5);
    var tgt = pre[n] / k, best = null, bestS = 1e18, cnt = 0;
    function rec(start, g, cuts) {
        if (++cnt > 5000) return;
        if (g === k - 1) {
            var s = 0, prev = 0, all = cuts.concat([n]);
            for (var q = 0; q < all.length; q++) { var d = pre[all[q]] - pre[prev] - tgt; s += d * d; prev = all[q]; }
            if (s < bestS) { bestS = s; best = all; }
            return;
        }
        for (var c = start + 1; c <= n - (k - 1 - g); c++) rec(c, g + 1, cuts.concat([c]));
    }
    rec(0, 0, []);
    if (!best) return words;
    var out = [], prev = 0;
    for (i = 0; i < best.length; i++) { out.push(words.slice(prev, best[i]).join(sep)); prev = best[i]; }
    return out;
}

/* ---------------------------------------------------------------- 15 mirror \u2014 \u93E1\u50CF */
jzReg('layout', 'mirror', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), strength: rng.range(0.34, 0.5), squash: rng.pick([1, 0.7, 0.85]), ripple: rng.chance(0.5), ticks: rng.chance(0.6) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), k;
        var font = jzFontOf(ctx, 'font', 'display'), squash = jzP(ctx, 'squash', 0.85), strength = jzP(ctx, 'strength', 0.42);
        var hd = la2_hd(ctx), eE = 'var e=ioc(time/0.7)*K;';
        // horizon (behind everything): grows from the centre, ticks appear as it passes, accent dots ride the ends
        var Hz = jzNoGhost(jzShapeLayer(ctx, 'horizon', W / 2, 0));
        var text = la2_brk(c.text, port ? 6 : 12);
        var L = la2_txt(ctx, text, { font: font, size: 100, color: sc.fg, x: W / 2, y: H / 2, track: 0.04, lead: 1.15, maxW: W * 0.84, maxH: H * 0.3, maxSize: u * 0.22 });
        var size = jzFontSize(L), sz = jzSize(L), mh = sz[1];
        var RH = mh * squash + size * 0.1, cy = H / 2 - RH * 0.42, hz = cy + mh / 2 + size * 0.08, hw = W * 0.44;
        jzXf(Hz, 'ADBE Position').setValue([W / 2, hz]);
        for (k = -1; k <= 1; k += 2) {
            var gk = jzGrp(Hz, 'dot'); jzAddEllipse(gk, u * 0.01, u * 0.01); jzAddFill(gk, sc.accent);
            la2_gxp(gk, hd + eE + '[' + k + '*' + jzN(hw) + '*e,0]'); la2_gxo(gk, hd + eE + 'e>0.001?100:0');
        }
        if (jzP(ctx, 'ticks', true)) {
            for (k = -8; k <= 8; k++) {
                var x = k * W * 0.05; if (Math.abs(x) > hw) continue;
                var gt = jzGrp(Hz, 'tick'); la2_line(gt, [[x, -u * 0.006], [x, u * 0.006 * (k % 4 === 0 ? 2 : 1)]], sc.sub, 1.2 * ctx.u, 60);
                la2_gxo(gt, hd + eE + 'e*' + jzN(hw) + '>=' + jzN(Math.abs(x)) + '-0.01?100:0');
            }
        }
        var gl = jzGrp(Hz, 'line');
        jzAddPath(gl, [[0, 0], [-hw, 0]], false); jzAddPath(gl, [[0, 0], [hw, 0]], false);
        jzAddStroke(gl, sc.sub, Math.max(1.2 * ctx.u, u * 0.0016), 85);
        jzAddTrimPaths(gl, hd + eE + '100*e');
        // the lyric
        jzXf(L, 'ADBE Position').setValue([W / 2, cy]);
        jzAnimate(ctx, L, { mi: 0 });
        // reflection: a flipped, squashed duplicate (same motion) faded with depth by a luma ramp matte, optional ripple
        var R = L.duplicate(); R.name = 'reflection'; R.moveAfter(L);
        jzXf(R, 'ADBE Position').setValue([W / 2, hz + (hz - cy) * squash]);
        jzXf(R, 'ADBE Scale').setValue([100, -100 * squash]);
        jzXf(R, 'ADBE Opacity').setValue(100 * strength);
        if (jzP(ctx, 'ripple', false)) {
            var ww = jzEffect(R, 'ADBE Wave Warp', 'JZ Ripple');
            jzEP(ww, 1, 1); jzEP(ww, 2, size * 0.022); jzEP(ww, 3, Math.max(8, RH * 0.9)); jzEP(ww, 4, 0); jzEP(ww, 5, 0.38); jzEP(ww, 6, 1);
        }
        var M = ctx.comp.layers.addSolid([1, 1, 1], 'reflection fade', W, H, 1, ctx.comp.duration);
        var rp = jzEffect(M, 'ADBE Ramp', 'JZ Depth');
        jzEP(rp, 1, [W / 2, hz]); jzEP(rp, 2, [0.92, 0.92, 0.92]); jzEP(rp, 3, [W / 2, hz + RH * 0.98]); jzEP(rp, 4, [0, 0, 0]); jzEP(rp, 5, 1);
        M.moveBefore(R);
        la2_ng([R, M]);                                  // the browser draws the reflection on the main pass only (ghost: false)
        try { R.trackMatteType = TrackMatteType.LUMA; } catch (e) { jzWarn('mirror matte: ' + e.toString()); }
        return jzBB(L);
    }
});

/* ---------------------------------------------------------------- 16 sideways \u2014 \u7E26\u5012\u3057 */
jzReg('layout', 'sideways', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'display', 'serif'])), side: rng.pick(['left', 'right']), copy: rng.pick(['stack', 'number']), rule: rng.chance(0.75) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx);
        var font = jzFontOf(ctx, 'font', 'display'), t0 = jzTrim(c.text), n = jzCount(t0);
        var text = (port ? n > 12 : n > 6) ? la2_brk(t0, Math.ceil(n / 2)) : t0;
        var L = la2_txt(ctx, text, { font: font, size: 100, color: sc.fg, x: W / 2, y: H / 2, track: 0.02, lead: 1.02, maxW: H * 0.88, maxH: W * (port ? 0.4 : 0.3), maxSize: u * (port ? 0.34 : 0.3) });
        var sz = jzSize(L), mw = sz[0], mh = sz[1];
        var left = jzP(ctx, 'side', 'left') === 'left', mx = W * 0.05, cx = left ? mx + mh / 2 : W - mx - mh / 2;
        jzXf(L, 'ADBE Position').setValue([cx, H / 2]);
        jzXf(L, 'ADBE Rotate Z').setValue(left ? -90 : 90);
        jzAnimate(ctx, L, { mi: 0 });
        var hd = la2_hd(ctx);
        // divider rule (grows downwards)
        var rx = left ? cx + mh / 2 + W * 0.035 : cx - mh / 2 - W * 0.035;
        if (jzP(ctx, 'rule', true)) jzNoGhost(jzPathLayer(ctx, 'rule', [[rx, H * 0.06], [rx, H * 0.94]], sc.accent, { width: Math.max(2 * ctx.u, u * 0.003), trim: hd + '100*ioc((time-0.05)/0.6)*K' }));
        // horizontal copy block in the free area
        var ax0 = left ? rx + W * 0.04 : W * 0.07, ax1 = left ? W * 0.93 : rx - W * 0.04, aw = ax1 - ax0;
        if (aw > u * 0.2) {
            var ls = jzSmallSize(ctx), copy = c.lineText || c.text, body = jzBodyF(ctx), mono = jzMonoF(ctx), sl = u * 0.03;
            var cs = Math.min(u * 0.05, la2_fitSize(ctx, la2_brk(copy, Math.max(4, Math.floor(aw / (u * 0.05)))), body, aw, H * 0.3, { lead: 1.5 }));
            var ct = la2_brk(copy, Math.max(4, Math.floor(aw / (cs * 1.02))));
            var CT = la2_txt(ctx, ct, { font: body, size: cs, lead: 1.5, color: sc.fg, x: ax0, y: 0, align: 'left' });
            var cmh = jzSize(CT)[1], by = H * 0.9 - cmh / 2;
            jzXf(CT, 'ADBE Position').setValue([ax0, by]);
            la2_io(ctx, CT, 0.2, 0.45, 0, -sl, 0.9);
            if (jzP(ctx, 'copy', 'stack') === 'number') {
                var ns = Math.min(aw * 0.5, H * 0.3);
                var NT = jzText(ctx, jzLineNo(ctx), { font: font, size: ns, fill: false, stroke: Math.max(1.5 * ctx.u, ns * 0.012), strokeColor: sc.sub, x: ax0 - ns * 0.04, y: H * 0.1 + ns * 0.45, align: 'left' });
                la2_io(ctx, NT, 0.2, 0.45, 0, sl, 1);
            } else {
                la2_io(ctx, jzText(ctx, 'No.' + jzLineNo(ctx), { font: mono, size: ls, track: 0.15, color: sc.accent, x: ax0, y: H * 0.1, align: 'left' }), 0.2, 0.45, 0, sl, 1);
                la2_io(ctx, jzText(ctx, jzFmtTime(c.start), { font: mono, size: ls, track: 0.15, color: sc.sub, x: ax0, y: H * 0.1 + ls * 1.6, align: 'left' }), 0.2, 0.45, 0, sl, 1);
            }
            var rom = jzRomajiOf(ctx);
            if (rom && rom !== copy) la2_io(ctx, jzText(ctx, rom, { font: mono, size: ls * 0.85, track: 0.2, color: sc.sub, x: ax0, y: by - cmh / 2 - ls * 1.4, align: 'left' }), 0.2, 0.45, 0, -sl, 1);
        }
        return la2_bb(cx - mh / 2, H / 2 - mw / 2, cx + mh / 2, H / 2 + mw / 2);
    }
});

/* ---------------------------------------------------------------- 17 edgeFrame \u2014 \u5916\u5468 */
jzReg('layout', 'edgeFrame', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), edgeFont: rng.pick(['body', 'mono']), sep: rng.pick(['\u3000\uFF0F\u3000', '\u3000\u30FB\u3000', '\u3000\u2014\u3000']), speed: rng.range(0.6, 1.2) * rng.pick([1, -1]), corner: rng.pick(['square', 'cross']), inner: rng.chance(0.6) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), k;
        var d = u * 0.045, es = u * 0.024, band = es * 1.7, hd = la2_hd(ctx);
        var ef = jzP(ctx, 'edgeFont', 'body') === 'mono' ? jzMonoF(ctx) : jzBodyF(ctx);
        var unit = jzFlat(c.lineText || c.text) + jzP(ctx, 'sep', '\u3000\uFF0F\u3000');
        // scroll period = width(unit x2) - width(unit): independent of how trailing spaces are measured
        var per = Math.max(10, la2_width(ctx, unit + unit, ef, es, 0.08) - la2_width(ctx, unit, ef, es, 0.08));
        var sp = jzP(ctx, 'speed', 0.9) * u * 0.07;
        var edges = [[d + band, d, W - 2 * d - 2 * band, 0], [W - d, d + band, H - 2 * d - 2 * band, 90], [W - d - band, H - d, W - 2 * d - 2 * band, 180], [d, H - d - band, H - 2 * d - 2 * band, 270]];
        for (k = 0; k < 4; k++) {
            var E = edges[k], reps = Math.min(60, Math.ceil(E[2] / per) + 2), s = '';
            for (var i = 0; i < reps; i++) s += unit;
            // the scrolling row (anchor slides along its own axis) under an alpha matte that reveals the edge
            var R = jzText(ctx, s, { font: ef, size: es, color: sc.sub, x: E[0], y: E[1], track: 0.08, align: 'left', rot: E[3], opacity: 0.85, name: 'edge copy ' + (k + 1) });
            jzSetExpr(jzXf(R, 'ADBE Anchor Point'), 'var P=' + jzN(per) + ',o=((time*' + jzN(sp) + ')%P+P)%P;[value[0]+P-o,value[1]]');
            var Mt = jzShapeLayer(ctx, 'edge clip ' + (k + 1), E[0], E[1]), gm = jzGrp(Mt, 'clip');
            jzAddRect(gm, E[2], band * 1.04, 0, E[2] / 2, 0); jzAddFill(gm, '#FFFFFF');
            jzXf(Mt, 'ADBE Rotate Z').setValue(E[3]);
            jzSetExpr(jzXf(Mt, 'ADBE Scale'), hd + 'var e=ioc((time-' + jzN(k * 0.07) + ')/0.5)*K;[value[0]*e,value[1]]');
            try { R.trackMatteType = TrackMatteType.ALPHA; } catch (e1) { jzWarn('edgeFrame matte: ' + e1.toString()); }
            la2_ng([R, Mt]);
        }
        // corner marks (outBack pop)
        var C = jzNoGhost(jzShapeLayer(ctx, 'corners', 0, 0)), cr = [[d, d], [W - d, d], [W - d, H - d], [d, H - d]], cross = jzP(ctx, 'corner', 'square') === 'cross';
        for (k = 0; k < 4; k++) {
            var gc = jzGrp(C, 'corner');
            if (cross) { var q = es * 0.6; jzAddPath(gc, [[-q, 0], [q, 0]], false); jzAddPath(gc, [[0, -q], [0, q]], false); jzAddStroke(gc, sc.accent, Math.max(1.5 * ctx.u, es * 0.08)); }
            else { jzAddRect(gc, es * 0.55, es * 0.55); jzAddFill(gc, sc.accent); }
            jzGX(gc).property('ADBE Vector Position').setValue(cr[k]);
            la2_gxs(gc, hd + 'var q=cl((time-0.1)/0.3);var s=q<=0?0:ob(q,2)*K*100;[s,s]');
        }
        if (jzP(ctx, 'inner', false)) {
            var g = d + band * 0.95;
            var IF = jzPathLayer(ctx, 'inner frame', [[g, g], [W - g, g], [W - g, H - g], [g, H - g], [g, g]], sc.sub, { width: 1.2 * ctx.u, trim: hd + '100*ioc((time-0.15)/0.8)*K' });
            jzXf(IF, 'ADBE Opacity').setValue(45); jzNoGhost(IF);
        }
        var L = la2_txt(ctx, la2_brk(c.text, port ? 6 : 11), { font: jzFontOf(ctx, 'font', 'display'), size: 100, color: sc.fg, x: W / 2, y: H / 2, track: 0.04, lead: 1.15, maxW: W * 0.72, maxH: H * 0.5, maxSize: u * 0.22 });
        jzAnimate(ctx, L, { mi: 0 });
        return jzBB(L);
    }
});

/* ---------------------------------------------------------------- 18 perspective \u2014 \u5965\u884C\u304D */
jzReg('layout', 'perspective', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display'])), mode: rng.pick(['floor', 'side', 'floor']), copies: rng.int(4, 6), speed: rng.range(0.25, 0.45), guides: rng.chance(0.7), vx: rng.pick([1, -1]) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx);
        var font = jzFontOf(ctx, 'font', 'display'), side = jzP(ctx, 'mode', 'floor') === 'side', vx = jzP(ctx, 'vx', 1) < 0 ? -1 : 1;
        var KC = Math.round(jzClamp(jzP(ctx, 'copies', 5), 2, 8)), speed = jzP(ctx, 'speed', 0.35), hd = la2_hd(ctx);
        var text = la2_brk(c.text, port ? 6 : 12);
        var L = la2_txt(ctx, text, { font: font, size: 100, color: sc.fg, x: W / 2, y: H / 2, track: 0.03, lead: 1.1, maxW: W * (side ? 0.7 : 0.84), maxH: H * 0.22, maxSize: u * 0.18 });
        var size = jzFontSize(L), sz = jzSize(L), mw = sz[0], mh = sz[1];
        var x0 = side ? W / 2 - vx * W * 0.12 : W / 2, y0 = H * 0.74 - mh / 2 + size * 0.5;
        var V = side ? [W / 2 + vx * W * 0.36, H * 0.12] : [W / 2, H * 0.1];
        var sy = side ? 1 : 0.8, D = Math.abs(y0 - V[1]), X = 2 * D / Math.max(1, mh * sy * 1.35), r = jzClamp((X - 1) / (X + 1), 0.45, 0.8);
        jzXf(L, 'ADBE Position').setValue([x0, y0]);
        // rails from the front copy to the vanishing point (behind everything)
        if (jzP(ctx, 'guides', true)) {
            var hw = mw / 2 + size * 0.3, yb = y0 + mh / 2 + size * 0.12;
            var G = jzShapeLayer(ctx, 'perspective rails', 0, 0), gr = jzGrp(G, 'rails');
            jzAddPath(gr, [[x0 - hw, yb], V], false); jzAddPath(gr, [[x0 + hw, yb], V], false);
            jzAddStroke(gr, sc.sub, 1.2 * ctx.u, 40); jzAddTrimPaths(gr, hd + '100*ioc(time/0.8)*K');
            var gv = jzGrp(G, 'vanishing point'); jzAddEllipse(gv, u * 0.01, u * 0.01, V[0], V[1]); jzAddFill(gv, sc.accent);
            la2_gxo(gv, hd + '100*ioc(time/0.8)*K');
            G.moveAfter(L); jzNoGhost(G);
        }
        // receding copies: copy j sits at depth z = j + phase and drifts back into the distance
        for (var j = KC; j >= 1; j--) {
            var Cp = la2_txt(ctx, text, { font: font, size: size, color: sc.sub, x: x0, y: y0, track: 0.03, lead: 1.1, name: 'depth copy ' + j });
            var zh = hd + 'var z=' + j + '+((time*' + jzN(speed) + ')%1);var k=Math.pow(' + jzN(r) + ',z);';
            jzSetExpr(jzXf(Cp, 'ADBE Position'), zh + '[' + jzN(V[0]) + '+' + jzN(x0 - V[0]) + '*k,' + jzN(V[1]) + '+' + jzN(y0 - V[1]) + '*k]');
            jzSetExpr(jzXf(Cp, 'ADBE Scale'), zh + '[value[0]*k,value[1]*k*' + jzN(sy) + ']');
            jzSetExpr(jzXf(Cp, 'ADBE Opacity'), zh + 'value*oc((time-0.05)/0.4)*K*cl((z-1)/0.6)*cl((' + (KC + 1) + '-z)/1.2)*(0.8-0.45*z/' + (KC + 1) + ')');
            if (side) jzAnimator(Cp, 'JZ Recede Skew', [['ADBE Text Skew', -vx * 14]], zh + '(1-k)*100');
            Cp.moveAfter(L); jzNoGhost(Cp);                 // ghost: false in the browser
        }
        jzAnimate(ctx, L, { mi: 0 });
        return jzBB(L);
    }
});

/* ---------------------------------------------------------------- 19 hanko \u2014 \u843D\u6B3E */
function la2_sealGlyphs(text) {
    var all = jzChars(jzStrip(text)), g = [], kan = [], i;
    for (i = 0; i < all.length; i++) if (!jzIsPunct(all[i]) && !la2_isSmall(all[i])) { g.push(all[i]); if (jzIsKanji(all[i])) kan.push(all[i]); }
    if (kan.length >= 2 || (kan.length === 1 && g.length > 4)) return kan.slice(0, 4);
    if (g.length <= 4) return g.length ? g : all.slice(0, 1);
    return g.slice(0, 2);
}
// the seal stamp: jittered square (\u767D\u6587 = filled, glyphs knocked out / \u6731\u6587 = outline, red glyphs); thumps in after the lyric landed
function la2_seal(ctx, sx, sy, S) {
    var sc = ctx.sc, c = ctx.cut, seed = c.seed || 0, haku = jzP(ctx, 'seal', 'haku') !== 'shu', i;
    var T0 = (c.inDur || 0.3) * 0.7 + 0.08;
    var hx = la2_hd(ctx) + 'var x=(time-' + jzN(T0) + ')/0.2;';
    var X = jzShapeLayer(ctx, 'seal', sx, sy);
    if (haku) {
        var gs0 = jzGrp(X, 'specks');
        for (i = 0; i < 12; i++) { var rr = S * (0.004 + 0.008 * jzR(seed, i, 7)); jzAddEllipse(gs0, rr * 2, rr * 2, (jzR(seed, i, 5) * 2 - 1) * S * 0.46, (jzR(seed, i, 6) * 2 - 1) * S * 0.46); }
        jzAddFill(gs0, sc.bg, 90);
    }
    var pts = [], M = 11, h = S / 2;
    function jit(a, b) { return (jzR(seed, a, b, 3) * 2 - 1) * S * 0.014; }
    for (i = 0; i < M; i++) pts.push([-h + S * i / M, -h + jit(i, 1)]);
    for (i = 0; i < M; i++) pts.push([h + jit(i, 2), -h + S * i / M]);
    for (i = 0; i < M; i++) pts.push([h - S * i / M, h + jit(i, 3)]);
    for (i = 0; i < M; i++) pts.push([-h + jit(i, 4), h - S * i / M]);
    var gq = jzGrp(X, 'stamp'); jzAddPath(gq, pts, true);
    if (haku) jzAddFill(gq, sc.accent); else { var st = jzAddStroke(gq, sc.accent, S * 0.07); try { st.property('ADBE Vector Stroke Line Join').setValue(2); } catch (e) {} }
    var opE = hx + 'x<=0?0:value*cl(x*3)*K';
    // seal glyphs: right column first, top to bottom; each a text layer parented to the stamp
    var G = la2_sealGlyphs(ctx.cut.text), gn = G.length, cols = gn >= 3 ? 2 : 1, rows = Math.ceil(gn / cols);
    var inner = S * (haku ? 0.8 : 0.72), cw = inner / cols, chh = inner / rows, gsz = Math.min(cw, chh) * 0.92, gsx = jzClamp(cw / chh, 0.8, 1.7);
    if (gsz * gsx > cw) gsx = cw / gsz;
    var sf = jzFontOf(ctx, 'sealFont', 'serif');
    for (i = 0; i < gn; i++) {
        var col = cols - 1 - Math.floor(i / rows), row = i % rows;
        var T = jzText(ctx, G[i], { font: sf, size: gsz, color: haku ? sc.bg : sc.accent, x: sx - inner / 2 + cw * (col + 0.5), y: sy - inner / 2 + chh * (row + 0.5), sx: gsx, name: 'seal glyph' });
        T.parent = X;                                   // (parent before any transform expression exists)
        jzNoGhost(T);                                   // glyphs: ghost off (the stamp is ghosted)
        jzSetExpr(jzXf(T, 'ADBE Opacity'), opE);
    }
    jzXf(X, 'ADBE Rotate Z').setValue(jzP(ctx, 'rot', 0));
    jzSetExpr(jzXf(X, 'ADBE Scale'), hx + 'var s=x<=0?0:(1.55+(1-1.55)*ob(cl(x),1.4))*100;[s,s]');
    jzSetExpr(jzXf(X, 'ADBE Position'), hx + 'var sh=0;if(x>1&&x<1.8){var k=1.8-x;seedRandom(Math.floor(time*12)+91,true);sh=random(-1,1)*' + jzN(S * 0.015) + '*k;}[value[0]+sh,value[1]]');
    jzSetExpr(jzXf(X, 'ADBE Opacity'), opE);
    return X;
}
jzReg('layout', 'hanko', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['serif'])), sealFont: rng.pick(jzFontsOf(st, ['serif', 'display'])), vert: cut.H > cut.W * 1.08 ? true : rng.chance(0.6), seal: rng.pick(['haku', 'shu', 'haku']), rot: rng.range(-7, 7) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx);
        var font = jzFontOf(ctx, 'font', 'serif'), text = jzStrip(c.text), n = jzCount(text), bb, S, sx, sy, L;
        if (jzP(ctx, 'vert', port)) {
            var cols = n > (port ? 8 : 6) ? 2 : 1, vt = cols > 1 ? la2_lines(la2_brk(text, Math.ceil(n / 2))) : [text], per = 1, i;
            for (i = 0; i < vt.length; i++) per = Math.max(per, jzCount(vt[i]));
            cols = vt.length;
            var size = Math.min(H * 0.7 / Math.max(per, 2.5), u * 0.2, W * 0.3 / cols);
            S = size * (n <= 2 ? 1.35 : 1.5);
            var lead = 1.35, bw = size * (1 + (cols - 1) * lead), colH = per * size;
            var x = W / 2 + S * 0.45 + (cols - 1) * size * lead / 2, top = H / 2 - colH / 2 - S * 0.25;
            L = la2_vcols(ctx, vt, { font: font, size: size, color: sc.fg, x: x, y: top, top: true, lead: lead, name: text }).L;
            jzAnimate(ctx, L, { mi: 0 });
            bb = la2_bb(x - bw / 2, top, x + bw / 2, top + colH);
            sx = x - bw / 2 - S * 0.72; sy = Math.max(top + jzCount(vt[vt.length - 1]) * size, top + S * 0.5) + S * 0.1;
        } else {
            var sz0 = Math.min(la2_fitSize(ctx, text, font, W * 0.6, H * 0.3), u * 0.17);
            S = sz0 * 1.7;
            L = jzText(ctx, text, { font: font, size: sz0, color: sc.fg, x: W / 2 - S * 0.5, y: H / 2 });
            jzAnimate(ctx, L, { mi: 0 });
            bb = jzBB(L);
            sx = bb.x1 + S * 0.75; sy = H / 2 + sz0 * 0.3;
        }
        la2_seal(ctx, sx, sy, S);
        return bb;
    }
});

/* ---------------------------------------------------------------- 20 genkou \u2014 \u539F\u7A3F\u7528\u7D19 */
jzReg('layout', 'genkou', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['serif'])), lineC: rng.pick(['accent', 'sub']), indent: rng.chance(0.5), pad: rng.int(2, 4) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, port = jzPortrait(ctx), k, i;
        var chars = jzChars(jzStrip(c.text)), n = chars.length, ind = jzP(ctx, 'indent', false) ? 1 : 0;
        if (!n) { chars = ['\u3000']; n = 1; }
        var maxR = port ? 14 : 10, used = Math.max(1, Math.ceil((n + ind) / maxR));
        var R = Math.max(7, Math.ceil((n + ind) / used) + (used === 1 && n + ind < maxR ? 1 : 0));
        var gapK = 0.3, cell = Math.min(H * 0.78 / R, W * 0.9 / ((used + 2) * (1 + gapK))), colW = cell * (1 + gapK);
        var C = Math.max(used + 2, Math.min(Math.floor(W * 0.92 / colW), used + jzP(ctx, 'pad', 3) * 2));
        var gw = C * colW - cell * gapK, gh = R * cell, gx0 = W / 2 - gw / 2, gy0 = H / 2 - gh / 2, cStart = Math.floor((C - used) / 2);
        function colX(q) { return gx0 + gw - cell - q * colW; }                    // left edge of column q (counted from the right)
        var lc = jzP(ctx, 'lineC', 'accent') === 'accent' ? sc.accent : sc.sub, lw = Math.max(1.5 * ctx.u, cell * 0.02), hd = la2_hd(ctx);
        // the grid: each column unrolls downwards (sides trimmed, rungs added by a repeater as the sides pass them)
        var G = jzNoGhost(jzShapeLayer(ctx, 'genkou grid', 0, 0));
        for (k = 0; k < C; k++) {
            var x = colX(k), dist = Math.abs(k - (cStart + (used - 1) / 2)) / Math.max(1, C / 2);
            var eX = hd + 'var e=ioc((time-' + jzN(k * 0.035) + ')/0.45)*K;';
            var gc = jzGrp(G, 'column ' + (k + 1));
            var gs = la2_sub(gc, 'sides'); jzAddPath(gs, [[x, gy0], [x, gy0 + gh]], false); jzAddPath(gs, [[x + cell, gy0], [x + cell, gy0 + gh]], false);
            jzAddStroke(gs, lc, lw); jzAddTrimPaths(gs, eX + '100*e');
            var gr = la2_sub(gc, 'rungs'); jzAddPath(gr, [[x, gy0], [x + cell, gy0]], false); jzAddStroke(gr, lc, lw, 85);
            var rp = jzVecs(gr).addProperty('ADBE Vector Filter - Repeater');
            jzSetExpr(rp.property('ADBE Vector Repeater Copies'), eX + 'e<=0?0:Math.min(' + (R + 1) + ',Math.floor(e*' + R + '+' + jzN(0.5 / cell) + ')+1)');
            rp.property('ADBE Vector Repeater Transform').property('ADBE Vector Repeater Position').setValue([0, cell]);
            jzGX(gc).property('ADBE Vector Group Opacity').setValue(85 * (1 - dist * 0.6));
        }
        // footer: (rows \u00D7 columns) and line number
        var fs = Math.max(11 * ctx.u, cell * 0.14), mono = jzMonoF(ctx);
        la2_io(ctx, jzText(ctx, '(' + R + '\u00D7' + C + ')', { font: mono, size: fs, color: lc, x: gx0, y: gy0 + gh + fs * 1.4, align: 'left' }), 0.3, 0.4, 0, 0, 0.8);
        la2_io(ctx, jzText(ctx, 'No.' + jzLineNo(ctx), { font: mono, size: fs, color: lc, x: gx0 + gw, y: gy0 + gh + fs * 1.4, align: 'right' }), 0.3, 0.4, 0, 0, 0.8);
        // the lyric: one text layer, one glyph per cell (top to bottom, right to left)
        var cells = [];
        for (i = 0; i < n; i++) {
            var q = i + ind, col = cStart + Math.floor(q / R), r = q % R, ch = chars[i], gx = colX(col) + cell / 2, gy = gy0 + (r + 0.5) * cell, rot = 0;
            if (la2_isSmall(ch)) { gx += cell * 0.1; gy -= cell * 0.1; }
            if (LA2_VPUNCT.indexOf(ch) >= 0) { gx += cell * 0.28; gy -= cell * 0.28; }
            if (LA2_VROT.indexOf(ch) >= 0 || jzIsLatin(ch)) rot = 90;
            cells.push([gx, gy, rot]);
        }
        var bx0 = colX(cStart + used - 1), bx1 = colX(cStart) + cell;
        var L = la2_cells(ctx, chars, cells, { font: jzFontOf(ctx, 'font', 'serif'), size: cell * 0.8, color: sc.fg, px: (bx0 + bx1) / 2, py: gy0 + gh / 2, name: jzStrip(c.text) });
        jzAnimate(ctx, L, { mi: 0 });
        // the browser writes glyph i as its own item (delay i x stagger): hold each glyph back until its turn
        jzAnimator(L, 'JZ Write Order', [['ADBE Text Opacity', 0]], 'time<(textIndex-1)*' + jzN(c.stagger || 0.04) + '?100:0');
        return la2_bb(bx0, gy0, bx1, gy0 + gh);
    }
});

/* ---------------------------------------------------------------- 21 panels \u2014 \u30B3\u30DE\u5272\u308A */
function la2_mask(L, pts) {
    var m = L.property('ADBE Mask Parade').addProperty('ADBE Mask Atom'), sh = new Shape();
    sh.vertices = pts; sh.closed = true;
    m.property('ADBE Mask Shape').setValue(sh);
    return m;
}
// Linear Wipe that reveals an identity-transform layer between comp x (or y) lo..hi as `eExpr` goes 0 -> 1
// fromRight: reveal right -> left (landscape reading order); else top -> bottom
function la2_reveal(ctx, L, lo, hi, eExpr, fromRight) {
    var lw = jzEffect(L, 'ADBE Linear Wipe', 'JZ Reveal');
    jzEP(lw, 2, fromRight ? 90 : 0); jzEP(lw, 3, 0);
    var edge = 'var ed=' + jzN(fromRight ? hi : lo) + '+(' + jzN(fromRight ? lo - hi : hi - lo) + ')*e;';
    jzEX(lw, 1, la2_hd(ctx) + eExpr + edge + (fromRight ? '100*ed/' + jzN(ctx.W) : '100*(1-ed/' + jzN(ctx.H) + ')'));
    return lw;
}
jzReg('layout', 'panels', {
    plan: function (rng, cut, st) {
        var k = cut.n >= 6 ? 3 : 2;
        return { chunks: la2_splitK(cut.text, k, 2), font: rng.pick(jzFontsOf(st, ['display', 'serif'])), acc: rng.int(0, 2), fx: rng.pick(['focus', 'tone', 'focus', 'none']), slant: rng.range(5, 11), widths: [rng.range(0.8, 1.25), rng.range(0.8, 1.25), rng.range(0.8, 1.25)] };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), i, j;
        var ch = jzP(ctx, 'chunks', null);
        if (!ch || !ch.length) ch = la2_splitK(c.text, 2, 2);
        var k = ch.length, m = u * 0.05, g = u * 0.028, lw = Math.max(3 * ctx.u, u * 0.0065);
        var font = jzFontOf(ctx, 'font', 'display'), acc = jzP(ctx, 'acc', 0) % k, fx = jzP(ctx, 'fx', 'focus'), widths = jzP(ctx, 'widths', [1, 1, 1]);
        var Lr = port ? H - 2 * m : W - 2 * m, cross = port ? W - 2 * m : H - 2 * m;
        var ws = [], tot = 0;
        for (i = 0; i < k; i++) { ws.push(Math.sqrt(jzCount(ch[i]) + 2) * (widths[i % 3] || 1)); tot += ws[i]; }
        var sl = Math.min(Math.tan(jzP(ctx, 'slant', 8) * Math.PI / 180) * cross / 2, Lr / k * 0.18);
        var bounds = [0], acc2 = 0;
        for (i = 0; i < k; i++) { acc2 += ws[i]; bounds.push(acc2 / tot * Lr); }
        var stag = Math.max(0.01, c.stagger || 0.04), bb = null, inkC = la2_onCol(sc, sc.ink), hd = la2_hd(ctx);
        for (i = 0; i < k; i++) {
            var a = bounds[i], b = bounds[i + 1];
            var sa = i === 0 ? 0 : sl * (i % 2 ? 1 : -1), sb = i === k - 1 ? 0 : sl * ((i + 1) % 2 ? 1 : -1);
            var a0 = a + (i === 0 ? 0 : g / 2), b0 = b - (i === k - 1 ? 0 : g / 2);
            var q = [[a0 + sa, 0], [b0 + sb, 0], [b0 - sb, cross], [a0 - sa, cross]], poly = [], cx = 0, cy = 0;
            for (j = 0; j < 4; j++) { poly.push(port ? [m + q[j][1], m + q[j][0]] : [W - m - q[j][0], m + q[j][1]]); cx += poly[j][0] / 4; cy += poly[j][1] / 4; }
            var along = Math.min((b0 + sb) - (a0 + sa), (b0 - sb) - (a0 - sa));
            var lo = 1e9, hi = -1e9;
            for (j = 0; j < 4; j++) { var vv = port ? poly[j][1] : poly[j][0]; lo = Math.min(lo, vv); hi = Math.max(hi, vv); }
            var d = 0.06 + i * 0.16, eE = 'var e=ioc((time-' + jzN(d) + ')/0.3);';
            var accent = i === acc;
            // panel: plate (accent) + border, revealed along the reading direction
            var Pn = jzNoGhost(jzShapeLayer(ctx, 'panel ' + (i + 1), 0, 0)), gp = jzGrp(Pn, 'panel');
            jzAddPath(gp, poly, true); jzAddStroke(gp, sc.fg, lw); if (accent) jzAddFill(gp, sc.ink);
            la2_reveal(ctx, Pn, lo, hi, eE, !port);
            jzSetExpr(jzXf(Pn, 'ADBE Opacity'), hd + 'value*K');
            var pw = port ? cross : along, ph = port ? along : cross, iw = Math.max(10, pw * 0.8), ih = Math.max(10, ph * 0.8);
            var vert = ih > iw * 1.25 && !jzHasLatin(ch[i]);
            var c0 = vert ? jzStrip(ch[i]) : jzTrim(ch[i]), cn = jzCount(c0), txt = c0, size, lines;
            if (vert) {
                lines = [c0]; size = Math.min(iw, ih / Math.max(1, cn));
                if (cn >= 4) { var l2 = la2_lines(la2_brk(c0, Math.ceil(cn / 2))), mx2 = 1; for (j = 0; j < l2.length; j++) mx2 = Math.max(mx2, jzCount(l2[j])); var s2 = Math.min(iw / (1 + (l2.length - 1) * 1.15), ih / mx2); if (s2 > size * 1.15) { lines = l2; size = s2; } }
            } else {
                size = la2_fitSize(ctx, c0, font, iw, ih, { lead: 1.15 });
                if (cn >= 4) { var t2 = la2_brk(c0, Math.ceil(cn / 2)), s3 = la2_fitSize(ctx, t2, font, iw, ih, { lead: 1.15 }); if (s3 > size * 1.15) { txt = t2; size = s3; } }
            }
            size = Math.min(size, u * 0.24);
            // effects inside the panel (clipped by a mask with the panel's outline)
            if (accent && fx === 'focus') {
                var Fx = jzNoGhost(jzShapeLayer(ctx, 'speed lines', 0, 0)), R0 = Math.sqrt(pw * pw + ph * ph);
                var tw = vert ? cn * size : la2_width(ctx, txt.split('\r')[0], font, size, 0);
                jzGrp(Fx, 'thin'); jzGrp(Fx, 'mid'); jzGrp(Fx, 'bold');           // (all three first, then re-fetched: adding a group invalidates its siblings)
                var rin = size * (vert ? 0.9 : 0.75) + Math.max(0, tw * 0.35), gl = [la2_top(Fx, 'thin'), la2_top(Fx, 'mid'), la2_top(Fx, 'bold')];
                for (j = 0; j < 48; j++) {
                    var ang = (j / 48 + jzR(c.seed, j, 61) * 0.01) * Math.PI * 2, r1 = rin * (1 + jzR(c.seed, j, 62) * 0.5), wk = jzR(c.seed, j, 63);
                    jzAddPath(gl[wk < 0.33 ? 0 : wk < 0.66 ? 1 : 2], [[cx + Math.cos(ang) * R0, cy + Math.sin(ang) * R0], [cx + Math.cos(ang) * r1, cy + Math.sin(ang) * r1]], false);
                }
                for (j = 0; j < 3; j++) jzAddStroke(gl[j], inkC, (1.4 + j * 1.2) * ctx.u, 22);
                la2_mask(Fx, poly); la2_reveal(ctx, Fx, lo, hi, eE, !port);
                jzSetExpr(jzXf(Fx, 'ADBE Opacity'), hd + 'value*K');
            }
            if (!accent && fx === 'tone' && i === (acc + 1) % k) {
                var cellT = Math.max(4 * ctx.u, u * 0.011), x0 = 1e9, y0 = 1e9, x1 = -1e9, y1 = -1e9;
                for (j = 0; j < 4; j++) { x0 = Math.min(x0, poly[j][0]); x1 = Math.max(x1, poly[j][0]); y0 = Math.min(y0, poly[j][1]); y1 = Math.max(y1, poly[j][1]); }
                var To = jzNoGhost(jzShapeLayer(ctx, 'screentone', 0, 0)), gt = jzGrp(To, 'tone'), gd = la2_sub(gt, 'dots');
                jzAddEllipse(gd, cellT * 0.68, cellT * 0.68, x0 + cellT / 2, y0 + cellT / 2); jzAddFill(gd, sc.sub);
                var r1p = jzVecs(gd).addProperty('ADBE Vector Filter - Repeater');
                r1p.property('ADBE Vector Repeater Copies').setValue(Math.ceil((x1 - x0) / cellT) + 1);
                r1p.property('ADBE Vector Repeater Transform').property('ADBE Vector Repeater Position').setValue([cellT, 0]);
                var r2p = jzVecs(gt).addProperty('ADBE Vector Filter - Repeater');
                r2p.property('ADBE Vector Repeater Copies').setValue(Math.ceil((y1 - y0) / cellT) + 1);
                r2p.property('ADBE Vector Repeater Transform').property('ADBE Vector Repeater Position').setValue([0, cellT]);
                la2_mask(To, poly); la2_reveal(ctx, To, lo, hi, eE, !port);
                jzXf(To, 'ADBE Opacity').setValue(30);
                jzSetExpr(jzXf(To, 'ADBE Opacity'), hd + 'value*K');
            }
            // the chunk (clipped to the revealed panel by an alpha matte copy of the panel)
            var col = accent ? inkC : sc.fg, T, tb;
            if (vert) { var vc = la2_vcols(ctx, lines, { font: font, size: size, color: col, x: cx, y: cy, lead: 1.15, name: c0 }); T = vc.L; tb = vc.bb; }
            else { T = la2_txt(ctx, txt, { font: font, size: size, color: col, x: cx, y: cy, lead: 1.15 }); tb = jzBB(T); }
            jzAnimate(ctx, T, { mi: (d + 0.08) / stag });
            var Mt = jzShapeLayer(ctx, 'panel clip ' + (i + 1), 0, 0), gm = jzGrp(Mt, 'clip');
            jzAddPath(gm, poly, true); jzAddFill(gm, '#FFFFFF');
            la2_reveal(ctx, Mt, lo, hi, eE, !port);
            Mt.moveBefore(T);
            try { T.trackMatteType = TrackMatteType.ALPHA; } catch (e1) { jzWarn('panels matte: ' + e1.toString()); }
            bb = jzUnion(bb, tb);
        }
        return bb || la2_bb(m, m, W - m, H - m);
    }
});

/* ---------------------------------------------------------------- 22 filmstrip \u2014 \u30D5\u30A3\u30EB\u30E0 */
jzReg('layout', 'filmstrip', {
    plan: function (rng, cut, st) {
        return { chunks: la2_splitK(cut.text, Math.min(3, cut.n), cut.n <= 4 ? 3 : 2), font: rng.pick(jzFontsOf(st, ['display', 'serif'])), dir: rng.pick([1, -1]), tone: rng.pick(['ink', 'fg']), codes: rng.chance(0.75), tilt: rng.pick([0, 0, -3, 3]) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), v = jzPortrait(ctx), i;
        var ch = jzP(ctx, 'chunks', null);
        ch = ch && ch.length ? ch.slice(0, 3) : [jzTrim(c.text)];
        var slots = ch.length === 3 ? ch : ch.length === 2 ? [ch[0], ch[1], null] : [null, ch[0], null];
        var A = v ? H : W, Bd = v ? W : H;
        var fw0 = Math.min(A * 0.88 / 3.2, Bd * 0.62 * 1.3), fh = fw0 / 1.3, band = fh / 0.72, gap = fw0 * 0.08, pitch = fw0 + gap;
        var stripC = jzP(ctx, 'tone', 'ink') === 'fg' ? sc.fg : sc.ink, oc0 = la2_onCol(sc, sc.bg), tcol = oc0 === sc.bg ? sc.fg : oc0;
        var dir = jzP(ctx, 'dir', 1) < 0 ? -1 : 1, font = jzFontOf(ctx, 'font', 'display'), hd = la2_hd(ctx);
        function P2(a, b) { return v ? [b, a] : [a, b]; }          // (along, across) from the strip centre -> px
        var fwX = v ? fh : fw0, fhY = v ? fw0 : fh;
        // the strip: frames, sprocket holes and band in one shape layer; it slides in, drifts, and leaves the other way
        var S = jzNoGhost(jzShapeLayer(ctx, 'film strip', W / 2, H / 2));
        for (i = 0; i < 3; i++) { var gf = jzGrp(S, 'frame ' + (i + 1)), pf = P2((i - 1) * pitch, 0); jzAddRect(gf, fwX, fhY, fh * 0.04, pf[0], pf[1]); jzAddFill(gf, sc.bg); }
        var bandLen = A * 1.6, hp = band * 0.11, hw = hp * 0.55, hh = hp * 0.72, hm = (band - fh) / 4, nH = Math.min(120, Math.ceil(bandLen / hp));
        for (var sd = -1; sd <= 1; sd += 2) {
            var gh = jzGrp(S, 'sprockets'), ph0 = P2(-bandLen / 2 + hp / 2, sd * (band / 2 - hm)), hs = P2(hw, hh);
            jzAddRect(gh, hs[0], hs[1], hw * 0.25, ph0[0], ph0[1]); jzAddFill(gh, sc.bg);
            var rp = jzVecs(gh).addProperty('ADBE Vector Filter - Repeater');
            rp.property('ADBE Vector Repeater Copies').setValue(nH);
            rp.property('ADBE Vector Repeater Transform').property('ADBE Vector Repeater Position').setValue(P2(hp, 0));
        }
        var gb = jzGrp(S, 'band'), bs = P2(bandLen, band); jzAddRect(gb, bs[0], bs[1]); jzAddFill(gb, stripC);
        var faE = '(1-sm(0.55,1,PO))', laE = 'cl(oe(time/0.6)*2-1)*' + faE;
        var kids = [], lyr = [];
        // edge codes
        if (jzP(ctx, 'codes', true)) {
            var cs = Math.max(10 * ctx.u, hm * 0.9);
            for (i = -2; i <= 2; i++) {
                var pc = P2(i * pitch - pitch / 2, (band / 2 - hm * 2.2) * (v ? 1 : -1));
                var Tc = jzText(ctx, '\u25B8' + (12 + i + (c.index || 0)) + (i % 2 ? 'A' : ''), { font: jzMonoF(ctx), size: cs, color: sc.accent, x: W / 2 + pc[0], y: H / 2 + pc[1], rot: v ? 90 : 0, opacity: 0.9 });
                la2_op(ctx, Tc, laE); kids.push(Tc);
            }
        }
        // frames: one type size for the whole strip; empty frames get a countdown leader
        var fsz = u * 0.2;
        for (i = 0; i < 3; i++) if (slots[i]) fsz = Math.min(fsz, la2_fitSize(ctx, la2_brk(slots[i], v ? 6 : 4), font, fwX * 0.8, fhY * 0.7, { lead: 1.1 }));
        var stag = Math.max(0.01, c.stagger || 0.04), bb = null;
        for (i = 0; i < 3; i++) {
            var p = P2((i - 1) * pitch, 0), fx = W / 2 + p[0], fy = H / 2 + p[1];
            if (slots[i]) {
                var T = la2_txt(ctx, la2_brk(slots[i], v ? 6 : 4), { font: font, size: fsz, lead: 1.1, color: tcol, x: fx, y: fy });
                bb = jzUnion(bb, jzBB(T));
                kids.push(T); lyr.push([T, 0.15 * i / stag]);
            } else {
                var r = Math.min(fwX, fhY) * 0.3, Ld = jzShapeLayer(ctx, 'leader', fx, fy);
                var g1 = jzGrp(Ld, 'ring'); jzAddEllipse(g1, r * 2, r * 2); jzAddStroke(g1, sc.sub, 1.5 * ctx.u, 50);
                var g2 = jzGrp(Ld, 'cross'); jzAddPath(g2, [[-r * 1.4, 0], [r * 1.4, 0]], false); jzAddPath(g2, [[0, -r * 1.4], [0, r * 1.4]], false); jzAddStroke(g2, sc.sub, 1.2 * ctx.u, 40);
                la2_op(ctx, Ld, laE);
                var Tn = jzText(ctx, String(i === 0 ? 3 : 1), { font: font, size: r * 1.1, color: sc.sub, x: fx, y: fy, opacity: 0.6 });
                la2_op(ctx, Tn, laE);
                kids.push(Ld, Tn);
            }
        }
        for (i = 0; i < kids.length; i++) kids[i].parent = S;   // parent first, then every transform expression
        jzXf(S, 'ADBE Rotate Z').setValue(jzP(ctx, 'tilt', 0));
        var offE = hd + 'var off=(1-oe(time/0.6))*' + jzN(A * 0.9 * dir) + '-ie(PO)*' + jzN(A * 1.4 * dir) + '+(time-DUR/2)*' + jzN(u * 0.03 * dir) + ';';
        jzSetExpr(jzXf(S, 'ADBE Anchor Point'), offE + (v ? '[value[0],value[1]-off]' : '[value[0]-off,value[1]]'));
        jzSetExpr(jzXf(S, 'ADBE Opacity'), hd + 'value*' + faE);
        for (i = 0; i < lyr.length; i++) jzAnimate(ctx, lyr[i][0], { mi: lyr[i][1] });
        S.moveToEnd();
        return bb || la2_bb(W / 2 - fwX * 1.6 / 2, H / 2 - fhY / 2, W / 2 + fwX * 1.6 / 2, H / 2 + fhY / 2);
    }
});

/* ---------------------------------------------------------------- 23 quote \u2014 \u5F15\u7528 */
jzReg('layout', 'quote', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['serif', 'display'])), markFont: rng.pick(jzFontsOf(st, ['serif'])), marks: /[A-Za-z]/.test(cut.text) ? 'latin' : rng.pick(['kagi', 'double', 'kagi']), markC: rng.pick(['accent', 'sub']), attrib: rng.chance(0.75) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx);
        var attrib = jzP(ctx, 'attrib', true), hd = la2_hd(ctx);
        var L = la2_txt(ctx, la2_brk(c.text, port ? 6 : 11), { font: jzFontOf(ctx, 'font', 'serif'), size: 100, color: sc.fg, x: W / 2, y: H / 2, track: 0.04, lead: 1.25, maxW: W * (port ? 0.58 : 0.62), maxH: H * 0.4, maxSize: u * 0.16 });
        var size = jzFontSize(L), sz = jzSize(L), mw = sz[0], mh = sz[1], cy = H / 2 - (attrib ? size * 0.25 : 0);
        jzXf(L, 'ADBE Position').setValue([W / 2, cy]);
        var bx0 = W / 2 - mw / 2, bx1 = W / 2 + mw / 2, by0 = cy - mh / 2, by1 = cy + mh / 2;
        var mc = jzP(ctx, 'markC', 'accent') === 'accent' ? sc.accent : sc.sub, marks = jzP(ctx, 'marks', 'kagi'), latin = marks === 'latin';
        var o1 = latin ? '\u201C' : marks === 'double' ? '\u300E' : '\u300C', c1 = latin ? '\u201D' : marks === 'double' ? '\u300F' : '\u300D';
        var mf = jzFontOf(ctx, 'markFont', 'serif');
        // big quotation marks: placed by their ink boxes, fly in diagonally from far away
        var Mo = jzText(ctx, o1, { font: mf, size: 100, color: mc, x: 0, y: 0 }), Mc = jzText(ctx, c1, { font: mf, size: 100, color: mc, x: 0, y: 0 });
        var gap = size * 0.2, inkW = jzClamp(Math.max(jzRect(Mo).width, jzRect(Mc).width) / 100, 0.05, 0.55);   // ink width in em (capped: some renderers report the advance)
        var ms = Math.min(Math.max(size * (latin ? 3.2 : 4), mh * 1.8), u * 0.5, (W * 0.94 - mw - gap * 2) / (2 * inkW));
        jzTextDoc(Mo, function (td) { td.fontSize = ms; }); jzTextDoc(Mc, function (td) { td.fontSize = ms; });
        var ra = jzRect(Mo), rb = jzRect(Mc);
        jzXf(Mo, 'ADBE Anchor Point').setValue([ra.left + ra.width, ra.top]); jzXf(Mo, 'ADBE Position').setValue([bx0 - gap, by0 - size * 0.1]);
        if (latin) { jzXf(Mc, 'ADBE Anchor Point').setValue([rb.left, rb.top]); jzXf(Mc, 'ADBE Position').setValue([bx1 + gap, by1 - size * 0.95]); }
        else { jzXf(Mc, 'ADBE Anchor Point').setValue([rb.left, rb.top + rb.height]); jzXf(Mc, 'ADBE Position').setValue([bx1 + gap, by1 + size * 0.1]); }
        var eM = hd + 'var e=oe((time-0.02)/0.5)*(1-ic(PO));var f=' + jzN(u * 0.35) + '*(1-e);';
        jzSetExpr(jzXf(Mo, 'ADBE Position'), eM + '[value[0]-f,value[1]-f*0.6]');
        jzSetExpr(jzXf(Mc, 'ADBE Position'), eM + '[value[0]+f,value[1]+f*0.6]');
        jzSetExpr(jzXf(Mo, 'ADBE Opacity'), eM + 'value*cl(e*1.5)'); jzSetExpr(jzXf(Mc, 'ADBE Opacity'), eM + 'value*cl(e*1.5)');
        Mo.moveAfter(L); Mc.moveAfter(L); la2_ng([Mo, Mc]);
        // attribution: \u2014 ROMAJI / No.xx
        if (attrib) {
            var ls = jzSmallSize(ctx), body = jzBodyF(ctx), at = jzRomajiOf(ctx) || 'No.' + jzLineNo(ctx), ay = by1 + size * 0.95;
            var aw = Math.min(W * 0.4, la2_width(ctx, '\u2014 ' + at, body, ls, 0.12));
            la2_io(ctx, jzText(ctx, at, { font: body, size: ls, track: 0.12, color: sc.sub, x: W / 2, y: ay }), 0.35, 0.4, 0, 0, 1);
            la2_io(ctx, jzPathLayer(ctx, 'attribution dash', [[W / 2 - aw / 2 - ls * 2, ay], [W / 2 - aw / 2 - ls * 0.8, ay]], mc, { width: 1.5 * ctx.u }), 0.35, 0.4, 0, 0, 1);
        }
        jzAnimate(ctx, L, { mi: 0 });
        return la2_bb(bx0, by0, bx1, by1);
    }
});

/* ---------------------------------------------------------------- 24 ruler \u2014 \u5BF8\u6CD5\u7DDA */
function la2_arrow(g, s, dir) {       // arrow head with its tip at the group origin; dir: 'l' 'r' 'u' 'd'
    var p = dir === 'r' ? [[0, 0], [-s, -s * 0.35], [-s, s * 0.35]] : dir === 'l' ? [[0, 0], [s, -s * 0.35], [s, s * 0.35]] : dir === 'u' ? [[0, 0], [-s * 0.35, s], [s * 0.35, s]] : [[0, 0], [-s * 0.35, -s], [s * 0.35, -s]];
    jzAddPath(g, p, true);
}
jzReg('layout', 'ruler', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif', 'body'])), dims: rng.pick(['topRight', 'bottomLeft']), ticks: rng.chance(0.7), guides: rng.chance(0.8), unit: rng.pick(['px', 'pt', 'px']) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), i;
        var font = jzFontOf(ctx, 'font', 'display'), text = la2_brk(c.text, port ? 6 : 12);
        var L = la2_txt(ctx, text, { font: font, size: 100, color: sc.fg, x: W / 2, y: H / 2, track: 0.04, lead: 1.15, maxW: W * 0.66, maxH: H * 0.34, maxSize: u * 0.19 });
        var size = jzFontSize(L), sz = jzSize(L), mw = sz[0], mh = sz[1], cx = W / 2, cy = H / 2;
        var x0 = cx - mw / 2, x1 = cx + mw / 2, y0 = cy - mh / 2, y1 = cy + mh / 2;
        var eX = la2_hd(ctx) + 'var e=ioc((time-0.08)/0.75)*K;';
        var lw = Math.max(1.2 * ctx.u, u * 0.0016), ls = jzSmallSize(ctx) * 0.9, ah = ls * 0.7, top = jzP(ctx, 'dims', 'topRight') === 'topRight';
        var unit = jzP(ctx, 'unit', 'px'), mono = jzMonoF(ctx);
        // dashed guides through the text's edges, growing from the centre
        if (jzP(ctx, 'guides', true)) {
            var G = jzNoGhost(jzShapeLayer(ctx, 'guides', 0, 0)), gg = jzGrp(G, 'guides'), gx = W * 0.47, gy = H * 0.47;
            jzAddPath(gg, [[x0, cy], [x0, cy - gy]], false); jzAddPath(gg, [[x0, cy], [x0, cy + gy]], false);
            jzAddPath(gg, [[x1, cy], [x1, cy - gy]], false); jzAddPath(gg, [[x1, cy], [x1, cy + gy]], false);
            jzAddPath(gg, [[cx, y0], [cx - gx, y0]], false); jzAddPath(gg, [[cx, y0], [cx + gx, y0]], false);
            jzAddPath(gg, [[cx, y1], [cx - gx, y1]], false); jzAddPath(gg, [[cx, y1], [cx + gx, y1]], false);
            var gs = jzAddStroke(gg, sc.sub, 1 * ctx.u, 35);
            la2_dash(gs, 8 * ctx.u, 8 * ctx.u);
            jzAddTrimPaths(gg, eX + '100*e');
        }
        // dimension lines with arrow heads (width above / below, height at the side)
        var D = jzNoGhost(jzShapeLayer(ctx, 'dimensions', 0, 0));
        var dy = top ? y0 - size * 0.55 : y1 + size * 0.55, ext = top ? -1 : 1, dx = top ? x1 + size * 0.5 : x0 - size * 0.5, ex = top ? 1 : -1;
        var ga = jzGrp(D, 'arrows'), arr = [['l', cx, dy, '[' + jzN(cx) + '-' + jzN(mw / 2) + '*e,' + jzN(dy) + ']'], ['r', cx, dy, '[' + jzN(cx) + '+' + jzN(mw / 2) + '*e,' + jzN(dy) + ']'],
            ['u', dx, cy, '[' + jzN(dx) + ',' + jzN(cy) + '-' + jzN(mh / 2) + '*e]'], ['d', dx, cy, '[' + jzN(dx) + ',' + jzN(cy) + '+' + jzN(mh / 2) + '*e]']];
        for (i = 0; i < 4; i++) { var gai = la2_sub(ga, 'arrow'); la2_arrow(gai, ah, arr[i][0]); jzAddFill(gai, sc.sub); la2_gxp(gai, eX + arr[i][3]); }
        var gdl = jzGrp(D, 'dimension lines');
        jzAddPath(gdl, [[cx, dy], [x0, dy]], false); jzAddPath(gdl, [[cx, dy], [x1, dy]], false);
        jzAddPath(gdl, [[dx, cy], [dx, y0]], false); jzAddPath(gdl, [[dx, cy], [dx, y1]], false);
        jzAddStroke(gdl, sc.sub, lw); jzAddTrimPaths(gdl, eX + '100*e');
        var gxl = jzGrp(D, 'extension lines'), ey = top ? y0 - size * 0.08 : y1 + size * 0.08, exx = top ? x1 + size * 0.08 : x0 - size * 0.08;
        jzAddPath(gxl, [[x0, ey], [x0, dy + ext * ls * 0.6]], false); jzAddPath(gxl, [[x1, ey], [x1, dy + ext * ls * 0.6]], false);
        jzAddPath(gxl, [[exx, y0], [dx + ex * ls * 0.6, y0]], false); jzAddPath(gxl, [[exx, y1], [dx + ex * ls * 0.6, y1]], false);
        jzAddStroke(gxl, sc.sub, lw, 80);
        jzSetExpr(jzXf(D, 'ADBE Opacity'), eX + 'e>0.0005?value:0');
        // counting labels
        var labs = [[Math.round(mw) + ' ' + unit, cx, dy + ext * ls * 0.95, 0, mw], [Math.round(mh) + ' ' + unit, dx + ex * ls * 1.1, cy, top ? 90 : -90, mh]];
        for (i = 0; i < 2; i++) {
            var Tl = jzText(ctx, labs[i][0], { font: mono, size: ls, track: 0.08, color: sc.accent, x: labs[i][1], y: labs[i][2], rot: labs[i][3] });
            try { Tl.property('ADBE Text Properties').property('ADBE Text Document').expression = eX + 'Math.round(' + jzN(labs[i][4]) + '*e)+" ' + unit + '"'; } catch (e1) { jzWarn('ruler label: ' + e1.toString()); }
            jzSetExpr(jzXf(Tl, 'ADBE Opacity'), eX + 'value*cl(e*2-0.3)'); jzNoGhost(Tl);
        }
        // glyph ticks under the last line (top) / over the first line (bottom) + glyph count
        if (jzP(ctx, 'ticks', true)) {
            var lines = la2_lines(text), ln = top ? lines[lines.length - 1] : lines[0], nl = Math.max(1, jzChars(ln).length);
            var lwid = la2_width(ctx, ln, font, size, 0.04), lx0 = cx - lwid / 2, lx1 = cx + lwid / 2;
            var ty = top ? y1 + size * 0.28 : y0 - size * 0.28, sg = top ? 1 : -1;
            var Tk = jzShapeLayer(ctx, 'glyph ticks', 0, 0), gbl = jzGrp(Tk, 'base');
            jzAddPath(gbl, [[lx0, ty], [lx1, ty]], false); jzAddStroke(gbl, sc.sub, lw, 70); jzAddTrimPaths(gbl, eX + '100*e');
            var gtk = jzGrp(Tk, 'ticks'); jzAddPath(gtk, [[lx0, ty], [lx0, ty + sg * ls * 0.5]], false); jzAddStroke(gtk, sc.sub, lw);
            var rp = jzVecs(gtk).addProperty('ADBE Vector Filter - Repeater');
            jzSetExpr(rp.property('ADBE Vector Repeater Copies'), eX + 'e<=0?0:Math.min(' + (nl + 1) + ',Math.floor(e*' + nl + '+0.0001)+1)');
            rp.property('ADBE Vector Repeater Transform').property('ADBE Vector Repeater Position').setValue([(lx1 - lx0) / nl, 0]);
            jzSetExpr(jzXf(Tk, 'ADBE Opacity'), eX + 'value*cl(e*1.6-0.4)');
            var Tn = jzText(ctx, 'n=' + jzCount(c.text), { font: mono, size: ls, track: 0.08, color: sc.sub, x: top ? lx0 : lx1, y: ty + sg * ls * 1.4, align: top ? 'left' : 'right' });
            jzSetExpr(jzXf(Tn, 'ADBE Opacity'), eX + 'value*cl(e*1.6-0.4)'); la2_ng([Tk, Tn]);
        }
        L.moveToBeginning();
        jzAnimate(ctx, L, { mi: 0 });
        return jzBB(L);
    }
});

/* ---------------------------------------------------------------- 25 searchBar \u2014 \u691C\u7D22\u7A93 */
// left-aligned text whose anchor is the line start at em-middle height (baseline-stable, leading spaces kept)
function la2_run(ctx, str, o) {
    var q = jzCopy(o); q.align = 'left';
    var L = jzText(ctx, str, q);
    jzXf(L, 'ADBE Anchor Point').setValue([0, -0.36 * (o.size || 100)]);
    return L;
}
function la2_magnifier(g, x, y, r, col, lw, op) {     // lens + handle (sub-groups of g)
    var a = la2_sub(g, 'lens'); jzAddEllipse(a, r * 1.24, r * 1.24, x - r * 0.15, y - r * 0.15); jzAddStroke(a, col, lw, op);
    var b = la2_sub(g, 'handle'); jzAddPath(b, [[x + r * 0.3, y + r * 0.3], [x + r * 0.8, y + r * 0.8]], false); jzAddStroke(b, col, lw * 1.2, op);
}
jzReg('layout', 'searchBar', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['body', 'display'])), shape: rng.pick(['pill', 'rect']), fill: rng.pick(['outline', 'filled']), sugg: rng.int(3, 4), pos: rng.pick(['center', 'upper']) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), i;
        var text = jzTrim(c.text), font = jzFontOf(ctx, 'font', 'body'), bw = W * (port ? 0.9 : 0.7);
        var ts = Math.min(la2_fitSize(ctx, text, font, bw - u * 0.22, u * 0.11, { track: 0.02 }), u * 0.095);
        var bh = ts * 2.1, rad = jzP(ctx, 'shape', 'pill') === 'pill' ? bh / 2 : bh * 0.16, by = H * (jzP(ctx, 'pos', 'center') === 'upper' ? 0.28 : 0.38);
        var filled = jzP(ctx, 'fill', 'outline') === 'filled', plate = sc.ink, tc = filled ? la2_onCol(sc, plate) : sc.fg, hd = la2_hd(ctx);
        var eW = hd + 'var e=oe(time/0.45)*K;var w=' + jzN(bw) + '*(0.3+0.7*e);';
        // the search box grows from 30 % to full width; lens rides the left end, the clear mark the right end
        var B = jzNoGhost(jzShapeLayer(ctx, 'search box', W / 2, by));
        var gm = jzGrp(B, 'lens'); la2_magnifier(gm, 0, 0, ts * 0.55, tc, Math.max(2 * ctx.u, ts * 0.06)); la2_gxp(gm, eW + '[-w/2+' + jzN(bh * 0.5) + ',0]');
        var gx = jzGrp(B, 'clear'), q = ts * 0.2; jzAddPath(gx, [[-q, -q], [q, q]], false); jzAddPath(gx, [[-q, q], [q, -q]], false); jzAddStroke(gx, tc, Math.max(1.5 * ctx.u, ts * 0.04), 60);
        la2_gxp(gx, eW + '[w/2-' + jzN(bh * 0.45) + ',0]');
        var gb = jzGrp(B, 'box'), rr = jzAddRect(gb, bw, bh, rad);
        jzSetExpr(rr.property('ADBE Vector Rect Size'), eW + '[w,' + jzN(bh) + ']');
        if (filled) jzAddFill(gb, plate); else jzAddStroke(gb, sc.fg, Math.max(2 * ctx.u, ts * 0.05));
        jzSetExpr(jzXf(B, 'ADBE Opacity'), eW + 'value*e');
        // the query (typed when the cut has no entrance of its own) + blinking caret
        var tx = W / 2 - bw / 2 + bh * 0.95;
        var L = jzText(ctx, text, { font: font, size: ts, track: 0.02, color: tc, x: tx, y: by, align: 'left' });
        var tw = jzSize(L)[0];
        jzAnimate(ctx, L, { mi: 0, enter: c.enter === 'cut' ? 'type' : undefined });
        var typed = (c.enter === 'cut' || c.enter === 'type') ? (c.inDur || 0.3) + 0.05 : (c.inDur || 0.3);
        var Cr = jzRectLayer(ctx, 'caret', tx + tw + ts * 0.12 + Math.max(2 * ctx.u, ts * 0.06) / 2, by, Math.max(2 * ctx.u, ts * 0.06), ts * 1.1, sc.accent);
        jzNoGhost(Cr);
        jzSetExpr(jzXf(Cr, 'ADBE Opacity'), hd + '(time>' + jzN(typed) + '&&K>0.5&&Math.floor(time*12)%2===0)?value:0');
        // suggestion panel drops open after typing; rows fade in one by one
        var rom = jzHasLatin(text) ? null : jzRomaji(jzStrip(text));
        var rows = [text + ' lyrics', c.lineText && jzStrip(c.lineText) !== jzStrip(text) ? c.lineText : text + ' meaning', rom ? rom.toLowerCase() : text + ' mv', text + ' cover'].slice(0, jzClamp(jzP(ctx, 'sugg', 3), 1, 4));
        var rs = ts * 0.52, rh = rs * 2.4, py = by + bh / 2 + rs * 0.8, t0 = Math.max(0.3, typed), ph = rows.length * rh + rs * 0.6;
        var pE = hd + 'var pa=oc((time-' + jzN(t0) + ')/0.3)*K;';
        var Pn = jzNoGhost(jzShapeLayer(ctx, 'suggestions', W / 2, py)), rowC = filled ? la2_onCol(sc, plate) : sc.sub;
        for (i = 0; i < rows.length; i++) {
            var y = rs * 0.3 + (i + 0.5) * rh, aE = pE + 'var a=cl((time-' + jzN(t0 + 0.05 + i * 0.07) + ')/0.2)*K;a=' + jzN((i + 1) * rh) + '>' + jzN(ph) + '*pa+0.5?0:a;';
            var gr = jzGrp(Pn, 'row ' + (i + 1)); la2_magnifier(gr, -bw / 2 + bh * 0.5, y, rs * 0.7, rowC, 1.5 * ctx.u, 70); la2_gxo(gr, aE + '100*a');
            var rt = rows[i], pre = rt.indexOf(text) === 0 ? text : '', rest = rt.substr(pre.length), xt = tx, pw = 0;
            if (pre) { var Tp = la2_run(ctx, pre, { font: font, size: rs, track: 0.02, color: filled ? rowC : sc.fg, x: xt, y: py + y }); var rp = jzRect(Tp); pw = rp.left + rp.width + rs * 0.02; jzSetExpr(jzXf(Tp, 'ADBE Opacity'), aE + 'value*a'); jzNoGhost(Tp); }
            if (rest) { var Tr = la2_run(ctx, rest, { font: jzBodyF(ctx), size: rs, track: 0.02, color: rowC, x: xt + pw, y: py + y, opacity: filled ? 0.65 : 1 }); jzSetExpr(jzXf(Tr, 'ADBE Opacity'), aE + 'value*a'); jzNoGhost(Tr); }
        }
        var gp = jzGrp(Pn, 'panel'), pr = jzAddRect(gp, bw, ph, bh * 0.16);
        jzSetExpr(pr.property('ADBE Vector Rect Size'), pE + '[' + jzN(bw) + ',Math.max(0.1,' + jzN(ph) + '*pa)]');
        jzSetExpr(pr.property('ADBE Vector Rect Position'), pE + '[0,' + jzN(ph) + '*pa/2]');
        if (filled) jzAddFill(gp, plate, 90); else jzAddStroke(gp, sc.sub, 1.2 * ctx.u, 90);
        la2_gxo(gp, pE + '100*pa');
        Pn.moveAfter(B);
        return la2_bb(W / 2 - bw / 2, by - bh / 2, W / 2 + bw / 2, by + bh / 2);
    }
});

/* ---------------------------------------------------------------- 26 chat \u2014 \u30C1\u30E3\u30C3\u30C8 */
jzReg('layout', 'chat', {
    plan: function (rng, cut, st) {
        var nw = jzChunk(cut.text).length;
        return { msgs: cut.n <= 4 ? [jzTrim(cut.text)] : la2_splitK(cut.text, jzClamp(nw, 2, cut.n > 12 ? 4 : 3)), font: rng.pick(jzFontsOf(st, ['body', 'display'])), side0: rng.pick([1, -1]), alt: rng.chance(0.55) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), i, j;
        var msgs = jzP(ctx, 'msgs', null);
        if (!msgs || !msgs.length) msgs = [jzTrim(c.text)];
        var font = jzFontOf(ctx, 'font', 'body'), side0 = jzP(ctx, 'side0', 1) < 0 ? -1 : 1, alt = jzP(ctx, 'alt', false);
        var colW = W * (port ? 0.86 : W / H < 1.5 ? 0.66 : 0.52), maxW = colW * 0.8;
        var sentC = sc.accent, recvC = sc.ink !== sc.accent ? sc.ink : sc.sub;
        var list = [];
        if (msgs.length === 1) list.push({ typing: true, side: -side0 });
        for (i = 0; i < msgs.length; i++) list.push({ t: jzTrim(msgs[i]), side: alt ? (i % 2 ? -side0 : side0) : side0 });
        var nL = list.length, fs = Math.min(u * 0.088, H * 0.62 / (nL * 2.2)), longest = 1;
        for (i = 0; i < nL; i++) if (!list[i].typing) longest = Math.max(longest, jzCount(list[i].t));
        fs = Math.max(Math.min(fs, (maxW - fs * 1.4) / (longest * 1.02)), fs * 0.62);
        var lines = [];
        for (i = 0; i < nL; i++) lines.push(list[i].typing ? '' : la2_brk(list[i].t, Math.max(3, Math.floor(maxW / (fs * 1.02)))));
        for (i = 0; i < nL; i++) if (lines[i]) fs = Math.min(fs, la2_fitSize(ctx, lines[i], font, maxW - fs * 1.4, H * 0.3, { lead: 1.25 }));
        var pad = fs * 0.62, sp = fs * 0.45, dims = [], texts = [];
        for (i = 0; i < nL; i++) {
            if (list[i].typing) { dims.push([fs * 3, fs * 1.9]); texts.push(null); continue; }
            var T = la2_txt(ctx, lines[i], { font: font, size: fs, lead: 1.25, color: '#FFFFFF', x: 0, y: 0 }), s = jzSize(T);
            dims.push([s[0] + pad * 2, s[1] + pad * 1.45]); texts.push(T);
        }
        var gap = Math.min(0.3, Math.max(0.12, c.dur * 0.45 / nL));
        function tAt(k) { return list[0].typing ? (k === 0 ? 0 : 0.35 + (k - 1) * gap) : k * gap; }
        var stag = Math.max(0.01, c.stagger || 0.04), bottom = H * (port ? 0.78 : 0.8), hd = la2_hd(ctx), bb = null;
        for (i = 0; i < nL; i++) {
            var q = list[i], bw = dims[i][0], bh = dims[i][1], right = q.side > 0;
            var x0 = right ? W / 2 + colW / 2 - bw : W / 2 - colW / 2, y1 = bottom, y0 = y1 - bh;
            var col = right ? sentC : recvC, tc = la2_onCol(sc, col), px = right ? x0 + bw : x0, py = y1;
            // bubble: pops from its tail corner, pushed up by every later bubble
            var Bu = jzNoGhost(jzShapeLayer(ctx, q.typing ? 'typing' : 'bubble ' + (i + 1), px, py));
            if (q.typing) {
                for (j = 0; j < 3; j++) {
                    var gd = jzGrp(Bu, 'dot'); jzAddEllipse(gd, fs * 0.32, fs * 0.32, x0 + bw / 2 + (j - 1) * fs * 0.62 - px, y0 + bh / 2 - py); jzAddFill(gd, tc);
                    var phE = 'var ph=Math.sin(time*9-' + jzN(j * 0.9) + ')*0.5+0.5;';
                    la2_gxp(gd, phE + '[0,-ph*' + jzN(fs * 0.12) + ']'); la2_gxo(gd, phE + '(0.45+ph*0.5)*100');
                }
            }
            var gt = jzGrp(Bu, 'tail'), tail = right ? [[x0 + bw - fs * 0.5, y1 - fs * 0.3], [x0 + bw + fs * 0.28, y1 + fs * 0.05], [x0 + bw - fs * 0.1, y1 - fs * 0.8]] : [[x0 + fs * 0.5, y1 - fs * 0.3], [x0 - fs * 0.28, y1 + fs * 0.05], [x0 + fs * 0.1, y1 - fs * 0.8]];
            for (j = 0; j < 3; j++) { tail[j][0] -= px; tail[j][1] -= py; }
            jzAddPath(gt, tail, true); jzAddFill(gt, col);
            var gbx = jzGrp(Bu, 'bubble'); jzAddRect(gbx, bw, bh, Math.min(bh / 2, fs * 0.9), x0 + bw / 2 - px, y0 + bh / 2 - py); jzAddFill(gbx, col);
            var lift = 'var l=0;', lf = 0;
            for (j = i + 1; j < nL; j++) { lift += 'l+=' + jzN(dims[j][1] + sp) + '*oe((time-' + jzN(tAt(j)) + ')/0.28);'; lf += dims[j][1] + sp; }
            bb = jzUnion(bb, la2_bb(x0, y0 - lf, x0 + bw, y1 - lf));
            var Tx = texts[i];
            if (Tx) {
                jzTextDoc(Tx, function (td) { td.fillColor = jzHex(tc); });
                jzXf(Tx, 'ADBE Position').setValue([x0 + bw / 2, y0 + bh / 2]);
                Tx.moveBefore(Bu);
                Tx.parent = Bu;                                 // parent before any transform expression exists
            }
            jzSetExpr(jzXf(Bu, 'ADBE Position'), hd + lift + '[value[0],value[1]-l]');
            jzSetExpr(jzXf(Bu, 'ADBE Scale'), hd + 'var q=(time-' + jzN(tAt(i)) + ')/0.25;var s=q<0?0:ob(cl(q),1.6)*K*100;[s,s]');
            if (Tx) jzAnimate(ctx, Tx, { mi: tAt(i) / stag });
            if (i === nL - 1) {
                var ms = Math.max(11 * ctx.u, fs * 0.36);
                var Ts = jzText(ctx, (right ? 'Read ' : '') + jzFmtTime(c.start).substr(0, 5), { font: jzMonoF(ctx), size: ms, color: sc.sub, x: right ? x0 + bw : x0, y: y1 + ms * 1.4, align: right ? 'right' : 'left' });
                jzSetExpr(jzXf(Ts, 'ADBE Opacity'), hd + '(time>' + jzN(tAt(i) + 0.11) + '?value:0)*K'); jzNoGhost(Ts);
            }
        }
        return bb;
    }
});

/* ---------------------------------------------------------------- 27 notification \u2014 \u901A\u77E5 */
jzReg('layout', 'notification', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['body', 'display'])), pos: rng.pick(['banner', 'lock', 'center']), stack: rng.chance(0.5), app: rng.pick(['MUSIC', 'LYRICS', 'MESSAGE']) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), i;
        var text = jzTrim(c.text), n = jzCount(text), font = jzFontOf(ctx, 'font', 'body'), pos = jzP(ctx, 'pos', 'banner'), mono = jzMonoF(ctx), hd = la2_hd(ctx);
        var cw = W * (port ? 0.9 : W / H < 1.5 ? 0.66 : 0.54), pd = cw * 0.05, hs = Math.max(12 * ctx.u, cw * 0.032);
        var plate = sc.ink, tc = la2_onCol(sc, plate), x0 = W / 2 - cw / 2;
        var mt = text, ms = Math.min(la2_fitSize(ctx, mt, font, cw - pd * 2, u * 0.11, { track: 0.02 }), u * 0.095);
        if (ms < u * 0.07 && n > 5) { mt = la2_brk(text, Math.ceil(n / 2)); ms = Math.min(la2_fitSize(ctx, mt, font, cw - pd * 2, u * 0.2, { track: 0.02, lead: 1.3 }), u * 0.085); }
        var L = la2_txt(ctx, mt, { font: font, size: ms, track: 0.02, lead: 1.3, color: tc, x: x0 + pd, y: 0, align: 'left' });
        var msz = jzSize(L), headH = hs * 2.2, titleH = hs * 1.9, ch = pd + headH + titleH + msz[1] + pd * 1.1;
        var clockY = H * (port ? 0.2 : 0.22), cs = Math.min(u * 0.2, H * 0.2);
        if (pos === 'lock') {
            var hh = jzPad(Math.floor(c.start / 60) % 24 || 12, 2), mi2 = jzPad(Math.floor(c.start) % 60, 2);
            la2_io(ctx, jzText(ctx, hh + ':' + mi2, { font: jzFontsOf(ctx.st, ['display'])[0], size: cs, track: 0.02, color: sc.sub, x: W / 2, y: clockY }), 0, 0.5, 0, 0, 0.8);
            la2_io(ctx, jzText(ctx, 'LINE ' + jzLineNo(ctx) + '  \u00B7  ' + jzFmtTime(c.start), { font: mono, size: hs, track: 0.2, color: sc.sub, x: W / 2, y: clockY - cs * 0.62 }), 0, 0.5, 0, 0, 0.8);
        }
        var target = pos === 'banner' ? H * 0.06 : pos === 'lock' ? clockY + cs * 0.75 : H / 2 - ch / 2;
        // the card (origin = its top centre) drops in from above the frame and slides back out
        var Cd = jzNoGhost(jzShapeLayer(ctx, 'notification card', W / 2, target));
        var iy = pd + headH / 2 - hs * 0.2, isz = hs * 1.7, ix = x0 + pd + isz / 2 - W / 2, s = isz * 0.8, nc = la2_onCol(sc, sc.accent);
        var gn = jzGrp(Cd, 'note');
        var gn1 = la2_sub(gn, 'head'); jzAddEllipse(gn1, s * 0.34, s * 0.34, ix - s * 0.18, iy + s * 0.2); jzAddFill(gn1, nc);
        var gn2 = la2_sub(gn, 'stem'); jzAddRect(gn2, s * 0.07, s * 0.52, 0, ix - s * 0.03 + s * 0.035, iy - s * 0.32 + s * 0.26); jzAddFill(gn2, nc);
        var gn3 = la2_sub(gn, 'flag'); jzAddPath(gn3, [[ix + s * 0.04, iy - s * 0.32], [ix + s * 0.3, iy - s * 0.18], [ix + s * 0.04, iy - s * 0.12]], true); jzAddFill(gn3, nc);
        var gi = jzGrp(Cd, 'app icon'); jzAddRect(gi, isz, isz, isz * 0.24, ix, iy); jzAddFill(gi, sc.accent);
        var gc = jzGrp(Cd, 'card'); jzAddRect(gc, cw, ch, pd * 0.9, 0, ch / 2); jzAddFill(gc, plate, 96);
        if (jzP(ctx, 'stack', false)) { var gs = jzGrp(Cd, 'stacked'); jzAddRect(gs, cw * 0.92, pd * 1.4, pd * 0.7, 0, ch - pd * 0.4 + pd * 0.7); jzAddFill(gs, plate, 45); }
        Cd.moveAfter(L);
        var kids = [];
        kids.push(jzText(ctx, jzP(ctx, 'app', 'MUSIC'), { font: mono, size: hs, track: 0.15, color: tc, x: x0 + pd + isz + hs * 0.7, y: target + iy, align: 'left', opacity: 0.6 }));
        kids.push(jzText(ctx, 'now', { font: mono, size: hs, track: 0.1, color: tc, x: x0 + cw - pd, y: target + iy, align: 'right', opacity: 0.5 }));
        var ty = target + pd + headH + titleH / 2;
        kids.push(jzText(ctx, jzRomajiOf(ctx) || 'No.' + jzLineNo(ctx), { font: jzBodyF(ctx), size: hs * 1.15, track: 0.06, color: tc, x: x0 + pd, y: ty, align: 'left', opacity: 0.9 }));
        la2_ng(kids);
        var my = ty + titleH / 2 + msz[1] / 2;
        jzXf(L, 'ADBE Position').setValue([x0 + pd, my]);
        kids.push(L);
        for (i = 0; i < kids.length; i++) kids[i].parent = Cd;   // parent first, then the transform expressions
        var D = target + ch + 30 * ctx.u;
        jzSetExpr(jzXf(Cd, 'ADBE Position'), hd + 'var eIn=ob(cl(time/0.45),1.1),eO=ic(PO);[value[0],value[1]-(1-eIn)*' + jzN(D) + '-eO*' + jzN(D) + ']');
        jzAnimate(ctx, L, { mi: 0 });
        return la2_bb(x0 + pd, my - msz[1] / 2, x0 + pd + msz[0], my + msz[1] / 2);
    }
});

/* ---------------------------------------------------------------- 28 ticket \u2014 \u30C1\u30B1\u30C3\u30C8 */
function la2_ticketParts(x0, y0, w, h, px, nr, r) {
    var seg = 8, x1 = x0 + w, y1 = y0 + h;
    function arc(cx, cy, rr, a0, a1, out) { for (var i = 0; i <= seg; i++) { var a = (a0 + (a1 - a0) * i / seg) * Math.PI / 180; out.push([cx + Math.cos(a) * rr, cy + Math.sin(a) * rr]); } }
    var main = [], stub = [];
    arc(x0 + r, y0 + r, r, 180, 270, main); main.push([px - nr, y0]); arc(px, y0, nr, 180, 90, main); main.push([px, y1 - nr]); arc(px, y1, nr, 270, 180, main); arc(x0 + r, y1 - r, r, 90, 180, main);
    stub.push([px + nr, y0]); arc(x1 - r, y0 + r, r, 270, 360, stub); arc(x1 - r, y1 - r, r, 0, 90, stub); stub.push([px + nr, y1]); arc(px, y1, nr, 360, 270, stub); stub.push([px, y0 + nr]); arc(px, y0, nr, 90, 0, stub);
    return { main: main, stub: stub };
}
jzReg('layout', 'ticket', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), fill: rng.pick(['accent', 'ink', 'outline']), tilt: rng.range(-5, 5), label: rng.pick(['ADMIT ONE', 'LIVE', 'TICKET']), serial: rng.int(1, 999999) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, port = jzPortrait(ctx), i;
        var tw = Math.min(W * (port ? 0.9 : 0.74), H * 1.9), th = Math.min(tw * (port ? 0.46 : 0.38), H * 0.5);
        var stubW = tw * 0.22, px0 = tw / 2 - stubW, nr = th * 0.075, r = th * 0.05;
        var fill = jzP(ctx, 'fill', 'accent'), outline = fill === 'outline', plate = fill === 'accent' ? sc.accent : sc.ink;
        var tc = outline ? sc.fg : la2_onCol(sc, plate), lc = outline ? sc.sub : tc, serial = Math.round(jzP(ctx, 'serial', 1234)), label = jzP(ctx, 'label', 'TICKET');
        var cx = W / 2, cy = H / 2, mono = jzMonoF(ctx), lwO = Math.max(2 * ctx.u, th * 0.012);
        var TP = la2_ticketParts(-tw / 2, -th / 2, tw, th, px0, nr, r);
        var eH = la2_hd(ctx) + 'var eIn=ob(cl(time/0.5),1.2),eO=ic(PO),a=cl(eIn*3)*(1-eO);';
        // ticket body: rises from below with a swing, settles at its tilt
        var Tk = jzNoGhost(jzShapeLayer(ctx, 'ticket', cx, cy));
        var gl = jzGrp(Tk, 'rules'), mx0 = -tw / 2 + th * 0.12, mx1 = px0 - th * 0.12;
        jzAddPath(gl, [[mx0, -th / 2 + th * 0.24], [mx1, -th / 2 + th * 0.24]], false); jzAddPath(gl, [[mx0, th / 2 - th * 0.24], [mx1, th / 2 - th * 0.24]], false); jzAddStroke(gl, lc, 1.2 * ctx.u, 60);
        var gp = jzGrp(Tk, 'perforation'), pn = Math.max(1, Math.ceil((th - nr * 3.2) / (th * 0.05)));
        jzAddRect(gp, 2.5 * ctx.u, th * 0.025, 0, px0 + 0.25 * ctx.u, -th / 2 + nr * 1.6 + th * 0.0125); jzAddFill(gp, lc, 70);
        var rpp = jzVecs(gp).addProperty('ADBE Vector Filter - Repeater');
        rpp.property('ADBE Vector Repeater Copies').setValue(pn);
        rpp.property('ADBE Vector Repeater Transform').property('ADBE Vector Repeater Position').setValue([0, th * 0.05]);
        var gmn = jzGrp(Tk, 'ticket'); jzAddPath(gmn, TP.main, true);
        if (outline) jzAddStroke(gmn, sc.fg, lwO); else jzAddFill(gmn, plate);
        // stub (tears away on exit) with label, serial and barcode
        var St = jzNoGhost(jzShapeLayer(ctx, 'stub', cx, cy)), sx = px0 + stubW / 2, bx = sx + stubW * 0.26, bh2 = th * 0.7;
        var gbc = jzGrp(St, 'barcode'), yb = -bh2 / 2;
        for (i = 0; i < 40 && yb < bh2 / 2; i++) { var hg = th * (0.006 + 0.014 * jzR(serial, i, 3)); jzAddRect(gbc, stubW * 0.16, hg, 0, bx, yb + hg / 2); yb += hg + th * (0.006 + 0.01 * jzR(serial, i, 4)); }
        jzAddFill(gbc, tc, 85);
        var gst = jzGrp(St, 'stub'); jzAddPath(gst, TP.stub, true);
        if (outline) jzAddStroke(gst, sc.fg, lwO); else jzAddFill(gst, plate);
        var ls = Math.max(11 * ctx.u, th * 0.07), skids = [], tkids = [];
        skids.push(jzText(ctx, label, { font: mono, size: ls, track: 0.25, color: tc, x: cx + sx - stubW * 0.22, y: cy, rot: -90 }));
        skids.push(jzText(ctx, 'No.' + jzPad(serial, 6), { font: mono, size: ls * 0.8, track: 0.12, color: lc, x: cx + sx + stubW * 0.02, y: cy, rot: -90, opacity: 0.8 }));
        tkids.push(jzText(ctx, label + '  \u00B7  No.' + jzLineNo(ctx), { font: mono, size: ls, track: 0.2, color: lc, x: cx + mx0, y: cy - th / 2 + th * 0.14, align: 'left' }));
        var Fg = jzText(ctx, 'GATE ' + String.fromCharCode(65 + serial % 6) + '   ROW ' + jzPad(1 + serial % 30, 2) + '   SEAT ' + jzPad(1 + (serial >> 3) % 40, 2), { font: mono, size: ls * 0.9, track: 0.15, color: lc, x: cx + mx0, y: cy + th / 2 - th * 0.14, align: 'left' });
        var Ft = jzText(ctx, jzFmtTime(c.start), { font: mono, size: ls * 0.9, track: 0.1, color: lc, x: cx + mx1, y: cy + th / 2 - th * 0.14, align: 'right' });
        var fk = (mx1 - mx0) / Math.max(1, jzSize(Fg)[0] + jzSize(Ft)[0] + ls);   // keep the footer from colliding with wide mono fonts
        if (fk < 1) { jzXf(Fg, 'ADBE Scale').setValue([100 * fk, 100 * fk]); jzXf(Ft, 'ADBE Scale').setValue([100 * fk, 100 * fk]); }
        tkids.push(Fg, Ft);
        la2_ng(skids); la2_ng(tkids);                           // (before the lyric joins tkids)
        for (i = 0; i < skids.length; i++) jzSetExpr(jzXf(skids[i], 'ADBE Opacity'), eH + 'value*a');
        for (i = 0; i < tkids.length; i++) jzSetExpr(jzXf(tkids[i], 'ADBE Opacity'), eH + 'value*a');
        // title = the lyric
        var t0 = jzTrim(c.text), text = la2_brk(t0, port ? 6 : 9);
        var L = la2_txt(ctx, text, { font: jzFontOf(ctx, 'font', 'display'), size: 100, color: tc, x: cx + mx0, y: cy, align: 'left', track: 0.03, lead: 1.1, maxW: mx1 - mx0, maxH: th * 0.44, maxSize: th * 0.3 });
        tkids.push(L);
        St.parent = Tk;                                         // parent everything first, then the transform expressions
        for (i = 0; i < skids.length; i++) skids[i].parent = St;
        for (i = 0; i < tkids.length; i++) tkids[i].parent = Tk;
        St.moveAfter(Tk);
        jzXf(Tk, 'ADBE Rotate Z').setValue(jzP(ctx, 'tilt', 0));
        jzSetExpr(jzXf(Tk, 'ADBE Position'), eH + '[value[0],value[1]+(1-eIn)*' + jzN(H * 0.7) + ']');
        jzSetExpr(jzXf(Tk, 'ADBE Rotate Z'), eH + 'value*(1-eO*0.5)+(1-eIn)*10');
        jzSetExpr(jzXf(Tk, 'ADBE Opacity'), eH + 'value*a');
        jzSetExpr(jzXf(St, 'ADBE Position'), eH + '[value[0]+eO*' + jzN(tw * 0.12) + ',value[1]+eO*' + jzN(th * 0.2) + ']');
        jzSetExpr(jzXf(St, 'ADBE Rotate Z'), eH + 'value+eO*12');
        jzSetExpr(jzXf(St, 'ADBE Opacity'), eH + 'value*a');
        jzAnimate(ctx, L, { mi: 0 });
        return la2_bb(cx - tw / 2, cy - th / 2, cx + tw / 2, cy + th / 2);
    }
});

// ================================================================ pack layoutsB part 1 (AE port of src/11p_layoutsB.js, entries 1-14)
// rain, hanging, orbit, tunnel, wordCloud, bounceLine, elastic, crossBands, stickerBomb, neon, keycaps, bubbles, slotMachine, flipBoard
//
// Techniques used here:
// - glyph positions are computed like the browser's layoutText (full-width glyphs = 1 em, latin estimated), so streams /
//   shadows / bands line up with the AE text (exact for Japanese).
// - "one glyph per line" text layers (lb1_glyphs) hold many freely placed glyphs in ONE editable layer: every line is
//   centre-justified, so glyph i sits at (0, i*LD) no matter how wide it is; a Position animator moves it anywhere.
// - moving plates / strings / bubbles are shape layers; the lyric glyph that rides on them is parented (setParentWithJump)
//   so the cut's enter / hold / exit expressions stay untouched on the text layer.

var LB1_CY = 0.38;     // glyph centre above the baseline (em) \u2014 CJK em box
// deterministic hash for expressions (seedRandom streams also depend on the layer / property in AE, so anything that
// several properties or layers must agree on uses this instead)
var LB1_HR = 'function hr(a,b){var x=Math.sin(a*12.9898+b*78.233)*43758.5453;return x-Math.floor(x);}';
var LB1_KATA = '\u30A2\u30A4\u30A6\u30A8\u30AA\u30AB\u30AD\u30AF\u30B1\u30B3\u30B5\u30B7\u30B9\u30BB\u30BD\u30BF\u30C1\u30C4\u30C6\u30C8\u30CA\u30CB\u30CC\u30CD\u30CE\u30CF\u30D2\u30D5\u30D8\u30DB\u30DE\u30DF\u30E0\u30E1\u30E2\u30E4\u30E6\u30E8\u30E9\u30EA\u30EB\u30EC\u30ED\u30EF\u30F2\u30F3';

/* ---------------------------------------------------------------- small helpers */
function lb1_k(ctx) { return jzU(ctx) / 1080; }                 // browser design px -> comp px
function lb1_clean(t) { return String(t || '').replace(/[\s\u3000]+/g, ''); }
function lb1_slots(t) { return jzChars(jzTrim(String(t || '')).replace(/[\s\u3000]+/g, ' ')); }
function lb1_isSp(ch) { return ch === ' ' || ch === '\u3000'; }
function lb1_roma(t) { var c = lb1_clean(t); if (!/[\u3041-\u30ff]/.test(c)) return null; var r = jzRomaji(c); return r ? r.toUpperCase() : null; }
function lb1_stag(ctx) { return Math.max(0.005, ctx.cut.stagger || 0.04); }
function lb1_miAt(ctx, t) { return Math.floor(Math.max(0, t) / lb1_stag(ctx) + 1e-6); }   // browser: (mi | 0) * stagger
function lb1_plateHold(ctx) { return jzIndexOf(['still', 'jitter', 'breathe', 'glitchtick'], ctx.cut.hold || 'still') < 0; }
function lb1_plateCol(sc, pref) { for (var i = 0; i < pref.length; i++) if (pref[i] && jzContrast(pref[i], sc.bg) >= 1.6) return pref[i]; return sc.fg; }
function lb1_onCol(sc, fill) {
    var c = [sc.bg, sc.fg, sc.ink, sc.accent, sc.sub], best = null, bv = 0;
    for (var i = 0; i < c.length; i++) { if (!c[i] || String(c[i]).toUpperCase() === String(fill).toUpperCase()) continue; var k = jzContrast(c[i], fill); if (k > bv) { bv = k; best = c[i]; } }
    return bv >= 2.4 ? best : (jzLum(fill) > 0.5 ? '#111111' : '#FFFFFF');
}
function lb1_arr(a) { return jzArrExpr(a); }
function lb1_strArr(a) { var o = []; for (var i = 0; i < a.length; i++) o.push('"' + String(a[i]).replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"'); return '[' + o.join(',') + ']'; }
// glyph pool for random reels / streams: the lyric's own kana & kanji + every other katakana
function lb1_pool(c) {
    var own = jzChars(lb1_clean((c.lineText || '') + c.text)), out = [], i;
    for (i = 0; i < own.length; i++) if (!jzIsLatin(own[i]) && !jzIsPunct(own[i]) && own[i] !== '\u30FB' && !/["'\\]/.test(own[i])) out.push(own[i]);
    var ka = jzChars(jzPool('kana'));
    for (i = 0; i < ka.length; i += 2) out.push(ka[i]);
    return out;
}
// main-text lines for a width budget: portrait -> short lines ('\r' separated)
function lb1_lines(text, W, H, perL, perP) {
    var t = jzTrim(String(text || '')), n = jzCount(t), per = W < H ? perP : perL;
    if (n <= per) return t;
    return jzSplitLines(t, Math.ceil(n / Math.ceil(n / per)));
}
// advance width in em (browser measures real fonts; full-width glyphs are exactly 1 em)
function lb1_adv(ch, mono) {
    var c = ch.charCodeAt(0);
    if (ch === ' ') return mono ? 0.6 : 0.3;
    if (c < 0x80) {
        if (mono) return 0.6;
        if (/[A-Z]/.test(ch)) return /[MW]/.test(ch) ? 0.9 : (ch === 'I' ? 0.32 : 0.7);
        if (/[a-z]/.test(ch)) return /[mw]/.test(ch) ? 0.88 : /[ijl]/.test(ch) ? 0.28 : /[frt]/.test(ch) ? 0.42 : 0.61;
        if (/[0-9]/.test(ch)) return 0.62;
        return /[.,:;!'|]/.test(ch) ? 0.28 : 0.42;
    }
    if (c < 0x250) return 0.56;
    if (c >= 0xFF61 && c <= 0xFF9F) return 0.5;
    return 1;
}
// glyph layout like the browser's layoutText: centres relative to the block centre; ti = AE textIndex-1
function lb1_layout(str, size, o) {
    o = o || {};
    var tr = o.track || 0, out = [], ti = 0, j, li;
    if (o.vertical) {
        var arr = jzChars(lb1_clean(str)), n = arr.length, st = size * (1 + tr);
        for (j = 0; j < n; j++) out.push({ ch: arr[j], x: 0, y: (j - (n - 1) / 2) * st, w: size, h: size, li: j, ti: ti++, sp: false });
        out.W = size; out.H = Math.max(size, n * st - tr * size); out.nL = n;
        return out;
    }
    var lines = String(str).split(/\r\n|\r|\n/), nL = lines.length, lead = (o.lead || 1.3) * size, maxW = 1;
    for (li = 0; li < nL; li++) {
        var a2 = jzChars(lines[li]), ws = [], w = 0;
        for (j = 0; j < a2.length; j++) { ws.push(lb1_adv(a2[j], o.mono) * size); w += ws[j] + (j < a2.length - 1 ? tr * size : 0); }
        maxW = Math.max(maxW, w);
        var x = -w / 2, y = (li - (nL - 1) / 2) * lead;
        for (j = 0; j < a2.length; j++) { out.push({ ch: a2[j], x: x + ws[j] / 2, y: y, w: ws[j], h: size, li: li, ti: ti++, sp: lb1_isSp(a2[j]) }); x += ws[j] + tr * size; }
    }
    out.W = maxW; out.H = nL * lead - (lead - size); out.nL = nL;
    return out;
}
function lb1_measure(str, size, o) { var l = lb1_layout(str, size, o); return { w: l.W, h: l.H }; }
function lb1_fitSize(str, maxW, maxH, o) { var m = lb1_measure(str, 100, o); return 100 * Math.min(maxW / Math.max(1, m.w), maxH / Math.max(1, m.h)); }
// a normal text block whose layout centre (as in the browser) sits at (o.x, o.y)
function lb1_textAt(ctx, str, o) {
    var vert = !!o.vertical, size = o.size;
    var txt = vert ? jzVertical(str) : String(str).replace(/\r\n|\n/g, '\r');
    var nL = txt.split('\r').length, LD = vert ? size * (1 + (o.track || 0)) : size * (o.lead || 1.2);
    var L = jzText(ctx, txt, { font: o.font, size: size, color: o.color || '#FFFFFF', x: o.x, y: o.y, track: vert ? 0 : (o.track || 0), leading: LD,
        fill: o.fill, stroke: o.stroke, strokeColor: o.strokeColor, strokeOver: o.strokeOver, name: o.name });
    jzXf(L, 'ADBE Anchor Point').setValue([0, -LB1_CY * size + (nL - 1) / 2 * LD]);
    jzXf(L, 'ADBE Position').setValue([o.x, o.y]);
    if (o.opacity != null) jzXf(L, 'ADBE Opacity').setValue(o.opacity * 100);
    return L;
}
// bbox of a laid-out block at (cx, cy)
function lb1_bb(cx, cy, w, h) { return { x0: cx - w / 2, x1: cx + w / 2, y0: cy - h / 2, y1: cy + h / 2, cx: cx, cy: cy }; }
// "one glyph per line" layer: glyph i rests at (0, i*LD) (baseline) in comp px; move it with lb1_place / lb1_placeAnim
function lb1_glyphs(ctx, chars, o) {
    var t = [], i;
    for (i = 0; i < chars.length; i++) t.push(chars[i] === '' ? ' ' : chars[i]);
    var L = jzText(ctx, t.join('\r'), { font: o.font, size: o.size, color: o.color || '#FFFFFF', x: 0, y: 0, track: 0, leading: o.size * 1.0, fill: o.fill, stroke: o.stroke, strokeColor: o.strokeColor, name: o.name || 'glyphs' });
    jzXf(L, 'ADBE Anchor Point').setValue([0, 0]); jzXf(L, 'ADBE Position').setValue([0, 0]);
    return { L: L, fs: o.size, LD: o.size, n: chars.length };      // (no expando properties on AE layer objects)
}
// static placement: pts[i] = [x, y] = glyph centre in the layer's space
function lb1_place(G, pts, name) {
    var offs = [];
    for (var i = 0; i < pts.length; i++) offs.push([pts[i][0], pts[i][1] + LB1_CY * G.fs - i * G.LD]);
    return jzCharOffsets(G.L, offs, name || 'JZ Place');
}
// animated placement: body is expression code that sets x, y (glyph centre, layer space) from i = textIndex-1
function lb1_placeAnim(ctx, G, name, head, body, bound) {
    var KB = Math.max(1000, bound || (Math.max(ctx.W, ctx.H) * 3 + G.n * G.LD));
    return jzAnimator(G.L, name, [['ADBE Text Position 3D', [KB, KB, 0]]], head + 'var i=textIndex-1,x=0,y=0;' + body + ';var KB=' + jzN(KB) + ';[x/KB*100,(y+' + jzN(LB1_CY * G.fs) + '-i*' + jzN(G.LD) + ')/KB*100,0]');
}
// per-glyph animators: body sets a variable (a = alpha 0..1 / sx, sy = scale 0..2 / dx, dy px / r deg / m = colour mix 0..1)
function lb1_opAnim(L, name, head, body) { return jzAnimator(L, name, [['ADBE Text Opacity', 0]], head + 'var a=1;' + body + ';(1-Math.max(0,Math.min(1,a)))*100'); }
function lb1_scAnim(L, name, head, body) { return jzAnimator(L, name, [['ADBE Text Scale 3D', [0, 0, 100]]], head + 'var sx=1,sy=1;' + body + ';[(1-sx)*100,(1-sy)*100,0]'); }
function lb1_posAnim(L, name, KB, head, body) { return jzAnimator(L, name, [['ADBE Text Position 3D', [KB, KB, 0]]], head + 'var dx=0,dy=0;' + body + ';[dx/' + jzN(KB) + '*100,dy/' + jzN(KB) + '*100,0]'); }
function lb1_rotAnim(L, name, R, head, body) { return jzAnimator(L, name, [['ADBE Text Rotation', R]], head + 'var r=0;' + body + ';r/' + jzN(R) + '*100'); }
function lb1_colAnim(L, name, hex, head, body) { return jzAnimator(L, name, [['ADBE Text Fill Color', jzHex(hex)]], head + 'var m=0;' + body + ';Math.max(0,Math.min(1,m))*100'); }
// the browser draws some lyrics glyph by glyph with mi = i * f: with the 'cut' enter that is a left-to-right reveal.
// Other enters already stagger per glyph in AE, so only 'cut' gets this (MP = textIndex-1 -> glyph index, -1 = space)
function lb1_cutStagger(ctx, L, MP, f) {
    if ((ctx.cut.enter || 'cut') !== 'cut') return null;
    var D = [], st = ctx.cut.stagger || 0;
    for (var i = 0; i < MP.length; i++) D.push(MP[i] < 0 ? 0 : Math.floor(MP[i] * f + 1e-6) * st);
    return lb1_opAnim(L, 'JZ Stagger In', 'var D=' + lb1_arr(D) + ';', 'a=time<(D[textIndex-1]||0)?0:1');
}
// alpha track matte: L shows only inside M (M directly above L). AE 23+ links mattes with setTrackMatte
function lb1_matte(L, M) {
    var ok = false;
    if (typeof L.setTrackMatte === 'function') { try { L.setTrackMatte(M, TrackMatteType.ALPHA); ok = true; } catch (e) { ok = false; } }
    if (!ok) { try { L.trackMatteType = TrackMatteType.ALPHA; } catch (e2) { jzWarn('matte: ' + e2.toString()); } }
}
// shape helpers
function lb1_shape(ctx, name, x, y) { return jzShapeLayer(ctx, name, x || 0, y || 0); }
// helper layers the browser draws with ghost off (main pass only) stay out of the tinted ghosts. Plates the browser ghosts
// only while they fly in (gIn: the first 0.6 s) are marked too: their steady state has no chromatic fringe.
function lb1_ng(L) { if (L) jzNoGhost(L); return L; }
function lb1_line(g, pts, col, w, op) { jzAddPath(g, pts, false); return jzAddStroke(g, col, w, op); }
function lb1_setX(prop, ex) { jzSetExpr(prop, ex); }
function lb1_gOp(g, ex) { jzSetExpr(jzGX(g).property('ADBE Vector Group Opacity'), ex); }
function lb1_gSc(g, ex) { jzSetExpr(jzGX(g).property('ADBE Vector Scale'), ex); }
function lb1_gPos(g, ex) { jzSetExpr(jzGX(g).property('ADBE Vector Position'), ex); }
function lb1_gRot(g, ex) { jzSetExpr(jzGX(g).property('ADBE Vector Rotation'), ex); }
// parent a (text) layer to a moving shape layer without moving it, then place it in the parent's space
function lb1_parent(L, P, x, y) { L.setParentWithJump(P); jzXf(L, 'ADBE Position').setValue([x, y]); }
// rounded rect path group helper (centre cx, cy)
// sub-group inside a shape group
function lb1_sub(g, name) { var n = jzVecs(g).addProperty('ADBE Vector Group'); if (name) n.name = name; return n; }
function lb1_rrect(g, w, h, r, cx, cy) { return jzAddRect(g, Math.max(0.5, w), Math.max(0.5, h), Math.max(0, Math.min(r, w / 2, h / 2)), cx || 0, cy || 0); }

/* ================================================================== 1 rain \u2014 \u6587\u5B57\u306E\u96E8 */
jzReg('layout', 'rain', {
    plan: function (rng, cut, st) {
        var port = cut.H > cut.W, mo = st.fonts.mono || [], monos = [];
        for (var i = 0; i < mo.length; i++) if (mo[i] === 'dot') monos.push(mo[i]);
        return {
            font: rng.pick(jzFontsOf(st, ['display', 'serif'])),
            rf: monos.length && rng.chance(0.5) ? 'dot' : rng.pick(jzFontsOf(st, ['body'])),
            orient: cut.n <= 7 && rng.chance(port ? 0.6 : 0.3) ? 'v' : 'h',
            density: rng.range(0.5, 0.72), speed: rng.range(0.85, 1.25), tint: rng.pick(['sub', 'sub', 'accent']),
            order: rng.pick(['ltr', 'random', 'random'])
        };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, s = c.seed, M = jzU(ctx), k = lb1_k(ctx), i, j;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), rf = jzP(ctx, 'rf', jzBodyF(ctx)), vert = jzP(ctx, 'orient', 'h') === 'v';
        var density = jzP(ctx, 'density', 0.6), speed = jzP(ctx, 'speed', 1), tail = jzP(ctx, 'tint', 'sub') === 'accent' ? sc.accent : sc.sub, order = jzP(ctx, 'order', 'random');
        var mt = vert ? lb1_clean(c.text) : lb1_lines(c.text, W, H, 11, 6);
        var lo = { track: 0.1, lead: 1.25, vertical: vert };
        var size = Math.min(lb1_fitSize(mt, W * (vert ? 0.5 : 0.84), H * (vert ? 0.8 : 0.5), lo), vert ? H * 0.15 : H * 0.24);
        var lay = lb1_layout(mt, size, lo), gl = [];
        for (i = 0; i < lay.length; i++) if (!lay[i].sp) gl.push(lay[i]);
        var n = gl.length, cx = W / 2, cy = H / 2;
        var kx0 = cx - lay.W / 2 - size * 0.8, kx1 = cx + lay.W / 2 + size * 0.8, ky0 = cy - lay.H / 2 - size * 0.8, ky1 = cy + lay.H / 2 + size * 0.8;
        var TH = jzTH(ctx), pool = lb1_pool(c), PA = lb1_strArr(pool);
        // ---- background streams: a stationary glyph grid, a bright head sweeps down each active column (one text layer)
        var cell = jzClamp(M * 0.036, 16 * k, 64 * k), cols = Math.floor(W / cell), rows = Math.ceil(H / cell), ox = (W - cols * cell) / 2, rs = cell * 0.72;
        var init = [];
        for (var r = 0; r < rows; r++) { var row = ''; for (j = 0; j < cols; j++) row += pool[jzHash(s, r, j) % pool.length]; init.push(row); }
        var G = lb1_ng(jzText(ctx, init.join('\r'), { font: rf, size: rs, color: sc.sub, x: 0, y: 0, track: (cell - rs) / rs, leading: cell, name: 'rain streams' }));
        jzXf(G, 'ADBE Anchor Point').setValue([0, 0]); jzXf(G, 'ADBE Position').setValue([W / 2, cell * 0.5 + LB1_CY * rs]);
        try {
            G.property('ADBE Text Properties').property('ADBE Text Document').expression = 'var P=' + PA + ',NP=P.length,C=' + cols + ',R=' + rows + ',SD=' + (s % 99991) +
                ';var f=Math.floor(time*24),s="";for(var r=0;r<R;r++){if(r)s+="\\r";for(var c=0;c<C;c++){seedRandom(SD+r*7919+c*131,true);var o=Math.floor(random()*16);seedRandom(SD+c*104729+r*37+Math.floor((f+o)/14)*7331,true);s+=P[Math.floor(random()*NP)%NP];}}s';
        } catch (e0) { jzWarn('rain source: ' + e0.toString()); }
        var colHead = LB1_HR + 'var C=' + cols + ',CL=' + jzN(cell) + ',OX=' + jzN(ox) + ',DN=' + jzN(density) + ',SPD=' + jzN(speed) + ',R=' + rows + ';var j=textIndex-1,c=j%C,r=Math.floor(j/C);' +
            'var on=hr(SD+c,1)<=DN,v=(8+8*hr(SD+c,2))*SPD,Ls=6+9*hr(SD+c,3),per=R+Ls+2+(R*0.8-2)*hr(SD+c,4),ph=hr(SD+c,5);var hy=((time*v+ph*per)%per)-1,d=hy-r,vis=on&&d>=0&&d<=Ls;';
        var od = Math.max(0.12, c.outDur || 0.15), slim = 'var SD=' + (s % 99991) + ',PO=Math.max(0,Math.min(1,(time-' + jzN(c.dur - od) + ')/' + jzN(od) + ')),K=1-PO*PO*PO;';
        function oc(x) { return 'Math.max(0,1-Math.pow(1-Math.max(0,Math.min(1,' + x + ')),3))'; }
        lb1_opAnim(G, 'JZ Rain Streams', slim + colHead, 'a=0;if(vis){a=d<1?1:0.1+Math.pow(1-d/Ls,1.4)*0.6;var gx=OX+(c+0.5)*CL,gy=(r+0.5)*CL;if(gx>' + jzN(kx0) + '&&gx<' + jzN(kx1) + '&&gy>' + jzN(ky0) + '&&gy<' + jzN(ky1) + ')a*=0.22;}a*=' + oc('time/0.35') + '*K');
        lb1_colAnim(G, 'JZ Rain Heads', sc.fg, 'var SD=' + (s % 99991) + ';' + colHead, 'm=vis&&d<1?1:0');
        // ---- per-glyph delivery times
        var span = jzClamp(c.dur * 0.2, 0.1, 0.5), fd = jzClamp(c.dur * 0.1, 0.14, 0.26), rank = [], pos = [];
        for (i = 0; i < n; i++) rank.push(i);
        if (order === 'random') rank.sort(function (a, b) { return jzR(s, a, 31) - jzR(s, b, 31); });
        for (i = 0; i < n; i++) pos[rank[i]] = i;
        var TA = [], GX = [], GY = [], taMin = 1e9;
        for (i = 0; i < n; i++) {
            var kk = vert ? n - 1 - i : pos[i];
            TA.push(0.05 + fd + (n > 1 ? kk / (n - 1) : 0) * span); taMin = Math.min(taMin, TA[i]);
            GX.push(cx + gl[i].x); GY.push(cy + gl[i].y);
        }
        // ---- trails: 8 small glyphs above every lyric glyph, falling with it (one layer, glyphs placed per frame)
        var trailN = 8, ts = jzClamp(size * 0.32, cell * 0.8, cell * 1.5), tsp = ts * 1.12, tch = [];
        for (i = 0; i < n * trailN; i++) tch.push(pool[jzHash(s, i, 77) % pool.length]);
        var T = lb1_glyphs(ctx, tch, { font: rf, size: ts, color: tail, name: 'rain trails' }); lb1_ng(T.L);
        try {
            T.L.property('ADBE Text Properties').property('ADBE Text Document').expression = 'var P=' + PA + ',NP=P.length,N=' + (n * trailN) + ',SD=' + (s % 99991) +
                ';var f=Math.floor(time*12),s="";for(var q=0;q<N;q++){if(q)s+="\\r";seedRandom(SD+q*7919+f*131,true);s+=P[Math.floor(random()*NP)%NP];}s';
        } catch (e1) { jzWarn('rain trails: ' + e1.toString()); }
        var fall = 'var TA=' + lb1_arr(TA) + ',GX=' + lb1_arr(GX) + ',GY=' + lb1_arr(GY) + ',FD=' + jzN(fd) + ',TS=' + jzN(ts) + ',TSP=' + jzN(tsp) + ',SZ=' + jzN(size) + ';';
        var trI = 'var g=Math.floor((textIndex-1)/' + trailN + '),jj=(textIndex-1)%' + trailN + ',u=(time-(TA[g]-FD))/FD;var e=iq(Math.min(1,u)),yh=-TSP*2+(GY[g]+TSP*2)*e,hs=u<1?TS+(SZ-TS)*Math.pow(Math.max(0,u),3):SZ;';
        lb1_placeAnim(ctx, T, 'JZ Trail Fall', TH + fall + trI, 'x=GX[g];y=yh-hs*0.5-' + trailN + '*TSP+(jj+0.5)*TSP');
        lb1_opAnim(T.L, 'JZ Trail Fade', TH + fall + trI, 'a=(u<=0||u>=2)?0:Math.pow((jj+1)/' + trailN + ',1.6)*0.95*(u<1?1:2-u)*K');
        // ---- heads: the lyric glyphs themselves, growing while they fall (same layout as the lyric)
        var tiMap = [];
        for (i = 0; i < lay.length; i++) tiMap.push(-1);
        for (i = 0; i < n; i++) tiMap[gl[i].ti] = i;
        var HL = lb1_ng(lb1_textAt(ctx, mt, { font: font, size: size, color: sc.fg, x: cx, y: cy, track: 0.1, lead: 1.25, vertical: vert, name: 'rain heads' }));
        var hdI = 'var MP=' + lb1_arr(tiMap) + ',g=MP[textIndex-1];if(g==null)g=-1;var u=g<0?-1:(time-(g<0?0:TA[g]-FD))/FD;var e=iq(Math.min(1,Math.max(0,u))),yh=-TSP*2+((g<0?0:GY[g])+TSP*2)*e,hs=u<1?TS+(SZ-TS)*Math.pow(Math.max(0,u),3):SZ;';
        lb1_posAnim(HL, 'JZ Head Fall', H * 2, TH + fall + hdI, 'if(g>=0&&u>0&&u<1)dy=yh-GY[g]');
        lb1_scAnim(HL, 'JZ Head Size', TH + fall + hdI, 'if(g>=0&&u>0&&u<1){sx=hs/SZ;sy=sx;}');
        lb1_opAnim(HL, 'JZ Head Show', TH + fall + hdI, 'a=(g>=0&&u>0&&u<1)?(0.6+0.4*u)*K:0');
        // ---- the lyric: each glyph locks in when its stream arrives (accent flash -> fg)
        var ML = lb1_textAt(ctx, mt, { font: font, size: size, color: sc.fg, x: cx, y: cy, track: 0.1, lead: 1.25, vertical: vert, name: c.text });
        var TAg = [];
        for (i = 0; i < lay.length; i++) TAg.push(tiMap[i] >= 0 ? TA[tiMap[i]] : 0);
        var land = 'var TL=' + lb1_arr(TAg) + ',tl=TL[textIndex-1]||0;';
        lb1_opAnim(ML, 'JZ Rain Land', land, 'a=time<tl?0:1');
        lb1_colAnim(ML, 'JZ Rain Flash', sc.accent, JZ_FNS + land, 'm=1-oc((time-tl)/0.45)');
        jzAnimate(ctx, ML, { mi: lb1_miAt(ctx, taMin) });
        return lb1_bb(cx, cy, lay.W, lay.H);
    }
});

/* ================================================================== 2 hanging \u2014 \u540A\u308A\u4E0B\u3052 */
jzReg('layout', 'hanging', {
    plan: function (rng, cut, st) {
        return {
            font: rng.pick(jzFontsOf(st, ['display', 'serif'])), shape: rng.pick(['arc', 'wave', 'random', 'stair']), dir: rng.pick([1, -1]),
            tag: rng.chance(0.35), rail: rng.chance(0.6), amp: rng.range(0.06, 0.11), kick: rng.range(0.16, 0.26), ph: rng.range(0, 6)
        };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, s = c.seed, M = jzU(ctx), k = lb1_k(ctx), i;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), shape = jzP(ctx, 'shape', 'arc'), dir = jzP(ctx, 'dir', 1), tag = !!jzP(ctx, 'tag', false), rail = !!jzP(ctx, 'rail', true);
        var amp = jzP(ctx, 'amp', 0.08), kick = jzP(ctx, 'kick', 0.2), ph = jzP(ctx, 'ph', 0);
        var chars = lb1_slots(c.text), n = chars.length;
        if (!n) return null;
        var rowsN = W < H && n > 5 ? 2 : 1, per = Math.ceil(n / rowsN);
        var sp = W * (rowsN > 1 ? 0.84 / (per + 0.5) : 0.88 / per);
        var size = Math.min(sp * (tag ? 0.76 : 0.84), M * 0.19), tagW = size * 1.12, tagH = size * 1.36, csize = tag ? size * 0.8 : size;
        var railY = rail ? H * 0.075 : -2 * k, gap = jzClamp(c.dur * 0.35 / n, 0.03, 0.08), strokeW = Math.max(1 * k, M * 0.0016);
        var TH = jzTH(ctx), DEG = Math.PI / 180;
        if (rail) {
            var RL = lb1_ng(lb1_shape(ctx, 'rail', W / 2, railY)), gr = jzGrp(RL, 'rail');
            lb1_line(gr, [[-W * 0.52, 0], [W * 0.52, 0]], sc.sub, Math.max(1.2 * k, M * 0.0022));
            jzSetExpr(jzXf(RL, 'ADBE Scale'), JZ_FNS + '[value[0]*oe(time/0.45),value[1]]');
            jzSetExpr(jzXf(RL, 'ADBE Opacity'), TH + '80*K');
        }
        var items = [], bb = null;
        for (i = 0; i < n; i++) {
            if (chars[i] === ' ') continue;
            var row = Math.floor(i / per), j = i - row * per, cnt = Math.min(per, n - row * per);
            var u = cnt > 1 ? j / (cnt - 1) : 0.5, am = rowsN > 1 ? 0.45 : 1, dev;
            if (shape === 'arc') dev = (Math.sin(Math.PI * u) - 0.55) * H * 0.13 * dir;
            else if (shape === 'wave') dev = Math.sin(u * Math.PI * 2 * 1.1 + ph) * H * 0.08;
            else if (shape === 'stair') dev = (u - 0.5) * H * 0.2 * dir;
            else dev = (jzR(s, i, 3) * 2 - 1) * H * 0.08;
            dev = Math.max(-H * 0.14, Math.min(H * 0.14, dev * am));
            var ax = W / 2 + (j - (cnt - 1) / 2) * sp + (rowsN > 1 ? (row ? 0.25 : -0.25) * sp : 0);
            var cyy = rowsN > 1 ? (row ? H * 0.64 : H * 0.4) + dev * 0.5 : H * 0.52 + dev;
            var attach = tag ? tagH * 0.4 : size * 0.56;
            var L = Math.max(H * 0.06, cyy - attach - railY), T = 1.5 * Math.sqrt(L / (H * 0.45)), w = Math.PI * 2 / T;
            var ta = 0.12 + i * gap, ampK = rowsN > 1 && row === 1 ? 0.5 : 1, dk = sp / L / DEG;
            var hd = 'var TA=' + jzN(ta) + ',LL=' + jzN(L) + ';var tau=time-TA,gw=oc((time-(TA-0.3))/0.3),bn=tau>0?1+0.05*Math.exp(-tau/0.18)*Math.cos(tau*30):1;';
            // string (+ pin, + tag) swinging about its rail point
            var S = lb1_ng(lb1_shape(ctx, 'string ' + (i + 1), ax, railY));   // (still the glyph's parent in the ghosts)
            var pc = i % 2 ? lb1_plateCol(sc, [sc.accent, sc.ink]) : lb1_plateCol(sc, [sc.ink, sc.fg]);
            if (tag) {
                var gt = jzGrp(S, 'tag');
                var gh = lb1_sub(gt, 'hole'); jzAddEllipse(gh, Math.max(2 * k, size * 0.055) * 2, Math.max(2 * k, size * 0.055) * 2, 0, -tagH / 2 + tagH * 0.1); jzAddFill(gh, sc.bg);
                var gp = lb1_sub(gt, 'plate'); lb1_rrect(gp, tagW, tagH, tagW * 0.12); jzAddFill(gp, pc);
                jzGX(gt).property('ADBE Vector Position').setValue([0, L + attach]);
                lb1_gPos(gt, TH + hd + '[value[0],value[1]+(bn-1)*LL]');
                lb1_gSc(gt, TH + hd + 'var e=tau<=0?0:ob(tau/0.18,1.4)*K;[value[0]*e,value[1]*e]');
            }
            if (rail) { var gd = jzGrp(S, 'pin'), pr = Math.max(2.5 * k, M * 0.004); jzAddEllipse(gd, pr * 2, pr * 2); jzAddFill(gd, sc.fg); lb1_gOp(gd, TH + hd + '(gw>0?100:0)*K'); }
            var gs = jzGrp(S, 'string');
            lb1_line(gs, [[0, 0], [0, L * 1.06]], sc.sub, strokeW, 90);
            jzAddTrimPaths(gs, TH + hd + 'Math.max(0,Math.min(100,bn*gw/1.06*100))');
            lb1_gOp(gs, TH + hd + '(gw>0?100:0)*K');
            var th = 'var th=' + jzN(amp * dk * ampK) + '*Math.sin(' + jzN(w) + '*time+' + jzN(jzR(s, i, 5) * 6) + ');if(tau>0)th+=' + jzN(kick * dk * ampK * (jzR(s, i, 6) < 0.5 ? 1 : -1)) + '*Math.exp(-tau/0.7)*Math.sin(' + jzN(w * 1.3) + '*tau);';
            jzSetExpr(jzXf(S, 'ADBE Rotate Z'), TH + hd + th + 'value-th');
            items.push({ i: i, S: S, L: L, attach: attach, ta: ta, ax: ax, cy: cyy, pc: pc, hd: hd });
            bb = jzUnion(bb, lb1_bb(ax, railY + L + attach, tag ? tagW : csize, tag ? tagH : csize));
        }
        // the glyphs ride on their strings (parented), their own motion stays on the text layer
        for (i = 0; i < items.length; i++) {
            var q = items[i], ch = chars[q.i];
            var yOff = q.L + q.attach + (tag ? tagH * 0.06 : 0);
            var TL = lb1_textAt(ctx, ch, { font: font, size: csize, color: tag ? lb1_onCol(sc, q.pc) : sc.fg, x: q.ax, y: railY + yOff, name: ch });
            lb1_parent(TL, q.S, 0, yOff);
            lb1_posAnim(TL, 'JZ Bounce', q.L * 0.06, TH + q.hd, 'if(tau>0)dy=(bn-1)*LL');
            jzAnimate(ctx, TL, { mi: lb1_miAt(ctx, q.ta), noHold: tag ? lb1_plateHold(ctx) : false, treat: tag ? false : true });
        }
        return bb;
    }
});

/* ================================================================== 3 orbit \u2014 \u5468\u56DE */
jzReg('layout', 'orbit', {
    plan: function (rng, cut, st) {
        return {
            font: rng.pick(jzFontsOf(st, ['display', 'serif'])), fo: rng.pick(jzFontsOf(st, ['body', 'serif', 'display'])),
            variant: rng.pick(['ring', 'ring', 'atom', 'wide']), tilt: rng.range(5, 13) * rng.pick([1, -1]),
            speed: rng.range(22, 40) * rng.pick([1, -1]), unit: rng.pick(['line', 'self', 'line'])
        };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, s = c.seed, M = jzU(ctx), k = lb1_k(ctx), i, j, r;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), fo = jzP(ctx, 'fo', jzBodyF(ctx)), variant = jzP(ctx, 'variant', 'ring');
        var tilt0 = jzP(ctx, 'tilt', 8), speed = jzP(ctx, 'speed', 30), unitK = jzP(ctx, 'unit', 'line');
        var port = W < H, mt = lb1_lines(c.text, W, H, 10, 5), lo = { track: 0.04, lead: 1.15 };
        var size = Math.min(lb1_fitSize(mt, W * (port ? 0.64 : 0.54), H * 0.3, lo), H * 0.19), mm = lb1_measure(mt, size, lo);
        var cx = W / 2, cy = H / 2, txt = lb1_clean(c.text), line = lb1_clean(c.lineText || ''), rom = lb1_roma(txt);
        var units = [];
        units.push(unitK === 'line' && line && line !== txt && jzChars(line).length <= 40 ? line + '\u30FB' : txt + '\u30FB');
        units.push(rom ? rom + ' \u30FB ' : (c.words && c.words.length > 1 ? c.words.join('\u30FB') + '\u30FB' : txt + ' \u30FB '));
        var nR = variant === 'atom' ? 2 : 1, rings = [], wide = variant === 'wide';
        for (r = 0; r < nR; r++) {
            var rx = jzClamp(Math.max(mm.w / 2 + size * (wide ? 1.4 : 1.05), M * (wide ? 0.44 : 0.36)), M * 0.2, W * 0.47);
            var ry = Math.min(rx * 0.62, Math.max(rx * (variant === 'atom' ? 0.36 : wide ? 0.2 : 0.27), mm.h / 2 + size * (r ? 0.75 : 0.5)));
            var tilt = (variant === 'atom' ? (r ? -1 : 1) * (Math.abs(tilt0) * 0.6 + 8) : tilt0) * Math.PI / 180;
            var os = jzClamp(M * (wide ? 0.05 : 0.042), 14 * k, 64 * k) * (r ? 0.82 : 1);
            var unit = jzChars(units[r]);
            var perim = Math.PI * (3 * (rx + ry) - Math.sqrt((3 * rx + ry) * (rx + 3 * ry)));
            var cnt = Math.max(Math.min(unit.length, 44), Math.min(44, Math.floor(perim / (os * 1.7))));
            rings.push({ rx: rx, ry: ry, tilt: tilt, os: os, unit: unit, cnt: cnt, sp: speed * (r ? -0.8 : 1), font: r ? jzMonoF(ctx) : fo });
        }
        var TH = jzTH(ctx), lw = Math.max(1 * k, M * 0.0016), a0 = jzR(s, 9) * Math.PI * 2;
        var grow = 'var gr=(0.7+0.3*oe(time/0.7))*(1+0.25*PO);';
        function halves(front) {
            var S = lb1_ng(lb1_shape(ctx, front ? 'orbit lines front' : 'orbit lines back', 0, 0));
            for (var rr = 0; rr < rings.length; rr++) {
                var R = rings[rr], cT = Math.cos(R.tilt), sT = Math.sin(R.tilt), pts = [];
                for (var q = 0; q <= 40; q++) { var a = front ? q / 40 * Math.PI : Math.PI + q / 40 * Math.PI, lx = Math.cos(a) * R.rx, ly = Math.sin(a) * R.ry; pts.push([cx + lx * cT - ly * sT, cy + lx * sT + ly * cT]); }
                var g = jzGrp(S, 'ring ' + (rr + 1));
                lb1_line(g, pts, sc.sub, lw);
                jzGX(g).property('ADBE Vector Anchor').setValue([cx, cy]); jzGX(g).property('ADBE Vector Position').setValue([cx, cy]);
                lb1_gSc(g, TH + grow + '[value[0]*gr,value[1]*gr]');
            }
            jzSetExpr(jzXf(S, 'ADBE Opacity'), TH + (front ? 65 : 40) + '*oe(time/0.7)*K');
            return S;
        }
        function ringLayer(R, rr, front) {
            var chs = [], JJ = [], SEP = [];
            for (var q = 0; q < R.cnt; q++) { var ch = R.unit[q % R.unit.length]; if (lb1_isSp(ch)) continue; chs.push(ch); JJ.push(q); SEP.push(ch === '\u30FB'); }
            if (!chs.length) return null;
            var G = lb1_glyphs(ctx, chs, { font: R.font, size: R.os, color: sc.sub, name: 'orbit ' + (rr + 1) + (front ? ' front' : ' back') }); lb1_ng(G.L);
            var hd = TH + grow + 'var JJ=' + lb1_arr(JJ) + ',jj=JJ[textIndex-1]||0;var an=' + jzN(a0) + '+time*' + jzN(R.sp * Math.PI / 180) + '+jj/' + R.cnt + '*Math.PI*2;var z=Math.sin(an),dd=(z+1)/2;';
            lb1_placeAnim(ctx, G, 'JZ Orbit', hd, 'var lx=Math.cos(an)*' + jzN(R.rx) + '*gr,ly=z*' + jzN(R.ry) + '*gr;x=' + jzN(cx) + '+lx*' + jzN(Math.cos(R.tilt)) + '-ly*' + jzN(Math.sin(R.tilt)) + ';y=' + jzN(cy) + '+lx*' + jzN(Math.sin(R.tilt)) + '+ly*' + jzN(Math.cos(R.tilt)));
            lb1_scAnim(G.L, 'JZ Depth Size', hd, 'sx=0.55+0.62*dd;sy=sx');
            lb1_opAnim(G.L, 'JZ Depth Fade', hd, 'a=' + (front ? '(z>=0)' : '(z<0)') + '?(0.18+0.82*Math.pow(dd,1.3))*oe(time/0.7)*K:0');
            lb1_colAnim(G.L, 'JZ Depth Colour', sc.fg, hd, 'm=dd>0.5?1:0');
            jzCharColors(G.L, sc.accent, SEP, 'JZ Separators');
            return G.L;
        }
        halves(false);
        for (r = 0; r < rings.length; r++) ringLayer(rings[r], r, false);
        var ML = lb1_textAt(ctx, mt, { font: font, size: size, color: sc.fg, x: cx, y: cy, track: 0.04, lead: 1.15, name: c.text });
        jzAnimate(ctx, ML, { mi: 0 });
        var FS = halves(true);
        var hm = jzMaskRect(FS, cx - mm.w / 2 - size * 0.12, cy - mm.h / 2 - size * 0.1, cx + mm.w / 2 + size * 0.12, cy + mm.h / 2 + size * 0.1);
        try { hm.maskMode = MaskMode.SUBTRACT; } catch (e) {}
        for (r = 0; r < rings.length; r++) ringLayer(rings[r], r, true);
        return lb1_bb(cx, cy, mm.w, mm.h);
    }
});

/* ================================================================== 4 tunnel \u2014 \u30C8\u30F3\u30CD\u30EB */
jzReg('layout', 'tunnel', {
    plan: function (rng, cut, st) {
        return {
            font: rng.pick(jzFontsOf(st, ['display', 'body'])), fontC: rng.pick(jzFontsOf(st, ['display', 'serif'])),
            q: rng.range(1.42, 1.62), speed: rng.range(0.22, 0.42) * rng.pick([1, 1, -1]), style: rng.pick(['outline', 'fill', 'alt']),
            unit: rng.pick(['line', 'text', 'line']), persp: rng.chance(0.65)
        };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, M = jzU(ctx), k = lb1_k(ctx), i, m;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), fontC = jzP(ctx, 'fontC', jzFontKeyOf(ctx.st, 'display'));
        var q = jzP(ctx, 'q', 1.5), speed = jzP(ctx, 'speed', 0.3), style = jzP(ctx, 'style', 'outline'), unitK = jzP(ctx, 'unit', 'line'), persp = !!jzP(ctx, 'persp', true);
        var cx = W / 2, cy = H / 2, mt = lb1_lines(c.text, W, H, 10, 5), lo = { track: 0.05, lead: 1.15 };
        var size = Math.min(lb1_fitSize(mt, W * (W < H ? 0.44 : 0.5), H * 0.26, lo), H * 0.16), mm = lb1_measure(mt, size, lo);
        var hw = mm.w / 2 + size * 0.55, hh = mm.h / 2 + size * 0.5;
        var ra = W < H ? Math.min(0.95, Math.sqrt((hw / hh) * (W / H))) : W / H;
        var A = W / 2, B = A / ra, s0 = Math.max(hw / A, hh / B, 0.12);
        var Lmax = Math.log(Math.max(W * 0.66 / A, H * 0.66 / B) / s0) / Math.log(q);
        var src = (unitK === 'line' && c.lineText && lb1_clean(c.lineText) !== lb1_clean(c.text)) ? c.lineText : c.text;
        var unit = jzChars(jzTrim(String(src).replace(/\s+/g, ' ')) + '\u3000'), NU = unit.length;
        var lw = Math.max(1 * k, M * 0.0015), sg = speed < 0 ? -1 : 1, TH = jzTH(ctx);
        // which frames ever exist: L = m - 1 + phase(t) must pass through [0, Lmax + 0.2]
        var pmin = 1e9, pmax = -1e9;
        for (var t = 0; t <= c.dur + 1e-6; t += 1 / 48) {
            var e0 = t / 0.6 >= 1 ? 1 : 1 - Math.pow(2, -10 * t / 0.6), p0 = t * speed + (1 - e0) * 1.2 * sg;
            pmin = Math.min(pmin, p0); pmax = Math.max(pmax, p0);
        }
        var mLo = Math.ceil(1 - pmax - 1e-6), mHi = Math.floor(Lmax + 1.2 - pmin);
        if (mHi - mLo > 13) mLo = mHi - 13;
        var ph = 'var ph=time*' + jzN(speed) + '+(1-oe(time/0.6))*' + jzN(1.2 * sg) + ';';
        var alpha = 'var sm=cl(Lv/0.9);sm=sm*sm*(3-2*sm);var al=(Lv<0||Lv>' + jzN(Lmax + 0.2) + '||SS*' + jzN(Math.min(A, B) * 0.15) + '<' + jzN(3 * k) + ')?0:sm*(0.3+0.7*cl(Lv/' + jzN(Math.max(0.01, Lmax)) + '*1.4))*oe(time/0.6)*K;';
        // perspective rails
        if (persp) {
            var RS = lb1_ng(lb1_shape(ctx, 'tunnel rails', 0, 0)), g0 = jzGrp(RS, 'rails'), sx0 = s0 * A, sy0 = s0 * B, cs = [[-1, -1], [1, -1], [1, 1], [-1, 1]];
            for (i = 0; i < 4; i++) jzAddPath(g0, [[cx + cs[i][0] * W * 0.75, cy + cs[i][1] * H * 0.75], [cx + cs[i][0] * sx0, cy + cs[i][1] * sy0]], false);
            jzAddStroke(g0, sc.sub, lw);
            jzSetExpr(jzXf(RS, 'ADBE Opacity'), TH + '28*oe(time/0.6)*K');
        }
        // thin frame lines (top / bottom of every frame)
        var LS = lb1_ng(lb1_shape(ctx, 'tunnel lines', 0, 0));
        for (m = mHi; m >= mLo; m--) {
            var gl = jzGrp(LS, 'frame ' + m), hdm = TH + ph + 'var Lv=' + m + '-1+ph,SS=' + jzN(s0) + '*Math.pow(' + jzN(q) + ',Lv);';
            // (each rect is fully set up before the next item is added: adding to the group invalidates held references in AE)
            var r1 = jzAddRect(gl, 2 * A, lw);
            jzSetExpr(r1.property('ADBE Vector Rect Size'), hdm + '[' + jzN(2 * A) + '*SS,' + jzN(lw) + ']');
            jzSetExpr(r1.property('ADBE Vector Rect Position'), hdm + '[' + jzN(cx) + ',' + jzN(cy) + '-' + jzN(B) + '*SS+' + jzN(lw / 2) + ']');
            var r2 = jzAddRect(gl, 2 * A, lw);
            jzSetExpr(r2.property('ADBE Vector Rect Size'), hdm + '[' + jzN(2 * A) + '*SS,' + jzN(lw) + ']');
            jzSetExpr(r2.property('ADBE Vector Rect Position'), hdm + '[' + jzN(cx) + ',' + jzN(cy) + '+' + jzN(B) + '*SS-' + jzN(lw / 2) + ']');
            jzAddFill(gl, sc.sub);
            lb1_gOp(gl, hdm + alpha + '50*al');
        }
        // text frames: 4 edges of glyphs per frame (one layer each), built at scale 1 and zoomed by expression
        var f0 = Math.min(A, B) * 0.15, inset = f0 * 0.62, hl = 2 * A - inset * 2.4, vl = 2 * B - inset * 2.4;
        for (m = mHi; m >= mLo; m--) {
            var gi = ((m * 7) % NU + NU) % NU, chs = [], pts = [], rots = [];
            var edge = function (len, ex, ey, ux, uy, rot) {
                var arr = [], acc = 0, tt;
                for (tt = 0; tt < 80; tt++) { var ch = unit[(gi + tt) % NU], ad = lb1_adv(ch) * f0 * 1.06; if (acc + ad > len) { gi += tt; break; } acc += ad; arr.push(ch); }
                var ws = [], tw = 0;
                for (tt = 0; tt < arr.length; tt++) { ws.push(lb1_adv(arr[tt]) * f0); tw += ws[tt] + (tt < arr.length - 1 ? 0.06 * f0 : 0); }
                var x = -tw / 2;
                for (tt = 0; tt < arr.length; tt++) {
                    var cc = x + ws[tt] / 2; x += ws[tt] + 0.06 * f0;
                    if (lb1_isSp(arr[tt])) continue;
                    chs.push(arr[tt]); pts.push([ex + ux * cc, ey + uy * cc]); rots.push(rot);
                }
            };
            edge(hl, cx, cy - B + inset, 1, 0, 0);
            edge(vl, cx + A - inset, cy, 0, 1, 90);
            edge(hl, cx, cy + B - inset, -1, 0, 180);
            edge(vl, cx - A + inset, cy, 0, -1, -90);
            if (!chs.length) continue;
            var outline = style === 'outline' || (style === 'alt' && ((m % 2) + 2) % 2 === 0);
            var col = ((m % 3) + 3) % 3 === 0 ? sc.accent : sc.fg;
            var go = { font: font, size: f0, color: col, name: 'tunnel frame ' + m };
            if (outline) { go.fill = false; go.stroke = Math.max(1 * k, f0 * 0.035); go.strokeColor = col; }
            var FL = lb1_glyphs(ctx, chs, go);
            lb1_place(FL, pts); jzCharRotations(FL.L, rots, 'JZ Edge');
            FL = lb1_ng(FL.L);
            jzXf(FL, 'ADBE Anchor Point').setValue([cx, cy]); jzXf(FL, 'ADBE Position').setValue([cx, cy]);
            var hdf = TH + ph + 'var Lv=' + m + '-1+ph,SS=' + jzN(s0) + '*Math.pow(' + jzN(q) + ',Lv);';
            jzSetExpr(jzXf(FL, 'ADBE Scale'), hdf + '[value[0]*SS,value[1]*SS]');
            jzSetExpr(jzXf(FL, 'ADBE Opacity'), hdf + alpha + 'value*al');
        }
        var ML = lb1_textAt(ctx, mt, { font: fontC, size: size, color: sc.fg, x: cx, y: cy, track: 0.05, lead: 1.15, name: c.text });
        jzAnimate(ctx, ML, { mi: 0 });
        return lb1_bb(cx, cy, mm.w, mm.h);
    }
});

/* ================================================================== 5 wordCloud \u2014 \u30EF\u30FC\u30C9\u30AF\u30E9\u30A6\u30C9 */
function lb1_segType(ch) {
    if (/[\s\u3000]/.test(ch)) return 'S';
    if (jzIsPunct(ch)) return 'P';
    if (jzIsKanji(ch)) return 'K';
    if (jzIsHira(ch)) return 'H';
    if (jzIsKata(ch) || ch === '\u30FC') return 'T';
    if (jzIsLatin(ch)) return 'L';
    return 'O';
}
// script runs (kanji + following kana stay together) \u2014 the browser's segmenter fallback
function lb1_segments(text) {
    var a = jzChars(String(text || '')), out = [], cur = '', ct = '';
    for (var i = 0; i < a.length; i++) { var t = lb1_segType(a[i]); if (cur && t !== ct && !(ct === 'K' && t === 'H')) { out.push(cur); cur = ''; } cur += a[i]; ct = t; }
    if (cur) out.push(cur);
    return out;
}
jzReg('layout', 'wordCloud', {
    plan: function (rng, cut, st) {
        var pool = jzFontsOf(st, ['display', 'serif', 'body']);
        return {
            font: rng.pick(jzFontsOf(st, ['display'])), fonts: [rng.pick(pool), rng.pick(pool), rng.pick(jzFontsOf(st, ['body', 'serif']))],
            vert: rng.pick([0, 0.3, 0.5]), accentN: rng.int(1, 3), outlineK: rng.pick([0, 0.2, 0.35]), wide: rng.range(1.2, 1.7)
        };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, s = c.seed, M = jzU(ctx), k = lb1_k(ctx), i, j;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), fonts = jzP(ctx, 'fonts', null);
        if (!fonts || !fonts.length) fonts = [font, jzSerifF(ctx), jzBodyF(ctx)];
        var vertK = jzP(ctx, 'vert', 0.3), accentN = jzP(ctx, 'accentN', 2), outlineK = jzP(ctx, 'outlineK', 0.2), wide = jzP(ctx, 'wide', 1.4);
        var port = W < H, mt = lb1_lines(c.text, W, H, 9, 5), lo = { track: 0.03, lead: 1.1 };
        var size = Math.min(lb1_fitSize(mt, W * (port ? 0.7 : 0.52), H * 0.24, lo), H * 0.18), mm = lb1_measure(mt, size, lo);
        // ---- word pool (same order as the browser)
        var txt = lb1_clean(c.text), pool = [];
        function add(w) {
            w = jzTrim(String(w || ''));
            if (!w || jzCount(w) > 14 || jzIndexOf(pool, w) >= 0) return;
            var ch = jzChars(w), allP = true;
            for (var q = 0; q < ch.length; q++) if (!jzIsPunct(ch[q])) { allP = false; break; }
            if (!allP) pool.push(w);
        }
        var src = c.lineText || c.text, arr = jzChunk(src);
        for (i = 0; i < arr.length; i++) add(arr[i]);
        arr = c.words || [];
        for (i = 0; i < arr.length; i++) add(arr[i]);
        arr = lb1_segments(src);
        for (i = 0; i < arr.length; i++) { var g0 = jzTrim(arr[i]), hasK = false, gc = jzChars(g0); for (j = 0; j < gc.length; j++) if (jzIsKanji(gc[j])) hasK = true; if (jzCount(g0) >= 2 || hasK) add(g0); }
        var rom = lb1_roma(txt); if (rom) add(rom);
        if (c.note) add(c.note);
        add(txt);
        arr = jzChars(txt);
        for (i = 0; i < arr.length; i++) if (jzIsKanji(arr[i])) add(arr[i]);
        if (jzCount(c.lineText || '') <= 14) add(c.lineText);
        if (!pool.length) pool.push(txt || '\u30FB');
        // ---- spiral placement around the lyric (box collisions)
        var boxes = [[W / 2 - mm.w / 2 - size * 0.25, H / 2 - mm.h / 2 - size * 0.18, W / 2 + mm.w / 2 + size * 0.25, H / 2 + mm.h / 2 + size * 0.18]];
        var stretchX = port ? 0.75 : wide, stretchY = port ? 1.3 : 1, maxR = Math.sqrt(W * W + H * H) * 0.55, words = [], acc = 0;
        for (var kk = 0; kk < 70 && words.length < 44; kk++) {
            var word = pool[kk % pool.length], latin = /[A-Za-z]/.test(word);
            var vertical = !latin && jzCount(word) <= 6 && jzR(s, kk, 3) < vertK;
            var tier = Math.pow(0.95, words.length);
            var fs = Math.max(M * 0.018, Math.min(W * 0.4 / Math.max(1.5, jzCount(word)), size * 0.6, M * 0.125 * tier * (0.65 + 0.7 * jzR(s, kk, 4))));
            var wo = { track: 0.02, vertical: vertical }, m = lb1_measure(word, fs, wo);
            if (m.w > W * 0.9 || m.h > H * 0.9) { fs *= Math.min(W * 0.9 / m.w, H * 0.9 / m.h); m = lb1_measure(word, fs, wo); }
            var pad = fs * 0.12, w2 = m.w / 2 + pad, h2 = m.h / 2 + pad, an0 = jzR(s, kk, 5) * Math.PI * 2, placed = null;
            for (var t = 0; t < 520; t++) {
                var ang = an0 + t * 0.42, rad = (t / 520) * maxR;
                var x = W / 2 + Math.cos(ang) * rad * stretchX, y = H / 2 + Math.sin(ang) * rad * stretchY * 0.8;
                if (x - w2 < W * 0.035 || x + w2 > W * 0.965 || y - h2 < H * 0.045 || y + h2 > H * 0.955) continue;
                var hit = false;
                for (var b = 0; b < boxes.length; b++) { var bx = boxes[b]; if (x - w2 < bx[2] && x + w2 > bx[0] && y - h2 < bx[3] && y + h2 > bx[1]) { hit = true; break; } }
                if (!hit) { placed = [x, y]; break; }
            }
            if (!placed) { acc++; if (acc > 16) break; continue; }
            boxes.push([placed[0] - w2, placed[1] - h2, placed[0] + w2, placed[1] + h2]);
            var d = Math.sqrt(Math.pow((placed[0] - W / 2) / W, 2) + Math.pow((placed[1] - H / 2) / H, 2)), idx = words.length, ai = jzIndexOf([4, 9, 15], idx);
            words.push({ text: word, font: fonts[kk % fonts.length], size: fs, x: placed[0], y: placed[1], vertical: vertical, delay: 0.06 + d * 0.9 + idx * 0.012,
                col: ai >= 0 && ai < accentN ? 'a' : (idx % 2 ? 's' : 'f'), a: idx % 2 ? 0.8 : 0.5 + 0.2 * tier, outline: jzR(s, kk, 7) < outlineK });
        }
        // ---- one text layer per (font, outline) group; every glyph placed / popped per word by expression
        var TH = jzTH(ctx), groups = {}, order = [];
        for (i = 0; i < words.length; i++) { var key = words[i].font + (words[i].outline ? '|o' : '|f'); if (!groups[key]) { groups[key] = []; order.push(key); } groups[key].push(i); }
        for (var gk = 0; gk < order.length; gk++) {
            var list = groups[order[gk]], fsL = 1, chs = [], WI = [], RX = [], RY = [], SC = [], SUB = [], ACC = [];
            for (i = 0; i < list.length; i++) fsL = Math.max(fsL, words[list[i]].size);
            for (i = 0; i < list.length; i++) {
                var wd = words[list[i]], lay = lb1_layout(wd.text, wd.size, { track: 0.02, vertical: wd.vertical });
                for (j = 0; j < lay.length; j++) {
                    if (lay[j].sp) continue;
                    chs.push(lay[j].ch); WI.push(list[i]); RX.push(lay[j].x); RY.push(lay[j].y); SC.push(wd.size / fsL);
                    SUB.push(wd.col === 's'); ACC.push(wd.col === 'a');
                }
            }
            if (!chs.length) continue;
            var ol = words[list[0]].outline, go = { font: words[list[0]].font, size: fsL, color: sc.fg, name: 'word cloud ' + (gk + 1) };
            if (ol) { go.fill = false; go.stroke = Math.max(1 * k, fsL * 0.03); go.strokeColor = sc.fg; }
            var G = lb1_glyphs(ctx, chs, go); lb1_ng(G.L);
            var WX = [], WY = [], DL = [], AL = [];
            for (i = 0; i < words.length; i++) { WX.push(words[i].x); WY.push(words[i].y); DL.push(words[i].delay); AL.push(words[i].a); }
            var hd = TH + 'var WI=' + lb1_arr(WI) + ',w=WI[textIndex-1]||0,DLs=' + lb1_arr(DL) + ',q=cl((time-DLs[w])/0.32),sq=q<=0?0:ob(q,1.7)*(1-0.25*ic(PO));';
            lb1_placeAnim(ctx, G, 'JZ Cloud Place', hd + 'var WX=' + lb1_arr(WX) + ',WY=' + lb1_arr(WY) + ',RX=' + lb1_arr(RX) + ',RY=' + lb1_arr(RY) + ',NA=' + jzN(M * 0.004) + ';',
                'x=WX[w]+noise(time*0.35+w*3.1+' + (s % 97) + ')*NA+RX[i]*sq;y=WY[w]+noise(time*0.3+w*5.7+' + (s % 89 + 50) + ')*NA+RY[i]*sq');
            lb1_scAnim(G.L, 'JZ Cloud Pop', hd + 'var SC=' + lb1_arr(SC) + ';', 'sx=SC[textIndex-1]*sq;sy=sx');
            lb1_opAnim(G.L, 'JZ Cloud Fade', hd + 'var AL=' + lb1_arr(AL) + ';', 'a=Math.min(1,q*2.5)*K*AL[w]');
            if (ol) {
                jzAnimator(G.L, 'JZ Cloud Sub', [['ADBE Text Stroke Color', jzHex(sc.sub)]], 'var a=' + lb1_arr(SUB.length ? (function () { var o = []; for (var z = 0; z < SUB.length; z++) o.push(SUB[z] ? 1 : 0); return o; })() : [0]) + ';(a[textIndex-1]||0)*100');
                jzAnimator(G.L, 'JZ Cloud Accent', [['ADBE Text Stroke Color', jzHex(sc.accent)]], 'var a=' + lb1_arr((function () { var o = []; for (var z = 0; z < ACC.length; z++) o.push(ACC[z] ? 1 : 0); return o; })()) + ';(a[textIndex-1]||0)*100');
            } else {
                jzCharColors(G.L, sc.sub, SUB, 'JZ Cloud Sub');
                jzCharColors(G.L, sc.accent, ACC, 'JZ Cloud Accent');
            }
        }
        var ML = lb1_textAt(ctx, mt, { font: font, size: size, color: sc.fg, x: W / 2, y: H / 2, track: 0.03, lead: 1.1, name: c.text });
        jzAnimate(ctx, ML, { mi: 0 });
        return lb1_bb(W / 2, H / 2, mm.w, mm.h);
    }
});

/* ================================================================== 6 bounceLine \u2014 \u8DF3\u306D\u308B */
jzReg('layout', 'bounceLine', {
    plan: function (rng, cut, st) {
        return {
            font: rng.pick(jzFontsOf(st, ['display'])), mode: rng.pick(['wave', 'wave', 'beat', 'hop']), hop: rng.range(0.38, 0.6),
            tempo: rng.range(0.42, 0.6), shadow: rng.chance(0.7), line: rng.pick(['line', 'line', 'dots', 'none']), tilt: rng.chance(0.5)
        };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, s = c.seed, M = jzU(ctx), k = lb1_k(ctx), i;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), mode = jzP(ctx, 'mode', 'wave'), hop = jzP(ctx, 'hop', 0.5), tempo = jzP(ctx, 'tempo', 0.5);
        var shadow = !!jzP(ctx, 'shadow', true), lineK = jzP(ctx, 'line', 'line'), tilt = !!jzP(ctx, 'tilt', false);
        var mt = lb1_lines(c.text, W, H, 11, 6), nL = mt.split('\r').length, lo = { track: 0.1, lead: 2.0 };
        var size = Math.min(lb1_fitSize(mt, W * 0.84, H * (nL > 1 ? 0.52 : 0.3), lo), H * 0.2);
        var cx = W / 2, cy = H / 2 + (nL > 1 ? 0 : H * 0.03);
        var lay = lb1_layout(mt, size, lo), gl = [], MP = [];
        for (i = 0; i < lay.length; i++) { MP.push(-1); if (!lay[i].sp) { MP[i] = gl.length; gl.push(lay[i]); } }
        var n = gl.length;
        if (!n) return null;
        var hopH = size * hop, hd = jzClamp(tempo * 0.62, 0.22, 0.34), sq = 0.13, TH = jzTH(ctx);
        // hop clock per glyph -> tau (time since take-off), then the squash / stretch / height profile
        var clock;
        if (mode === 'wave') {
            var gap = Math.min(0.09, 0.9 / n), P = Math.max(tempo * 2.2, n * gap + hd + 0.35);
            clock = 'var tt=t-gi*' + jzN(gap) + ';tau=tt<-0.1?-1:((tt+0.07)%' + jzN(P) + ')-0.07;';
        } else if (mode === 'beat') {
            clock = 'var kb=Math.floor(t/' + jzN(tempo) + ');tau=(((kb%' + n + ')+' + n + ')%' + n + ')===gi?t-kb*' + jzN(tempo) + ':-1;';
        } else {
            var PP = [], PH = [];
            for (i = 0; i < n; i++) { PP.push(0.9 + 0.8 * jzR(s, i, 7)); PH.push(jzR(s, i, 8) * PP[i]); }
            clock = 'var PP=' + lb1_arr(PP) + ',PH=' + lb1_arr(PH) + ';var pp=PP[gi],ps=PH[gi];tau=((t+ps)%pp)-0.07;if(t+ps<pp-0.07)tau=-1;';
        }
        var prof = 'var t=time-0.25,tau=-1;if(gi>=0&&t>-0.1){' + clock + '}var h=0,px=1,py=1,HD=' + jzN(hd) + ',SQ=' + sq + ';' +
            'if(tau>=-0.07&&tau<=HD+SQ){if(tau<0){var kq=Math.sin(Math.PI*(tau+0.07)/0.07);px=1+0.08*kq;py=1-0.1*kq;}else if(tau<HD){var uu=tau/HD;h=4*uu*(1-uu);px=0.94;py=1.08;}else{var kq=Math.sin(Math.PI*(tau-HD)/SQ);px=1+0.2*kq;py=1-0.24*kq;}}';
        var gHead = TH + 'var MP=' + lb1_arr(MP) + ',gi=MP[textIndex-1];if(gi==null)gi=-1;' + prof;
        // shadows under the glyphs (one shape layer)
        if (shadow) {
            var SH = lb1_ng(lb1_shape(ctx, 'hop shadows', 0, 0));
            for (i = 0; i < n; i++) {
                var gs = jzGrp(SH, 'shadow ' + (i + 1)), el = jzAddEllipse(gs, size * 0.68, size * 0.136, cx + gl[i].x, cy + gl[i].y + size * 0.56);
                var sh = TH + 'var gi=' + i + ';' + prof + 'var kk=1-h*0.55;';
                jzSetExpr(el.property('ADBE Vector Ellipse Size'), sh + '[' + jzN(size * 0.68) + '*kk*px,' + jzN(size * 0.136) + '*kk*px]');
                jzAddFill(gs, sc.sub);                              // (after the ellipse is set up: adding invalidates `el` in AE)
                lb1_gOp(gs, sh + '28*kk*K*oe(time/0.5)');
            }
        }
        // the lyric (one text layer): per-glyph hop, squash about the glyph's foot, tilt, beat colour
        var ML = lb1_textAt(ctx, mt, { font: font, size: size, color: sc.fg, x: cx, y: cy, track: 0.1, lead: 2.0, name: c.text });
        lb1_posAnim(ML, 'JZ Hop', size * 2, gHead, 'dy=' + jzN(size * 0.5) + '*(1-py)-h*' + jzN(hopH));
        lb1_scAnim(ML, 'JZ Squash', gHead, 'sx=px;sy=py');
        if (tilt) lb1_rotAnim(ML, 'JZ Hop Tilt', 7, gHead, 'if(h>0)r=Math.sin(tau/HD*Math.PI*2)*7*(gi%2?1:-1)');
        if (mode === 'beat') lb1_colAnim(ML, 'JZ Beat Colour', sc.accent, gHead, 'm=h>0?1:0');
        lb1_cutStagger(ctx, ML, MP, 1);
        jzAnimate(ctx, ML, { mi: 0 });
        // base line / dots per text line
        if (lineK !== 'none') {
            var LS = lb1_ng(lb1_shape(ctx, 'hop baseline', 0, 0)), lw = Math.max(1.5 * k, M * 0.0022), lines = {};
            for (i = 0; i < n; i++) { var g = gl[i], q = lines[g.li] || (lines[g.li] = { x0: 1e9, x1: -1e9, y: cy + g.y + size * 0.5 }); q.x0 = Math.min(q.x0, cx + g.x - g.w / 2); q.x1 = Math.max(q.x1, cx + g.x + g.w / 2); }
            for (var li in lines) if (lines.hasOwnProperty(li)) {
                var Lq = lines[li], pad = size * 0.35, x0 = Lq.x0 - pad, x1 = Lq.x1 + pad, yy = Lq.y + size * 0.1, gg = jzGrp(LS, 'line ' + li);
                if (lineK === 'line') { lb1_line(gg, [[x0, yy], [x1, yy]], sc.sub, lw, 70); jzAddTrimPaths(gg, JZ_FNS + '100*oe(time/0.5)'); }
                else {
                    var mm = Math.max(4, Math.round((x1 - x0) / (size * 0.25)));
                    jzAddEllipse(gg, lw * 2.4, lw * 2.4, x0, yy); jzAddFill(gg, sc.sub, 80);
                    var rp = jzVecs(gg).addProperty('ADBE Vector Filter - Repeater');
                    jzSetExpr(rp.property('ADBE Vector Repeater Copies'), JZ_FNS + 'Math.floor(oe(time/0.5)*' + mm + '+1e-6)+1');
                    jzVecs(gg).property('ADBE Vector Filter - Repeater').property('ADBE Vector Repeater Transform').property('ADBE Vector Repeater Position').setValue([(x1 - x0) / mm, 0]);
                }
            }
            jzSetExpr(jzXf(LS, 'ADBE Opacity'), TH + '100*K');
        }
        return lb1_bb(cx, cy, lay.W, lay.H);
    }
});

/* ================================================================== 7 elastic \u2014 \u30B4\u30E0 */
jzReg('layout', 'elastic', {
    plan: function (rng, cut, st) {
        var port = cut.H > cut.W;
        return {
            font: rng.pick(jzFontsOf(st, ['display', 'serif'])), orient: port ? (cut.n <= 5 ? rng.pick(['v', 'v', 'h']) : 'v') : rng.pick(['h', 'h', 'diag']),
            ang: rng.range(6, 12) * rng.pick([1, -1]), every: rng.range(1.1, 1.6), amp: rng.range(0.3, 0.45), anchor: rng.pick(['dot', 'ring', 'pin'])
        };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, M = jzU(ctx), k = lb1_k(ctx), i, j;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), orient = jzP(ctx, 'orient', 'h'), every = jzP(ctx, 'every', 1.3), amp = jzP(ctx, 'amp', 0.38), anchor = jzP(ctx, 'anchor', 'dot');
        var vert = orient === 'v', txt = vert ? lb1_clean(c.text) : jzTrim(c.text), ang = orient === 'diag' ? jzP(ctx, 'ang', 8) : 0;
        var lo = { track: 0.08, vertical: vert }, avail = vert ? H * 0.58 : (W < H ? W * 0.7 : W * 0.62);
        var size = Math.min(lb1_fitSize(txt, vert ? W * 0.3 : avail, vert ? avail : H * 0.22, lo), H * 0.2);
        var lay = lb1_layout(txt, size, lo), gl = [], AL = [], MP = [];
        for (i = 0; i < lay.length; i++) { AL.push(vert ? lay[i].y : lay[i].x); MP.push(lay[i].sp ? -1 : gl.length); if (!lay[i].sp) gl.push(lay[i]); }
        var n = gl.length;
        if (!n) return null;
        var al = function (g) { return vert ? g.y : g.x; }, ext = function (g) { return vert ? g.h : g.w; };
        var a0 = al(gl[0]) - ext(gl[0]) / 2, a1 = al(gl[n - 1]) + ext(gl[n - 1]) / 2, half = (a1 - a0) / 2 + size * 0.85;
        var cx = W / 2, cy = H / 2, TH = jzTH(ctx);
        // spring stretch k(t), pluck standing wave wv(u) (u = 0..1 along the band; k cancels out of u)
        var hd = TH + 'var te=time-0.02,spr=te<=0?0:1-Math.exp(-te/0.1)*Math.cos(te*15),kk=Math.max(0.04,spr)*(1+0.6*ic(PO));' +
            'var tau=time>0.45?(time-0.45)%' + jzN(every) + ':-1,A=' + jzN(size * amp) + '*(time<0.45?0:1),HF=' + jzN(half) + ';' +
            'function wv(u){return tau<0?0:A*Math.sin(Math.PI*u)*Math.cos(tau*22)*Math.exp(-tau/0.42);}function sl(u){return tau<0?0:A*Math.PI/Math.max(1,2*HF*kk)*Math.cos(Math.PI*u)*Math.cos(tau*22)*Math.exp(-tau/0.42);}';
        // band + anchors (one shape layer in band space: x = along, y = across)
        var S = lb1_shape(ctx, 'elastic band', cx, cy), lw = Math.max(1.5 * k, size * 0.03), R = Math.max(5 * k, size * 0.1), sgn = vert ? -1 : 1;
        jzXf(S, 'ADBE Rotate Z').setValue(vert ? 90 : ang);
        var ends = [[-1, a0 - size * 0.12], [1, a1 + size * 0.12]], LMAX = Math.max(W, H) * 2, PC = 4;
        for (var e = 0; e < 2; e++) {
            var side = ends[e][0], tE = ends[e][1];
            // (anchor at side*HF*kk) -> (text end tE*kk): PC straight pieces following the wave
            for (j = 0; j < PC; j++) {
                var gp = jzGrp(S, 'band ' + (e + 1) + '.' + (j + 1));
                lb1_line(gp, [[0, 0], [LMAX, 0]], sc.sub, lw, 90);
                var seg = hd + 'var aA=' + side + '*HF*kk,aT=' + jzN(tE) + '*kk,pa=aA+(aT-aA)*' + jzN(j / PC) + ',pb=aA+(aT-aA)*' + jzN((j + 1) / PC) + ';' +
                    'var ua=(pa/kk+HF)/(2*HF),ub=(pb/kk+HF)/(2*HF),da=' + sgn + '*wv(ua),db=' + sgn + '*wv(ub);';
                jzAddTrimPaths(gp, seg + 'Math.min(100,Math.sqrt((pb-pa)*(pb-pa)+(db-da)*(db-da))/' + jzN(LMAX) + '*100)');
                lb1_gPos(gp, seg + '[pa,da]');
                lb1_gRot(gp, seg + 'Math.atan2(db-da,pb-pa)*180/Math.PI');
            }
        }
        // anchors: the accent parts are ghosted in the browser (own layer, same band space), the fg parts are not (band layer)
        var SA = lb1_shape(ctx, 'elastic anchors', cx, cy);
        jzXf(SA, 'ADBE Rotate Z').setValue(vert ? 90 : ang);
        for (e = 0; e < 2; e++) {
            var ga = anchor === 'dot' ? null : jzGrp(S, 'anchor ' + (e + 1)), gz = jzGrp(SA, 'anchor ' + (e + 1));
            if (anchor === 'dot') { var d1 = lb1_sub(gz, 'dot'); jzAddEllipse(d1, R * 2, R * 2); jzAddFill(d1, sc.accent); }
            else if (anchor === 'ring') {
                var d2 = lb1_sub(ga, 'core'); jzAddEllipse(d2, R * 0.9, R * 0.9); jzAddFill(d2, sc.fg);
                var d3 = lb1_sub(gz, 'ring'); jzAddEllipse(d3, R * 2.6, R * 2.6); jzAddStroke(d3, sc.accent, lw * 1.3);
            } else {
                var d4 = lb1_sub(gz, 'head'); jzAddEllipse(d4, R * 1.8, R * 1.8, 0, -sgn * R * 3.2); jzAddFill(d4, sc.accent);
                var d5 = lb1_sub(ga, 'foot'); jzAddEllipse(d5, R * 0.8, R * 0.8); jzAddFill(d5, sc.fg);
                var d6 = lb1_sub(ga, 'pin'); lb1_line(d6, [[0, -sgn * R * 3.2], [0, 0]], sc.fg, lw);
            }
            var gg = ga ? [ga, gz] : [gz];
            for (j = 0; j < gg.length; j++) {
                lb1_gPos(gg[j], hd + '[' + (e ? 1 : -1) + '*HF*kk,0]');
                lb1_gSc(gg[j], TH + 'var q=cl(time/0.2);var e=q<=0?0:ob(q,2)*K;[value[0]*e,value[1]*e]');
            }
        }
        lb1_ng(S);                                               // band (+ fg anchor parts): ghost off
        // the lyric rides on the band: along-stretch, squash / stretch, wave offset and slope per glyph
        var ML = lb1_textAt(ctx, txt, { font: font, size: size, color: sc.fg, x: cx, y: cy, track: 0.08, vertical: vert, name: c.text });
        if (ang) jzXf(ML, 'ADBE Rotate Z').setValue(ang);
        var gh = hd + 'var AL=' + lb1_arr(AL) + ',ag=AL[textIndex-1]||0,u=(ag+HF)/(2*HF),st=Math.max(0.15,Math.min(1.6,kk));';
        lb1_posAnim(ML, 'JZ Elastic Move', Math.max(W, H) * 2, gh, vert ? 'dx=wv(u);dy=ag*(kk-1)' : 'dx=ag*(kk-1);dy=wv(u)');
        lb1_scAnim(ML, 'JZ Elastic Stretch', gh, vert ? 'sx=1/Math.sqrt(st);sy=Math.min(2,st)' : 'sx=Math.min(2,st);sy=1/Math.sqrt(st)');
        lb1_rotAnim(ML, 'JZ Elastic Slope', 60, gh, 'r=' + (vert ? '-' : '') + 'Math.atan(sl(u))*180/Math.PI');
        lb1_cutStagger(ctx, ML, MP, 0.5);
        jzAnimate(ctx, ML, { mi: 0 });
        var bw = vert ? size : (a1 - a0), bh = vert ? (a1 - a0) : size;
        return lb1_bb(cx, cy, bw, bh);
    }
});

/* ================================================================== 8 crossBands \u2014 \u4EA4\u5DEE\u5E2F */
jzReg('layout', 'crossBands', {
    plan: function (rng, cut, st) {
        return {
            font: rng.pick(jzFontsOf(st, ['display'])), fb: rng.pick(jzFontsOf(st, ['body', 'display'])), ang: rng.range(13, 22),
            plate: rng.pick(['box', 'double', 'shadow']), speed: rng.range(0.7, 1.2), swap: rng.chance(0.5), sep: rng.pick(['\uFF0F', '\u30FB', '\u3000', '\u00D7'])
        };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, M = jzU(ctx), k = lb1_k(ctx), b;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), fb = jzP(ctx, 'fb', jzBodyF(ctx)), ang0 = jzP(ctx, 'ang', 16), plate = jzP(ctx, 'plate', 'box');
        var speed = jzP(ctx, 'speed', 1), swap = !!jzP(ctx, 'swap', false), sep = jzP(ctx, 'sep', '\uFF0F');
        var port = W < H, ang = port ? ang0 + 16 : ang0, bh = M * (port ? 0.12 : 0.105);
        var cols = [lb1_plateCol(sc, [sc.ink, sc.fg]), lb1_plateCol(sc, [sc.accent, sc.accent2, sc.sub])];
        if (swap) cols.reverse();
        var len = Math.sqrt(W * W + H * H) * 1.15, TH = jzTH(ctx);
        var unit = jzTrim(String(c.lineText || c.text).replace(/\s+/g, ' ')) + '\u3000' + sep + '\u3000';
        var fsz = bh * 0.46, per = Math.max(1, lb1_measure(unit, fsz, { track: 0.08 }).w + 0.08 * fsz), reps = Math.min(40, Math.ceil(len * 1.2 / per) + 2), row = '';
        for (var r = 0; r < reps; r++) row += unit;
        for (b = 0; b < 2; b++) {
            var a = b ? -ang : ang, dir = b ? -1 : 1, ar = a * Math.PI / 180;
            // the band grows from one end (scale on the group, anchored at that end)
            var B = lb1_ng(lb1_shape(ctx, 'band ' + (b + 1), W / 2, H / 2)), gb = jzGrp(B, 'band');
            jzAddRect(gb, len, bh); jzAddFill(gb, cols[b]);
            jzGX(gb).property('ADBE Vector Anchor').setValue([-dir * len / 2, 0]); jzGX(gb).property('ADBE Vector Position').setValue([-dir * len / 2, 0]);
            lb1_gSc(gb, TH + 'var e=oe((time-' + jzN(b * 0.08) + ')/0.5)*K;[value[0]*e,value[1]]');
            jzXf(B, 'ADBE Rotate Z').setValue(a);
            // scrolling copy inside the band (alpha matte = a copy of the band)
            var T = jzText(ctx, row, { font: fb, size: fsz, color: lb1_onCol(sc, cols[b]), x: 0, y: 0, align: 'left', track: 0.08, name: 'band text ' + (b + 1) });
            jzXf(T, 'ADBE Anchor Point').setValue([0, -LB1_CY * fsz]); jzXf(T, 'ADBE Rotate Z').setValue(a);
            jzSetExpr(jzXf(T, 'ADBE Position'), 'var per=' + jzN(per) + ',off=((time*' + jzN(speed * M * 0.12 * dir) + ')%per+per)%per,xl=' + jzN(-len * 0.6 - per) + '+off;[' + jzN(W / 2) + '+' + jzN(Math.cos(ar)) + '*xl,' + jzN(H / 2) + '+' + jzN(Math.sin(ar)) + '*xl]');
            var D = B.duplicate(); D.name = 'band ' + (b + 1) + ' matte';
            D.moveBefore(T);
            lb1_matte(T, D); lb1_ng(T);
        }
        // lyric plate at the crossing
        var mt = lb1_lines(c.text, W, H, 9, 5), lo = { track: 0.04, lead: 1.12 };
        var size = Math.min(lb1_fitSize(mt, W * (port ? 0.66 : 0.5), H * 0.24, lo), H * 0.17), mm = lb1_measure(mt, size, lo);
        var pw = mm.w + size * 0.9, ph = mm.h + size * 0.7, lw = Math.max(2 * k, size * 0.035);
        var PL = lb1_ng(lb1_shape(ctx, 'plate', W / 2, H / 2));
        if (plate === 'double') { var o = lw * 2.6, g2 = jzGrp(PL, 'inner'); jzAddRect(g2, pw - o * 2, ph - o * 2); jzAddStroke(g2, sc.fg, lw * 0.5); }
        var g1 = jzGrp(PL, 'plate'); jzAddRect(g1, pw, ph); jzAddStroke(g1, sc.fg, lw); jzAddFill(g1, sc.bg);
        if (plate === 'shadow') { var os = size * 0.12, g3 = jzGrp(PL, 'shadow'); jzAddRect(g3, pw, ph, 0, os, os); jzAddFill(g3, cols[1]); }
        jzSetExpr(jzXf(PL, 'ADBE Scale'), TH + 'var q=cl((time-0.1)/0.28);var e=q<=0?0:ob(q,1.6)*K;[value[0]*e,value[1]*e]');
        var ML = lb1_textAt(ctx, mt, { font: font, size: size, color: sc.fg, x: W / 2, y: H / 2, track: 0.04, lead: 1.12, name: c.text });
        jzAnimate(ctx, ML, { mi: 0 });
        return lb1_bb(W / 2, H / 2, pw, ph);
    }
});

/* ================================================================== 9 stickerBomb \u2014 \u30B9\u30C6\u30C3\u30AB\u30FC */
function lb1_stickerShape(g, shape, w, h, grow, dx, dy) {
    var W2 = w + grow * 2, H2 = h + grow * 2;
    if (shape === 'circle') { var D = Math.max(W2, H2); return jzAddEllipse(g, D, D, dx || 0, dy || 0); }
    if (shape === 'burst') { var R = Math.max(W2, H2) / 2 * 1.08, st = jzAddStar(g, 16, R, R * 0.84); if (dx || dy) st.property('ADBE Vector Star Position').setValue([dx, dy]); return st; }
    return lb1_rrect(g, W2, H2, shape === 'pill' ? H2 / 2 : Math.min(W2, H2) * 0.2, dx || 0, dy || 0);
}
// a slapped-on sticker: border + face (+ soft shadow on light backgrounds); returns the shape layer (text children ride on it)
function lb1_sticker(ctx, name, x, y, rot, sh, w, h, fill, t0, bw, dark, border) {
    var S = lb1_ng(lb1_shape(ctx, name, x, y)), TH = jzTH(ctx);
    var gF = jzGrp(S, 'face'); lb1_stickerShape(gF, sh, w, h, 0); jzAddFill(gF, fill);
    var gB = jzGrp(S, 'border'); lb1_stickerShape(gB, sh, w, h, bw); jzAddFill(gB, border);
    if (!dark) { var gS = jzGrp(S, 'shadow'); lb1_stickerShape(gS, sh, w, h, bw, bw * 0.5, bw * 0.7); jzAddFill(gS, ctx.sc.fg, 22); }
    jzXf(S, 'ADBE Rotate Z').setValue(rot);
    var q = TH + 'var q=cl((time-' + jzN(t0) + ')/0.16);';
    jzSetExpr(jzXf(S, 'ADBE Rotate Z'), q + 'value+(1-oc(q))*14');
    jzSetExpr(jzXf(S, 'ADBE Scale'), q + 'var s=q<=0?0:(1+0.3*Math.pow(1-q,2)-0.06*Math.sin(Math.PI*q)*(q<1?1:0))*(1+0.08*oc(PO));[value[0]*s,value[1]*s]');
    jzSetExpr(jzXf(S, 'ADBE Opacity'), q + 'q<=0?0:value*Math.min(1,q*3)*K');
    return S;
}
jzReg('layout', 'stickerBomb', {
    plan: function (rng, cut, st) {
        var n = cut.n;
        return {
            font: rng.pick(jzFontsOf(st, ['display'])), fs: rng.pick(jzFontsOf(st, ['body', 'display'])),
            main: n <= 3 ? rng.pick(['circle', 'burst', 'rrect']) : n <= 5 ? rng.pick(['rrect', 'burst', 'pill']) : rng.pick(['rrect', 'pill']),
            cnt: rng.int(4, 6), rot: rng.range(-5, 5), spin: rng.range(0, 6)
        };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, s = c.seed, M = jzU(ctx), k = lb1_k(ctx), i;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), fsF = jzP(ctx, 'fs', jzBodyF(ctx)), mainSh = jzP(ctx, 'main', 'rrect');
        var cnt = jzP(ctx, 'cnt', 5), rot0 = jzP(ctx, 'rot', 0), spin = jzP(ctx, 'spin', 0);
        var port = W < H, mt = lb1_lines(c.text, W, H, 8, 5), lo = { track: 0.03, lead: 1.1 };
        var size = Math.min(lb1_fitSize(mt, W * (port ? 0.64 : 0.5), H * 0.26, lo), H * (mainSh === 'circle' || mainSh === 'burst' ? 0.15 : 0.19)), mm = lb1_measure(mt, size, lo);
        var pw = mm.w + size * 0.8, ph = mm.h + size * 0.6, dark = jzLum(sc.bg) < 0.45, border = dark ? sc.fg : sc.bg, bw = Math.max(4 * k, size * 0.09);
        var TH = jzTH(ctx);
        // secondary stickers: words / romaji / chunks / No.xx / \u2661 / !!
        var txt = lb1_clean(c.text), pool = [];
        function add(w) { w = jzTrim(String(w || '')); if (w && w !== txt && jzCount(w) <= 12 && jzIndexOf(pool, w) < 0) pool.push(w); }
        var ws = c.words || [];
        for (i = 0; i < ws.length; i++) add(ws[i]);
        var rom = lb1_roma(txt); if (rom) add(rom);
        ws = jzChunk(c.lineText || '');
        for (i = 0; i < ws.length; i++) add(ws[i]);
        add('No.' + jzPad((c.line || 0) + 1, 2)); add('\u2661'); add('!!');
        var fills = [], fc = [sc.accent, sc.ink, sc.accent2, sc.fg];
        for (i = 0; i < 4; i++) fills.push(lb1_plateCol(sc, [fc[i]]));
        var mainFill = lb1_plateCol(sc, [sc.ink, sc.accent]), hw = pw / 2, hh = ph / 2;
        for (var kk = 0; kk < cnt; kk++) {
            var word = pool[kk % pool.length], gc = jzCount(word);
            var sh = gc <= 2 ? (jzR(s, kk, 2) < 0.5 ? 'circle' : 'burst') : (jzR(s, kk, 2) < 0.5 ? 'pill' : 'rrect');
            var fsz = jzClamp(M * (0.042 + 0.018 * jzR(s, kk, 3)), 12 * k, 80 * k), m = lb1_measure(word, fsz, { track: 0.06 });
            var w = m.w + fsz, h = fsz * 1.7;
            if (sh === 'circle' || sh === 'burst') { w = h = Math.max(m.w, fsz) + fsz * 1.2; }
            var a = (kk / cnt) * Math.PI * 2 + (jzR(s, kk, 4) * 2 - 1) * 0.35 + spin;
            var x = jzClamp(W / 2 + Math.cos(a) * (hw + w * 0.32), W * 0.05 + w / 2, W * 0.95 - w / 2), y = jzClamp(H / 2 + Math.sin(a) * (hh + h * 0.4), H * 0.06 + h / 2, H * 0.94 - h / 2);
            var fill = fills[kk % 4] === mainFill ? fills[(kk + 1) % 4] : fills[kk % 4], t0 = 0.14 + kk * 0.07;
            var S = lb1_sticker(ctx, 'sticker ' + (kk + 1), x, y, (jzR(s, kk, 5) * 2 - 1) * 18, sh, w, h, fill, t0, bw, dark, border);
            var T = lb1_textAt(ctx, word, { font: fsF, size: fsz, color: lb1_onCol(sc, fill), x: x, y: y, track: 0.06, name: word });
            lb1_parent(T, S, 0, 0); lb1_ng(T);
            jzSetExpr(jzXf(T, 'ADBE Opacity'), TH + 'var q=cl((time-' + jzN(t0) + ')/0.16);q<=0?0:value*Math.min(1,q*3)*K');
        }
        // main sticker last (on top) with the lyric riding on it
        var mw = pw, mh = ph;
        if (mainSh === 'circle' || mainSh === 'burst') mw = mh = Math.max(pw, ph) * 1.02;
        var MS = lb1_sticker(ctx, 'main sticker', W / 2, H / 2, rot0, mainSh, mw, mh, mainFill, 0.02, bw, dark, border);
        var ML = lb1_textAt(ctx, mt, { font: font, size: size, color: lb1_onCol(sc, mainFill), x: W / 2, y: H / 2, track: 0.03, lead: 1.1, name: c.text });
        lb1_parent(ML, MS, 0, 0);
        jzAnimate(ctx, ML, { mi: 0, noHold: lb1_plateHold(ctx) });
        return lb1_bb(W / 2, H / 2, mw, mh);
    }
});

/* ================================================================== 10 neon \u2014 \u30CD\u30AA\u30F3 */
jzReg('layout', 'neon', {
    plan: function (rng, cut, st) {
        return {
            font: rng.pick(jzFontsOf(st, ['display', 'body'])), tube: rng.pick(['accent', 'accent', 'accent2', 'fg']),
            frame: rng.pick(['box', 'under', 'bracket', 'none']), flick: rng.chance(0.75), sub: rng.chance(0.5)
        };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, s = c.seed, k = lb1_k(ctx), i;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), tubeK = jzP(ctx, 'tube', 'accent'), frame = jzP(ctx, 'frame', 'box'), flick = !!jzP(ctx, 'flick', true), sub = !!jzP(ctx, 'sub', false);
        var mt = lb1_lines(c.text, W, H, 9, 5), lo = { track: 0.08, lead: 1.25 };
        var size = Math.min(lb1_fitSize(mt, W * 0.72, H * 0.34, lo), H * 0.19), lay = lb1_layout(mt, size, lo);
        var dark = jzLum(sc.bg) < 0.45;
        var cand = tubeK === 'accent2' ? [sc.accent2, sc.accent, sc.fg] : tubeK === 'fg' ? [sc.fg, sc.accent] : [sc.accent, sc.accent2, sc.fg], tube = null;
        for (i = 0; i < cand.length; i++) if (cand[i] && jzContrast(cand[i], sc.bg) >= 2.2) { tube = cand[i]; break; }
        tube = tube || sc.fg;
        var core = jzMixHex(tube, '#FFFFFF', dark ? 0.72 : 0.35);
        var tw = Math.max(1.5 * k, size * (dark ? 0.05 : 0.06)), cw = Math.max(1 * k, size * 0.018), glowR = Math.min(60 * k, size * (dark ? 0.24 : 0.14));
        var TH = jzTH(ctx), cx = W / 2, cy = H / 2;
        function glow(L, r, amt) { var g = jzEffect(L, 'ADBE Glo2', 'JZ Neon Glow'); jzEP(g, 2, 25); jzEP(g, 3, r); jzEP(g, 4, amt); return g; }
        // ignition (random on / off while it strikes) + rare flicker, on a 24 fps step clock \u2014 the same hash on tube and core
        function on(idExpr, t0Expr, v) {     // expression code setting kk<v> (needs LB1_HR)
            return 'var tq' + v + '=time-(' + t0Expr + '),st' + v + '=Math.floor(time*24),kk' + v + '=1,ID' + v + '=' + idExpr + ';' +
                'if(tq' + v + '<0)kk' + v + '=0;else if(tq' + v + '<0.32)kk' + v + '=hr(SD+ID' + v + '*7,st' + v + ')<0.25+tq' + v + '*2?1:0.12;' +
                (flick ? 'else if(hr(SD+ID' + v + '*13,Math.floor(st' + v + '/2)+0.5)<0.007)kk' + v + '=0.2;' : '');
        }
        var T0 = [];
        for (i = 0; i < lay.length; i++) T0.push(0.04 + jzR(s, i, 5) * 0.28);
        var gOn = LB1_HR + 'var T0A=' + lb1_arr(T0) + ';' + on('textIndex', 'T0A[textIndex-1]||0', '');
        // tube (stroked, glowing) + a thin bright core on top (duplicate: same motion)
        var L = lb1_textAt(ctx, mt, { font: font, size: size, color: tube, fill: false, stroke: tw, strokeColor: tube, x: cx, y: cy, track: 0.08, lead: 1.25, name: c.text });
        glow(L, glowR, dark ? 1.4 : 0.8);
        var MPn = [], gn = 0;
        for (i = 0; i < lay.length; i++) MPn.push(lay[i].sp ? -1 : gn++);
        lb1_cutStagger(ctx, L, MPn, 0.4);
        jzAnimate(ctx, L, { mi: 0, treat: false });
        var D = L.duplicate();
        D.name = 'neon core'; lb1_ng(D);                     // the core is drawn with ghost: false (the tube is the ghosted lyric)
        jzTextDoc(D, function (td) { td.applyStroke = true; td.strokeColor = jzHex(core); td.strokeWidth = cw; });
        try { var fxp = D.property('ADBE Effect Parade'); for (i = fxp.numProperties; i >= 1; i--) if (fxp.property(i).name === 'JZ Neon Glow') fxp.property(i).remove(); } catch (e0) { jzWarn('neon core glow: ' + e0.toString()); }
        lb1_opAnim(L, 'JZ Neon Ignite', TH + gOn, 'a=kk');
        lb1_opAnim(D, 'JZ Neon Ignite', TH + gOn, 'a=kk>0.5?1:(kk>0?0.3:0)');
        // frame + sub line
        var bx0 = cx - lay.W / 2, bx1 = cx + lay.W / 2, by0 = cy - lay.H / 2, by1 = cy + lay.H / 2;
        var px = size * 0.55, py = size * 0.42, x0 = bx0 - px, x1 = bx1 + px, y0 = by0 - py, y1 = by1 + py, lw = Math.max(1.5 * k, size * 0.035);
        var fk = TH + LB1_HR + on('99', '0.22', 'F') + 'var fk=kkF*K;';
        if (frame !== 'none') {
            var F = lb1_shape(ctx, 'neon frame', 0, 0), paths = [];
            if (frame === 'under') paths.push([[x0 + px * 0.5, y1], [x1 - px * 0.5, y1]]);
            else if (frame === 'bracket') { var cc = size * 0.5; paths.push([[x0, y0 + cc], [x0, y0], [x0 + cc, y0]], [[x1 - cc, y0], [x1, y0], [x1, y0 + cc]], [[x1, y1 - cc], [x1, y1], [x1 - cc, y1]], [[x0 + cc, y1], [x0, y1], [x0, y1 - cc]]); }
            for (var pass = 0; pass < 2; pass++) {
                var g = jzGrp(F, pass ? 'tube' : 'core');
                if (frame === 'box') lb1_rrect(g, x1 - x0, y1 - y0, size * 0.3, (x0 + x1) / 2, (y0 + y1) / 2);
                else for (i = 0; i < paths.length; i++) jzAddPath(g, paths[i], false);
                var st = jzAddStroke(g, pass ? tube : core, pass ? lw : lw * 0.35);
                try { st.property('ADBE Vector Stroke Line Cap').setValue(2); } catch (e1) {}
            }
            glow(F, glowR * 0.8, dark ? 1.3 : 0.7);
            jzSetExpr(jzXf(F, 'ADBE Opacity'), fk + 'value*fk');
            if (sub) {
                var stx = lb1_roma(c.text) || (c.lineText !== c.text ? c.lineText : null);
                if (stx) {
                    var fs = jzClamp(size * 0.2, 12 * k, 34 * k), c2 = sc.sub, cs = [sc.accent2, sc.fg, sc.sub];
                    for (i = 0; i < cs.length; i++) if (cs[i] && cs[i] !== tube && jzContrast(cs[i], sc.bg) >= 2) { c2 = cs[i]; break; }
                    var SL = lb1_textAt(ctx, stx, { font: jzMonoF(ctx), size: fs, color: c2, x: (x0 + x1) / 2, y: y1 + fs * 1.6, track: 0.3, name: 'neon sub' });
                    glow(SL, fs * 0.6, 1); lb1_ng(SL);
                    jzSetExpr(jzXf(SL, 'ADBE Opacity'), fk + on('98', '0.4', 'S') + 'value*fk*kkS');
                }
            }
        }
        return lb1_bb(cx, cy, lay.W, lay.H);
    }
});

/* ================================================================== 11 keycaps \u2014 \u30AD\u30FC\u30AD\u30E3\u30C3\u30D7 */
// rows of equal cells (the browser's cellRows): cells [{i, x, y, r, j}], cell size k
function lb1_cellRows(n, W, H, maxPerRow, asp, maxK, gapK, stagger) {
    var rows = Math.ceil(n / maxPerRow), per = Math.ceil(n / rows);
    var wk = per + (per - 1) * gapK + (stagger && rows > 1 ? 0.5 : 0), hk = rows * asp + (rows - 1) * gapK * 1.6;
    var k = Math.min(W * 0.86 / wk, H * 0.7 / hk, maxK), out = [];
    for (var i = 0; i < n; i++) {
        var r = Math.floor(i / per), j = i - r * per, cnt = Math.min(per, n - r * per);
        out.push({ i: i, r: r, j: j, x: W / 2 + (j - (cnt - 1) / 2) * k * (1 + gapK) + (stagger && rows > 1 ? (r % 2 ? 0.25 : -0.25) * k : 0), y: H / 2 + (r - (rows - 1) / 2) * k * (asp + gapK * 1.6) });
    }
    return { cells: out, k: k, rows: rows };
}
jzReg('layout', 'keycaps', {
    plan: function (rng, cut, st) {
        return {
            font: rng.pick(jzFontsOf(st, ['display', 'body'])), style: rng.pick(['light', 'light', 'dark']), stagger: rng.chance(0.6),
            accent: rng.int(0, 20), legend: rng.chance(0.7), plate: rng.chance(0.45)
        };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, s = c.seed, k = lb1_k(ctx), M = jzU(ctx), i;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), style = jzP(ctx, 'style', 'light'), stag = !!jzP(ctx, 'stagger', true);
        var accI = jzP(ctx, 'accent', 3), legend = !!jzP(ctx, 'legend', true), plate = !!jzP(ctx, 'plate', false);
        var chs = lb1_slots(c.text), n = chs.length;
        if (!n) return null;
        var port = W < H, cr = lb1_cellRows(n, W, H, port ? 4 : (n > 8 ? 5 : 10), 1.06, M * 0.26, 0.16, stag), cells = cr.cells, kz = cr.k;
        var lightC = jzLum(sc.fg) > jzLum(sc.bg) ? sc.fg : sc.bg, darkC = lightC === sc.fg ? sc.bg : sc.fg;
        var d = kz * 0.15, rr = kz * 0.16, ins = kz * 0.09, gap = jzClamp(c.dur * 0.38 / n, 0.05, 0.13), acc = n > 2 ? accI % n : -1;
        var TH = jzTH(ctx), TIS = [], SCH = [], RE0 = 0.14 + n * gap + 0.3;
        for (i = 0; i < n; i++) TIS.push(0.14 + i * gap);
        for (i = 0; i < Math.ceil(Math.max(0, c.dur + 1 - RE0) / 0.55) + 2; i++) SCH.push(jzHash(s, i, 3) % n);
        var PZ = 'function pz(t){return t<0?0:t<0.05?t/0.05:t<0.09?1:t<0.22?1-(t-0.09)/0.13:0;}var TIS=' + lb1_arr(TIS) + ',SCH=' + lb1_arr(SCH) + ',RE0=' + jzN(RE0) + ';' +
            'function prs(I){var p=pz(time-TIS[I]),re=time-RE0;if(re>0){var kb=Math.floor(re/0.55);if(SCH[kb]===I)p=Math.max(p,pz(re-kb*0.55));}return p;}';
        var S = lb1_ng(lb1_shape(ctx, 'keycaps', 0, 0)), x0 = 1e9, x1 = -1e9, y0 = 1e9, y1 = -1e9, keys = [];
        for (i = 0; i < cells.length; i++) {
            var q = cells[i]; x0 = Math.min(x0, q.x - kz / 2); x1 = Math.max(x1, q.x + kz / 2); y0 = Math.min(y0, q.y - kz / 2); y1 = Math.max(y1, q.y + kz / 2 + d);
            if (chs[i] === ' ') continue;
            var isA = i === acc, top, side, leg;
            if (isA) { top = lb1_plateCol(sc, [sc.accent, sc.ink]); side = jzMixHex(top, darkC, 0.4); leg = lb1_onCol(sc, top); }
            else if (style === 'light') { top = lightC; side = jzMixHex(lightC, darkC, 0.32); leg = darkC; }
            else { top = jzMixHex(darkC, lightC, 0.16); side = jzMixHex(darkC, lightC, 0.06); leg = lightC; }
            var g = jzGrp(S, 'key ' + (i + 1)), dyE = TH + PZ + 'var dp=prs(' + i + ')*' + jzN(d * 0.75) + ';';
            // (each sub-group is finished before the next one is added: adding a sibling invalidates held references in AE)
            var gt = lb1_sub(g, 'top'); lb1_rrect(gt, kz - ins * 2, kz - ins * 2.1, rr * 0.7, 0, -kz / 2 + ins * 0.35 + (kz - ins * 2.1) / 2);
            if (style === 'dark' && !isA) jzAddStroke(gt, sc.sub, 1 * k);
            jzAddFill(gt, top);
            lb1_gPos(gt, dyE + '[value[0],value[1]+dp]');
            var gr = lb1_sub(g, 'rim'); lb1_rrect(gr, kz - ins, kz - ins * 0.9, rr * 0.85, 0, -kz / 2 + (kz - ins * 0.9) / 2); jzAddFill(gr, jzMixHex(top, side, 0.35));
            lb1_gPos(gr, dyE + '[value[0],value[1]+dp]');
            var gs = lb1_sub(g, 'side'); lb1_rrect(gs, kz, kz + d * 0.65, rr, 0, -kz / 2 + d * 0.35 + (kz + d * 0.65) / 2); jzAddFill(gs, side);
            jzGX(g).property('ADBE Vector Position').setValue([q.x, q.y]);
            lb1_gSc(g, TH + 'var q=cl((time-' + jzN(i * 0.025) + ')/0.2);var e=q<=0?0:ob(q,1.6);[value[0]*e,value[1]*e]');
            lb1_gOp(g, TH + '100*K');
            keys.push({ i: i, q: q, leg: leg, isA: isA });
        }
        if (plate) {
            var p = kz * 0.22, gp = jzGrp(S, 'plate'), ph = y1 - y0 + p * 2;
            lb1_rrect(gp, x1 - x0 + p * 2, ph, rr * 1.4, 0, ph / 2);
            jzAddStroke(gp, sc.sub, 1 * k); jzAddFill(gp, jzMixHex(sc.bg, darkC === sc.bg ? lightC : darkC, 0.1));
            jzGX(gp).property('ADBE Vector Position').setValue([(x0 + x1) / 2, y0 - p]);
            lb1_gSc(gp, TH + '[value[0],value[1]*oe(time/0.35)*K]');
        }
        // legends (romaji, top-left of each key): one left-justified text, one legend per line
        if (legend) {
            var labs = [], LM = [], TX = [], TY = [], KI = [], LA = [];
            for (i = 0; i < keys.length; i++) {
                var rom = jzRomaji(chs[keys[i].i]);
                if (!rom) continue;
                rom = rom.toUpperCase();
                var cl = labs.length, rc = jzChars(rom);
                for (var z = 0; z < rc.length; z++) { LM.push(cl); LA.push(keys[i].isA); }
                labs.push(rom); TX.push(keys[i].q.x - kz / 2 + ins * 1.9); TY.push(keys[i].q.y - kz / 2 + ins * 1.7); KI.push(keys[i].i);
            }
            if (labs.length) {
                var ls = kz * 0.13, LD = ls * 1.2;
                var LG = jzText(ctx, labs.join('\r'), { font: jzMonoF(ctx), size: ls, color: keys.length ? (style === 'light' ? darkC : lightC) : sc.fg, x: 0, y: 0, align: 'left', leading: LD, name: 'key legends' });
                jzXf(LG, 'ADBE Anchor Point').setValue([0, 0]); jzXf(LG, 'ADBE Position').setValue([0, 0]); lb1_ng(LG);
                var lh = TH + PZ + 'var LM=' + lb1_arr(LM) + ',TX=' + lb1_arr(TX) + ',TY=' + lb1_arr(TY) + ',KI=' + lb1_arr(KI) + ',l=LM[textIndex-1]||0,ki=KI[l];';
                lb1_posAnim(LG, 'JZ Legend Place', Math.max(W, H) * 2, lh, 'dx=TX[l];dy=TY[l]+' + jzN(LB1_CY * ls) + '-l*' + jzN(LD) + '+prs(ki)*' + jzN(d * 0.75));
                lb1_opAnim(LG, 'JZ Legend Show', lh, 'a=time>TIS[ki]?0.7*K:0');
                if (acc >= 0) jzCharColors(LG, lb1_onCol(sc, lb1_plateCol(sc, [sc.accent, sc.ink])), LA, 'JZ Legend Accent');
            }
        }
        // the glyphs: typed one after another, pressed with their key
        for (i = 0; i < keys.length; i++) {
            var kq = keys[i], ch = chs[kq.i];
            var T = lb1_textAt(ctx, ch, { font: font, size: kz * 0.52, color: kq.leg, x: kq.q.x, y: kq.q.y - kz * 0.03, name: ch });
            var th = TH + PZ + 'var q=cl((time-' + jzN(kq.i * 0.025) + ')/0.2),e=q<=0?0:Math.min(1,ob(q,1.6));';
            lb1_scAnim(T, 'JZ Key Pop', th, 'sx=e;sy=e');
            lb1_posAnim(T, 'JZ Key Press', kz, th, 'dy=prs(' + kq.i + ')*' + jzN(d * 0.75) + '*e');
            jzAnimate(ctx, T, { mi: lb1_miAt(ctx, TIS[kq.i]), noHold: lb1_plateHold(ctx) });
        }
        return { x0: x0, x1: x1, y0: y0, y1: y1, cx: (x0 + x1) / 2, cy: (y0 + y1) / 2 };
    }
});

/* ================================================================== 12 bubbles \u2014 \u6CE1 */
function lb1_isSmallKana(ch) { return '\u3041\u3043\u3045\u3047\u3049\u3063\u3083\u3085\u3087\u308E\u30A1\u30A3\u30A5\u30A7\u30A9\u30C3\u30E3\u30E5\u30E7\u30EE\u30F5\u30F6'.indexOf(ch) >= 0; }
jzReg('layout', 'bubbles', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'body'])), style: rng.pick(['soap', 'soap', 'solid', 'mixed']), rise: rng.range(0.018, 0.035), wob: rng.range(0.6, 1.2), motes: rng.chance(0.75) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, s = c.seed, M = jzU(ctx), k = lb1_k(ctx), i, m;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), style = jzP(ctx, 'style', 'soap'), rise = jzP(ctx, 'rise', 0.025), wob = jzP(ctx, 'wob', 0.9), motes = !!jzP(ctx, 'motes', true);
        var chs = lb1_slots(c.text), n = chs.length;
        if (!n) return null;
        var port = W < H, cr = lb1_cellRows(n, W, H, port ? 4 : 7, 1.1, M * 0.3, 0.12, true), cells = cr.cells, kz = cr.k, TH = jzTH(ctx);
        // background motes rising (one shape layer)
        if (motes) {
            var MS = lb1_ng(lb1_shape(ctx, 'motes', 0, 0));
            for (m = 0; m < 14; m++) {
                var sp = (0.05 + 0.07 * jzR(s, m, 1)) * H, per = H * 1.2 / sp, r = (0.006 + 0.014 * jzR(s, m, 4)) * M, gm = jzGrp(MS, 'mote ' + (m + 1));
                jzAddEllipse(gm, r * 2, r * 2); jzAddStroke(gm, sc.sub, Math.max(1 * k, r * 0.12));
                lb1_gPos(gm, 'var per=' + jzN(per) + ',t=(time+' + jzN(jzR(s, m, 2) * per) + ')%per;[' + jzN((0.04 + 0.92 * jzR(s, m, 3)) * W) + '+Math.sin(time*1.7+' + m + ')*' + jzN(M * 0.012) + ',' + jzN(H * 1.08) + '-t*' + jzN(sp) + ']');
            }
            jzSetExpr(jzXf(MS, 'ADBE Opacity'), TH + '45*K*oe(time/0.5)');
        }
        var bb = null;
        for (i = 0; i < cells.length; i++) {
            var q = cells[i], ch = chs[i];
            if (ch === ' ') continue;
            var kind = jzIsKanji(ch) ? 1 : (lb1_isSmallKana(ch) || jzIsPunct(ch)) ? 0.7 : 0.86;
            var R = kz * 0.5 * kind * (0.94 + 0.12 * jzR(s, i, 5)), t0 = 0.04 + i * 0.05 + jzR(s, i, 6) * 0.08, ph = jzR(s, i, 7) * Math.PI * 2;
            var bx = q.x + (jzR(s, i, 8) * 2 - 1) * kz * 0.1, by = q.y + (jzR(s, i, 10) * 2 - 1) * kz * 0.14;
            var solid = style === 'solid' || (style === 'mixed' && jzR(s, i, 11) < 0.4), tc = sc.fg;
            var B = lb1_ng(lb1_shape(ctx, 'bubble ' + (i + 1), bx, by));   // (the glyph's parent)
            if (solid) {
                var f = i % 3 === 1 ? lb1_plateCol(sc, [sc.accent, sc.ink]) : lb1_plateCol(sc, [sc.ink, sc.accent]), gf = jzGrp(B, 'bubble');
                jzAddEllipse(gf, R * 2, R * 2); jzAddFill(gf, f); tc = lb1_onCol(sc, f);
            } else {
                var gd = jzGrp(B, 'glint dot'), dr = Math.max(1.5 * k, R * 0.05);
                jzAddEllipse(gd, dr * 2, dr * 2, R * 0.52, -R * 0.52); jzAddFill(gd, sc.fg, 90);
                var ga = jzGrp(B, 'glint arc'), pts = [];
                for (var z = 0; z <= 8; z++) { var an = (200 + 45 * z / 8) * Math.PI / 180; pts.push([Math.cos(an) * R * 0.78, Math.sin(an) * R * 0.78]); }
                var sa = lb1_line(ga, pts, sc.fg, Math.max(1.5 * k, R * 0.07), 90);
                try { sa.property('ADBE Vector Stroke Line Cap').setValue(2); } catch (e0) {}
                var go = jzGrp(B, 'film'); jzAddEllipse(go, R * 2, R * 2); jzAddStroke(go, sc.fg, Math.max(1.2 * k, R * 0.035), 85); jzAddFill(go, sc.fg, 7);
            }
            var bq = TH + 'var q0=cl((time-' + jzN(t0) + ')/0.3),q=q0<=0?0:ob(q0,2.2),pk=1+0.35*oc(PO);';
            jzSetExpr(jzXf(B, 'ADBE Position'), '[value[0]+Math.sin(time*' + jzN(1.6 * wob) + '+' + jzN(ph) + ')*' + jzN(R * 0.1) + ',value[1]-' + jzN(H * rise * (0.7 + 0.6 * jzR(s, i, 9))) + '*time+Math.cos(time*1.2+' + jzN(ph) + ')*' + jzN(R * 0.06) + ']');
            jzSetExpr(jzXf(B, 'ADBE Scale'), bq + '[value[0]*q*pk,value[1]*q*pk]');
            jzSetExpr(jzXf(B, 'ADBE Opacity'), TH + 'value*K');
            // the glyph floats inside (parented); keep its size at min(1, q) like the browser (no overshoot / pop growth)
            var T = lb1_textAt(ctx, ch, { font: font, size: R * 1.05, color: tc, x: bx, y: by, name: ch });
            lb1_parent(T, B, 0, 0);
            lb1_scAnim(T, 'JZ Bubble Size', bq, 'var qs=q*pk;sx=qs>0.001?Math.min(1,q)/qs:0;sy=sx');
            jzAnimate(ctx, T, { mi: lb1_miAt(ctx, t0), noHold: lb1_plateHold(ctx) });
            bb = jzUnion(bb, lb1_bb(bx, by, R * 2, R * 2));
        }
        return bb;
    }
});

/* ================================================================== 13 slotMachine \u2014 \u30B9\u30ED\u30C3\u30C8 */
jzReg('layout', 'slotMachine', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display'])), style: rng.pick(['cabinet', 'window', 'cabinet']), v: rng.range(13, 18), line: rng.chance(0.7) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, s = c.seed, M = jzU(ctx), k = lb1_k(ctx), i, j;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), cab = jzP(ctx, 'style', 'cabinet') === 'cabinet', v = jzP(ctx, 'v', 15), line = !!jzP(ctx, 'line', true);
        var chs = lb1_slots(c.text), n = chs.length;
        if (!n) return null;
        var port = W < H, cr = lb1_cellRows(n, W, H, port ? 5 : 10, 1.34, M * 0.24, 0.1), cells = cr.cells, kz = cr.k;
        var w = kz, h = kz * 1.34, step = kz * 0.92, gsz = kz * 0.7, pool = lb1_pool(c), TH = jzTH(ctx);
        var gap = jzClamp(c.dur * 0.3 / n, 0.08, 0.2), t1 = jzClamp(c.dur * 0.16, 0.2, 0.45);
        var panel = lb1_plateCol(sc, [sc.ink, sc.fg]);
        var winC = cab ? (lb1_onCol(sc, panel) === sc.bg ? sc.bg : jzMixHex(sc.bg, sc.fg, 0.06)) : sc.bg;
        var glyC = jzContrast(sc.fg, winC) > 2.5 ? sc.fg : lb1_onCol(sc, winC);
        var x0 = 1e9, x1 = -1e9, y0 = 1e9, y1 = -1e9;
        for (i = 0; i < cells.length; i++) { x0 = Math.min(x0, cells[i].x - w / 2); x1 = Math.max(x1, cells[i].x + w / 2); y0 = Math.min(y0, cells[i].y - h / 2); y1 = Math.max(y1, cells[i].y + h / 2); }
        var inA = 'oe(time/0.25)';
        // cabinet + window backgrounds
        var CB = lb1_ng(lb1_shape(ctx, 'slot cabinet', 0, 0));
        for (i = 0; i < cells.length; i++) {
            if (chs[i] === ' ') continue;
            var gw = jzGrp(CB, 'window ' + (i + 1)); jzAddRect(gw, w, h, 0, 0, h / 2); jzAddFill(gw, winC);
            jzGX(gw).property('ADBE Vector Position').setValue([cells[i].x, cells[i].y - h / 2]);
            lb1_gSc(gw, TH + '[value[0],value[1]*' + inA + ']');
        }
        if (cab) {
            var p = kz * 0.22, gp = jzGrp(CB, 'cabinet');
            lb1_rrect(gp, x1 - x0 + p * 2, y1 - y0 + p * 2, p * 1.2, (x0 + x1) / 2, (y0 + y1) / 2); jzAddFill(gp, panel);
            lb1_gOp(gp, TH + '100*' + inA);
        }
        jzSetExpr(jzXf(CB, 'ADBE Opacity'), TH + 'value*K');
        // reels: a strip of random glyphs ending in the lyric glyph, scrolled by one Position animator, masked to the window
        var mains = [];
        for (i = 0; i < cells.length; i++) {
            var q = cells[i], ch = chs[i];
            if (ch === ' ') continue;
            var ts = t1 + i * gap, N = Math.ceil(v * ts) + 4, reel = [];
            for (j = 0; j < N - 1; j++) reel.push(pool[jzHash(s, i, j) % pool.length]);
            reel.push(ch);
            var G = lb1_glyphs(ctx, reel, { font: font, size: gsz, color: glyC, name: 'reel ' + (i + 1) }); lb1_ng(G.L);
            var rh = TH + 'var TS=' + jzN(ts) + ',FIN=' + (N - 1) + ',tau=TS-time,P=tau>0?FIN-' + jzN(v) + '*tau:FIN+0.22*Math.sin(-tau*30)*Math.exp(tau/0.08);';
            lb1_placeAnim(ctx, G, 'JZ Reel Spin', rh, 'x=' + jzN(q.x) + ';y=' + jzN(q.y) + '+(i-P)*' + jzN(step), Math.max(W, H) * 2 + N * gsz * 2);
            lb1_opAnim(G.L, 'JZ Reel Blur Fade', rh, 'a=(textIndex-1===FIN&&tau<=0)?0:(tau>0.05?0.62:0.9)*K');
            var mb = jzEffect(G.L, 'ADBE Motion Blur', 'JZ Reel Blur'); jzEP(mb, 1, 0);
            jzEX(mb, 2, rh + 'tau>0.05?' + jzN(step * 0.32) + ':0');
            jzMaskRect(G.L, q.x - w / 2, q.y - h / 2, q.x + w / 2, q.y + h / 2);
            mains.push({ i: i, q: q, ts: ts });
        }
        // the lyric glyph takes over when the reel stops (small bounce), clipped to its window
        for (i = 0; i < mains.length; i++) {
            var mq = mains[i], T = lb1_textAt(ctx, chs[mq.i], { font: font, size: gsz, color: glyC, x: mq.q.x, y: mq.q.y, name: chs[mq.i] });
            lb1_posAnim(T, 'JZ Reel Stop', step, TH + 'var tau=' + jzN(mq.ts) + '-time;', 'if(tau<=0)dy=-0.22*Math.sin(-tau*30)*Math.exp(tau/0.08)*' + jzN(step));
            jzMaskRect(T, -w / 2, -h / 2 - LB1_CY * gsz, w / 2, h / 2 - LB1_CY * gsz);
            jzAnimate(ctx, T, { mi: lb1_miAt(ctx, mq.ts), noHold: lb1_plateHold(ctx) });
        }
        // cylinder shading (stepped fade at top / bottom), window borders, pay-lines
        var SH = lb1_ng(lb1_shape(ctx, 'slot glass', 0, 0)), steps = 4;
        for (i = 0; i < mains.length; i++) {
            var cq = mains[i].q, wy = cq.y - h / 2, gs = jzGrp(SH, 'shade ' + (mains[i].i + 1));
            for (j = 0; j < steps; j++) {
                var al = 0.95 * (1 - (j + 0.5) / steps) * 100, bh = h * 0.3 / steps;
                var s1 = lb1_sub(gs, 't' + j); jzAddRect(s1, w, bh, 0, cq.x, wy + (j + 0.5) * bh); jzAddFill(s1, winC, al);
                var s2 = lb1_sub(gs, 'b' + j); jzAddRect(s2, w, bh, 0, cq.x, wy + h - (j + 0.5) * bh); jzAddFill(s2, winC, al);
            }
            var gb = jzGrp(SH, 'border ' + (mains[i].i + 1));
            lb1_rrect(gb, w, h, kz * 0.06, cq.x, cq.y); jzAddStroke(gb, cab ? jzMixHex(panel, winC, 0.5) : sc.fg, Math.max(1.5 * k, kz * 0.02));
            lb1_gOp(gb, TH + '100*' + inA);
        }
        if (line) {
            var rowsY = [], tri = kz * 0.1, xa = x0 - kz * 0.12, xb = x1 + kz * 0.12, plw = Math.max(1.2 * k, kz * 0.012);
            for (i = 0; i < cells.length; i++) if (jzIndexOf(rowsY, cells[i].y) < 0) rowsY.push(cells[i].y);
            for (i = 0; i < rowsY.length; i++) {
                var yy = rowsY[i], gl = jzGrp(SH, 'payline ' + (i + 1));
                lb1_line(gl, [[xa, yy], [xb, yy]], sc.accent, plw, 70); jzAddTrimPaths(gl, TH + '100*' + inA);
                var gt = jzGrp(SH, 'markers ' + (i + 1));
                jzAddPath(gt, [[xa - tri * 1.6, yy - tri], [xa, yy], [xa - tri * 1.6, yy + tri]], true);
                jzAddPath(gt, [[xb + tri * 1.6, yy - tri], [xb, yy], [xb + tri * 1.6, yy + tri]], true);
                jzAddFill(gt, sc.accent);
                lb1_gOp(gt, TH + '100*' + inA);
            }
        }
        jzSetExpr(jzXf(SH, 'ADBE Opacity'), TH + 'value*K');
        return { x0: x0, x1: x1, y0: y0, y1: y1, cx: (x0 + x1) / 2, cy: (y0 + y1) / 2 };
    }
});

/* ================================================================== 14 flipBoard \u2014 \u30D1\u30BF\u30D1\u30BF */
jzReg('layout', 'flipBoard', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'body'])), header: rng.chance(0.65), flips: rng.int(3, 5), style: rng.pick(['ink', 'ink', 'fg']) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, s = c.seed, M = jzU(ctx), k = lb1_k(ctx), i, j;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), header = !!jzP(ctx, 'header', true), flips = jzP(ctx, 'flips', 4), style = jzP(ctx, 'style', 'ink');
        var all = jzChars(jzTrim(String(c.text))), chs = [];
        for (i = 0; i < all.length; i++) if (all[i] !== '\u3000') chs.push(all[i]);
        var n = chs.length;
        if (!n) return null;
        var port = W < H, cr = lb1_cellRows(n, W, H, port ? 5 : 12, 1.32, M * 0.24, 0.08), cells = cr.cells, kz = cr.k;
        var w = kz, h = kz * 1.32, gsz = kz * 0.78, panel = lb1_plateCol(sc, style === 'fg' ? [sc.fg, sc.ink] : [sc.ink, sc.fg]);
        var flap = jzMixHex(panel, sc.bg, 0.08), gc = lb1_onCol(sc, panel), pool = lb1_pool(c), fd = 0.065, t0 = 0.06, lw = Math.max(1.5 * k, kz * 0.018);
        var TH = jzTH(ctx), FC = [], ST = [], PTS = [], x0 = 1e9, x1 = -1e9, y0 = 1e9;
        for (i = 0; i < n; i++) {
            var F = flips + i % 3 + Math.floor(i * 0.7), seq = [' '];
            for (j = 1; j <= F; j++) seq.push(j >= F ? chs[i] : pool[jzHash(s, i, j) % pool.length]);
            FC.push(seq); ST.push(t0 + F * fd); PTS.push([cells[i].x, cells[i].y]);
            x0 = Math.min(x0, cells[i].x - w / 2); x1 = Math.max(x1, cells[i].x + w / 2); y0 = Math.min(y0, cells[i].y - h / 2);
        }
        // shared flip clock: u = (t - t0) / fd, m = flip index, f = flap phase; a cell stops flipping at its settle time
        var clk = 'var FC=' + (function () { var o = []; for (var z = 0; z < FC.length; z++) o.push(lb1_strArr(FC[z])); return '[' + o.join(',') + ']'; })() + ',ST=' + lb1_arr(ST) + ';' +
            'var u=(time-' + t0 + ')/' + fd + ',m=Math.floor(u),f=u-m;function chA(i,mm){var a=FC[i];return mm<=0?" ":(a[Math.min(mm,a.length-1)]||" ");}';
        var qe = TH + clk + 'var i=textIndex-1,q=oc(cl((time-i*0.02)/0.18))*K,fl=time<ST[i]&&u>0;';
        function src(pick) { return 'var s="";for(var i=0;i<' + n + ';i++){if(i)s+="\\r";var fl=time<ST[i]&&u>0;s+=fl?(' + pick + '):" ";}s'; }
        // panels
        var PN = lb1_ng(lb1_shape(ctx, 'flip panels', 0, 0));
        for (i = 0; i < n; i++) {
            var gpn = jzGrp(PN, 'panel ' + (i + 1)); lb1_rrect(gpn, w, h, kz * 0.07, cells[i].x, cells[i].y); jzAddFill(gpn, panel);
            lb1_gOp(gpn, TH + '100*oc(cl((time-' + jzN(i * 0.02) + ')/0.18))*K');
        }
        // static halves: next glyph on top, current glyph below (clipped to the half-cells)
        function halfLayer(name, pick, top) {
            var G = lb1_glyphs(ctx, chs, { font: font, size: gsz, color: gc, name: name }); lb1_ng(G.L);
            try { G.L.property('ADBE Text Properties').property('ADBE Text Document').expression = clk + src(pick); } catch (e0) { jzWarn('flip source: ' + e0.toString()); }
            lb1_place(G, PTS);
            for (var z = 0; z < n; z++) jzMaskRect(G.L, cells[z].x - w / 2, top ? cells[z].y - h / 2 : cells[z].y, cells[z].x + w / 2, top ? cells[z].y : cells[z].y + h / 2);
            return G;
        }
        var HT = halfLayer('flip next (top)', 'chA(i,m+1)', true), HB = halfLayer('flip current (bottom)', 'chA(i,m)', false);
        lb1_opAnim(HT.L, 'JZ Flip Show', qe, 'a=fl?q:0');
        lb1_opAnim(HB.L, 'JZ Flip Show', qe, 'a=fl?q:0');
        // falling flap: plate + half glyph squashed toward the hinge, then the shade on top
        var FP = lb1_ng(lb1_shape(ctx, 'flip flaps', 0, 0)), FS = null;
        var fq = 'var sy=Math.abs(Math.cos(f*Math.PI)),HH=' + jzN(h / 2) + ';';
        for (i = 0; i < n; i++) {
            var gf = jzGrp(FP, 'flap ' + (i + 1)), rf = jzAddRect(gf, w, h / 2, 0, cells[i].x, cells[i].y - h / 4);
            var fh = TH + clk + fq + 'var fl=time<' + jzN(ST[i]) + '&&u>0;';
            jzSetExpr(rf.property('ADBE Vector Rect Size'), fh + '[' + jzN(w) + ',Math.max(0.01,HH*sy)]');
            jzSetExpr(rf.property('ADBE Vector Rect Position'), fh + '[' + jzN(cells[i].x) + ',' + jzN(cells[i].y) + '+(f<0.5?-1:1)*HH*sy/2]');
            jzAddFill(gf, flap);                                   // (after the rect is set up: adding invalidates `rf` in AE)
            lb1_gOp(gf, fh + 'fl?100*oc(cl((time-' + jzN(i * 0.02) + ')/0.18))*K:0');
        }
        var FT = halfLayer('flip flap (top)', 'chA(i,m)', true), FB = halfLayer('flip flap (bottom)', 'chA(i,m+1)', false);
        lb1_opAnim(FT.L, 'JZ Flap Show', qe, 'a=fl&&f<0.5?q:0');
        lb1_opAnim(FB.L, 'JZ Flap Show', qe, 'a=fl&&f>=0.5?q:0');
        lb1_scAnim(FT.L, 'JZ Flap Fold', TH + clk + fq, 'sy=sy');
        lb1_scAnim(FB.L, 'JZ Flap Fold', TH + clk + fq, 'sy=sy');
        FS = lb1_ng(lb1_shape(ctx, 'flip shade', 0, 0));
        for (i = 0; i < n; i++) {
            var gsd = jzGrp(FS, 'shade ' + (i + 1)), rs = jzAddRect(gsd, w, h / 2, 0, cells[i].x, cells[i].y - h / 4);
            var sh = TH + clk + fq + 'var fl=time<' + jzN(ST[i]) + '&&u>0;';
            jzSetExpr(rs.property('ADBE Vector Rect Size'), sh + '[' + jzN(w) + ',Math.max(0.01,HH*sy)]');
            jzSetExpr(rs.property('ADBE Vector Rect Position'), sh + '[' + jzN(cells[i].x) + ',' + jzN(cells[i].y) + '+(f<0.5?-1:1)*HH*sy/2]');
            jzAddFill(gsd, sc.bg);                                 // (after the rect is set up)
            lb1_gOp(gsd, sh + 'fl?100*oc(cl((time-' + jzN(i * 0.02) + ')/0.18))*K*(f<0.5?f:1-f)*0.5:0');
        }
        // settled glyphs = the lyric (one layer per cell, entering when the cell settles)
        var bb = null;
        for (i = 0; i < n; i++) {
            if (chs[i] === ' ') continue;
            var T = lb1_textAt(ctx, chs[i], { font: font, size: gsz, color: gc, x: cells[i].x, y: cells[i].y, name: chs[i] });
            lb1_opAnim(T, 'JZ Panel Fade', TH, 'a=oc(cl((time-' + jzN(i * 0.02) + ')/0.18))*K');
            jzAnimate(ctx, T, { mi: lb1_miAt(ctx, ST[i]), noHold: lb1_plateHold(ctx) });
            bb = jzUnion(bb, lb1_bb(cells[i].x, cells[i].y, w, h));
        }
        // split line + hinges on top
        var SP = lb1_ng(lb1_shape(ctx, 'flip split', 0, 0)), hc = jzMixHex(panel, sc.bg, 0.5);
        for (i = 0; i < n; i++) {
            var gsp = jzGrp(SP, 'split ' + (i + 1));
            var g1 = lb1_sub(gsp, 'hinges'); jzAddRect(g1, lw * 1.2, h * 0.14, 0, cells[i].x - w / 2, cells[i].y); jzAddRect(g1, lw * 1.2, h * 0.14, 0, cells[i].x + w / 2, cells[i].y); jzAddFill(g1, hc);
            var g2 = lb1_sub(gsp, 'line'); jzAddRect(g2, w, lw, 0, cells[i].x, cells[i].y); jzAddFill(g2, sc.bg);
            lb1_gOp(gsp, TH + '100*oc(cl((time-' + jzN(i * 0.02) + ')/0.18))*K');
        }
        if (header) {
            var fs = jzClamp(kz * 0.2, 12 * k, 30 * k), hy = y0 - fs * 1.4;
            var hl = jzText(ctx, 'LINE ' + jzLineNo(ctx), { font: jzMonoF(ctx), size: fs, color: sc.sub, x: x0, y: hy, align: 'left', track: 0.2, name: 'flip header' });
            var hr = jzText(ctx, jzFmtTime(c.start || 0), { font: jzMonoF(ctx), size: fs, color: sc.accent, x: x1, y: hy, align: 'right', track: 0.2, name: 'flip time' });
            jzSetExpr(jzXf(hl, 'ADBE Opacity'), TH + 'value*oe(time/0.2)*K');
            jzSetExpr(jzXf(hr, 'ADBE Opacity'), TH + 'value*oe(time/0.2)*K');
            lb1_ng(hl); lb1_ng(hr);
        }
        return bb || { x0: x0, x1: x1, y0: y0, y1: y0 + h, cx: (x0 + x1) / 2, cy: y0 + h / 2 };
    }
});

// ================================================================ pack layoutsB part 2 (AE port of src/11p_layoutsB.js)
// credits, zoomRepeat, splitHalves, columnsBig, circleWords, dotMatrix, depthStack, typeSpecimen, kanjiFocus,
// halfVertical, curtain, equalizer, tape. Every lyric stays ONE editable text layer (or one per browser item);
// plates / rules / rings / LED grids are shape layers driven by expressions on `time` (0 = cut start).

/* ---------------------------------------------------------------- shared helpers (lb2_) */
// expression header: jzTH (DUR IN OS OD SD PO K + easings) + smoothstep sm(a,b,x) + inOutCubic ioc(x)
function lb2_HD(ctx) {
    return jzTH(ctx) + 'function sm(a,b,x){var t=cl((x-a)/(b-a));return t*t*(3-2*t);}function ioc(x){x=cl(x);return x<0.5?4*x*x*x:1-Math.pow(-2*x+2,3)/2;}\n';
}
function lb2_isPunctLine(l) { var a = jzChars(l); for (var i = 0; i < a.length; i++) if (!(jzIsPunct(a[i]) || a[i] === ' ' || a[i] === '\u3000')) return false; return true; }
// J.splitLines that never leaves a line of punctuation only (lines joined with \r)
function lb2_splitL(t, per) {
    var ls = jzSplitLines(t, per).split('\r'), out = [];
    for (var i = 0; i < ls.length; i++) { if (out.length && lb2_isPunctLine(ls[i])) out[out.length - 1] += ls[i]; else out.push(ls[i]); }
    return out.join('\r');
}
// mainLines(text, W, H, perL, perP): portrait -> short lines
function lb2_lines(ctx, text, perL, perP) {
    var t = jzTrim(String(text || '')), n = jzCount(t), per = ctx.W < ctx.H ? perP : perL;
    if (n <= per) return t;
    return lb2_splitL(t, Math.ceil(n / Math.ceil(n / per)));
}
// reading in capitals, only for kana text
function lb2_roma(t) { var c = jzStrip(t); if (!/[\u3041-\u30FF]/.test(c)) return null; var r = jzRomaji(c); return r ? r.toUpperCase() : null; }
function lb2_onCol(sc, fill) {
    var cs = [sc.bg, sc.fg, sc.ink, sc.accent, sc.sub], best = null, bv = 0;
    for (var i = 0; i < cs.length; i++) { if (!cs[i] || cs[i] === fill) continue; var k = jzContrast(cs[i], fill); if (k > bv) { bv = k; best = cs[i]; } }
    return bv >= 2.4 ? best : (jzRelLum(fill) > 0.5 ? '#111111' : '#FFFFFF');
}
function lb2_plateCol(sc, pref) { for (var i = 0; i < pref.length; i++) if (pref[i] && jzContrast(pref[i], sc.bg) >= 1.6) return pref[i]; return sc.fg; }
// helper layers the browser draws with ghost off (main pass only) are kept out of the cut's tinted ghost copies
// (so faint items keep the browser's opacity: no ghost copy doubles their weight any more)
function lb2_ng(L) { if (L) jzNoGhost(L); return L; }
function lb2_miAt(ctx, t) { return Math.max(0, t) / Math.max(0.005, ctx.cut.stagger || 0.04); }
function lb2_plateHold(ctx) { return jzIndexOf(['still', 'jitter', 'breathe', 'glitchtick'], ctx.cut.hold) < 0; }
// text layer fitted like J.fitSize: o = { font, color, size, track, lead, maxW, maxH, maxSize, x, y, align, fill, stroke, strokeK (x size), strokeColor, rot, opacity }
function lb2_txt(ctx, str, o) {
    var lead = o.lead || 0, sz = o.size || 100, px = o.x == null ? 0 : o.x, py = o.y == null ? 0 : o.y;
    var L = jzText(ctx, str, { font: o.font, size: sz, color: o.color, x: px, y: py, align: o.align, track: o.track, leading: lead ? sz * lead : null, fill: o.fill,
        stroke: o.strokeK ? sz * o.strokeK : o.stroke, strokeColor: o.strokeColor, strokeOver: o.strokeOver, rot: o.rot, opacity: o.opacity, name: o.name });
    if (o.maxW || o.maxH) {
        var r = jzRect(L), k = Math.min((o.maxW || 1e6) / Math.max(1, r.width), (o.maxH || 1e6) / Math.max(1, r.height));
        var s = sz * k; if (o.maxSize) s = Math.min(s, o.maxSize); s = Math.max(1, s);
        jzTextDoc(L, function (td) { td.fontSize = s; if (lead) { td.autoLeading = false; td.leading = s * lead; } if (o.strokeK) td.strokeWidth = s * o.strokeK; });
        jzAnchor(L, o.align); jzXf(L, 'ADBE Position').setValue([px, py]);
    }
    return L;
}
// measure a string (width, height in px) without leaving a layer behind
function lb2_measure(ctx, str, o) { var P = lb2_txt(ctx, str, o), s = jzSize(P), f = jzFontSize(P); P.remove(); return { w: s[0], h: s[1], size: f }; }
// place a layer (anchored at its rect centre) so its top edge sits at `top`
function lb2_top(L, x, top) { jzXf(L, 'ADBE Position').setValue([x, top + jzSize(L)[1] / 2]); }
// glyph-by-glyph reveal (browser charFn reveal(t0, cnt) over 0.7 s); off = index of this layer's first glyph in the block
function lb2_reveal(ctx, L, t0, cnt, off) {
    jzAnimator(L, 'JZ Reveal', [['ADBE Text Opacity', 0]], JZ_FNS + 'var k=Math.floor(cl((time-' + jzN(t0) + ')/0.7)*' + jzN(cnt + 0.99) + ');(textIndex-1+' + (off || 0) + ')>=k?100:0');
}
// fade a text layer's fill / stroke colour from `fromHex` (usually the background) to `toHex` by `a` (defined in head):
// the browser's translucent items become opaque colour fades, so the cut's tinted ghost copies do not show through them
function lb2_colAnim(L, kind, fromHex, toHex, head) {
    var a = jzHex(fromHex), b = jzHex(toHex), mn = kind === 'stroke' ? 'ADBE Text Stroke Color' : 'ADBE Text Fill Color';
    var an = jzAnimator(L, 'JZ Fade Colour', [[mn, b]], '100');
    jzSetExpr(an.property('ADBE Text Animator Properties').property(mn), head + '[' + jzN(a[0]) + '+' + jzN(b[0] - a[0]) + '*a,' + jzN(a[1]) + '+' + jzN(b[1] - a[1]) + '*a,' + jzN(a[2]) + '+' + jzN(b[2] - a[2]) + '*a,1]');
    return an;
}
// latin-only strings: the browser turns latin glyphs 90 deg in vertical text, so a vertical latin arm / column becomes
// one horizontal text layer rotated 90 deg (reads top to bottom, spaces kept)
function lb2_isLatinStr(t) { return /[A-Za-z0-9]/.test(t) && /^[\x00-\x7F\s]*$/.test(t); }
// polygon / line shape layer at 0,0 (comp space)
function lb2_poly(ctx, name, pts, fill, stroke, w, closed) {
    var S = jzShapeLayer(ctx, name, 0, 0), g = jzGrp(S, name);
    jzAddPath(g, pts, closed);
    if (fill) jzAddFill(g, fill);
    if (stroke) jzAddStroke(g, stroke, w || 2);
    return S;
}

/* ================================================================ 15 credits \u2014 \u30A8\u30F3\u30C9\u30ED\u30FC\u30EB */
// one text layer per column of the roll (roles / values), scrolled by a Position expression; every row fades at the
// frame edges (and around the lyric) through a per-glyph Opacity animator that knows the row of each glyph
function lb2_credCol(ctx, rows, o, R) {
    var lines = [], li = [], j, k, any = false;
    for (j = 0; j < rows.length; j++) {
        lines.push(rows[j]);
        if (rows[j]) any = true;
        var cs = jzChars(rows[j]); for (k = 0; k < cs.length; k++) li.push(j);
    }
    if (!any) return null;
    var L = lb2_ng(jzText(ctx, lines.join('\r'), { font: o.font, size: o.size, color: o.color, x: o.x, y: R.y0 + (o.dy || 0), align: o.align, track: o.track, leading: R.rowH }));
    jzXf(L, 'ADBE Anchor Point').setValue([0, -o.size * 0.35]);
    jzXf(L, 'ADBE Position').setValue([o.x, R.y0 + (o.dy || 0)]);
    var S = 'var S=((time+' + jzN(R.off) + ')*' + jzN(R.speed) + ')%' + jzN(R.block) + ';';
    jzSetExpr(jzXf(L, 'ADBE Position'), S + '[value[0],value[1]-S]');
    jzSetExpr(jzXf(L, 'ADBE Opacity'), lb2_HD(ctx) + 'value*oe(time/0.6)*K');
    jzXf(L, 'ADBE Opacity').setValue(o.alpha * 100);
    var band = R.side ? '1' : 'sm(' + jzN(R.bandH) + ',' + jzN(R.bandH + R.fs * 2) + ',Math.abs(yy-' + jzN(ctx.H / 2) + '))';
    jzAnimator(L, 'JZ Roll Fade', [['ADBE Text Opacity', 0]], lb2_HD(ctx) + S + 'var li=' + jzArrExpr(li) + ';var yy=' + jzN(R.y0) + '+li[textIndex-1]*' + jzN(R.rowH) + '-S;' +
        'var a=sm(' + jzN(ctx.H * 0.02) + ',' + jzN(ctx.H * 0.14) + ',yy)*sm(' + jzN(ctx.H * 0.98) + ',' + jzN(ctx.H * 0.86) + ',yy)*' + band + ';(1-a)*100');
    return L;
}
jzReg('layout', 'credits', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['serif', 'display'])), fc: rng.pick(jzFontsOf(st, ['serif', 'body'])), variant: rng.pick(['center', 'center', 'side', 'single']), speed: rng.range(0.035, 0.06), off: rng.range(0, 10) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, M = jzU(ctx), u = ctx.u, i, r;
        var variant = jzP(ctx, 'variant', 'center'); if (W < H && variant === 'side') variant = 'center';
        var side = variant === 'side';
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'serif')), fc = jzP(ctx, 'fc', jzFontKeyOf(ctx.st, 'serif')), mono = jzMonoF(ctx);
        var mt = lb2_lines(ctx, c.text, side ? 6 : 10, 5);
        // the credit rows: role / value
        var rom = lb2_roma(c.text), vals = [], seen = {};
        function add(rr, v) { v = jzTrim(String(v || '')).replace(/\s+/g, ' '); if (v && !seen['$' + v]) { seen['$' + v] = 1; vals.push([rr, v]); } }
        var line = c.lineText || c.text;
        add('\u8A5E', line);
        var ch = jzChunk(line); for (i = 0; i < ch.length; i++) add(i === 0 ? '\u8A9E' : '', ch[i]);
        if (rom) add('READING', rom);
        if (c.note) add('NOTE', c.note);
        add('LINE', jzLineNo(ctx));
        add('TIME', jzFmtTime(c.start || 0));
        var fs = jzClamp(M * 0.026, 14 * u, 36 * u), rowH = fs * 2.3, nb = vals.length + 2, block = nb * rowH;
        var Kb = Math.ceil(H * 1.1 / block), reps = Kb + 1;
        // main lyric (measured first: the roll keeps clear of it)
        var mx = side ? W * 0.3 : W / 2;
        var P0 = lb2_measure(ctx, mt, { font: font, size: 100, track: 0.06, lead: 1.2, x: mx, y: H / 2, maxW: side ? W * 0.44 : W * 0.72, maxH: H * 0.26, maxSize: H * 0.16 });
        var R = { y0: H * 1.05 - block * Kb, rowH: rowH, block: block, speed: jzP(ctx, 'speed', 0.045) * H, off: jzP(ctx, 'off', 0), side: side, fs: fs,
            bandH: P0.size * mt.split('\r').length * 1.25 / 2 + fs * 2.2 };
        function col(fn) { var o = []; for (r = 0; r < reps; r++) { for (var q = 0; q < vals.length; q++) o.push(fn(vals[q])); o.push(''); o.push(''); } return o; }
        var cx = side ? W * 0.74 : W / 2;
        if (variant === 'single') {
            lb2_credCol(ctx, col(function (v) { return v[0]; }), { font: mono, size: fs * 0.62, color: sc.sub, x: cx, dy: -fs * 0.95, align: 'center', track: 0.3, alpha: 0.8 }, R);
            lb2_credCol(ctx, col(function (v) { return v[1]; }), { font: fc, size: fs, color: sc.fg, x: cx, align: 'center', track: 0.12, alpha: 0.85 }, R);
        } else {
            var g = fs * 0.9;
            lb2_credCol(ctx, col(function (v) { return /[A-Z]/.test(v[0]) ? '' : v[0]; }), { font: fc, size: fs * 0.72, color: sc.sub, x: cx - g, align: 'right', track: 0.25, alpha: 0.85 }, R);
            lb2_credCol(ctx, col(function (v) { return /[A-Z]/.test(v[0]) ? v[0] : ''; }), { font: mono, size: fs * 0.72, color: sc.sub, x: cx - g, align: 'right', track: 0.25, alpha: 0.85 }, R);
            lb2_credCol(ctx, col(function (v) { return v[1]; }), { font: fc, size: fs, color: sc.fg, x: cx + g, align: 'left', track: 0.1, alpha: 0.85 }, R);
        }
        if (side) {
            var S = lb2_ng(lb2_poly(ctx, 'credits rule', [[W * 0.52, H * 0.2], [W * 0.52, H * 0.8]], null, sc.sub, Math.max(u, M * 0.0012)));
            jzAddTrimPaths(S.property('ADBE Root Vectors Group').property(1), JZ_FNS + '100*oe(time/0.8)');
            jzSetExpr(jzXf(S, 'ADBE Opacity'), lb2_HD(ctx) + '50*K');
        }
        var L = lb2_txt(ctx, mt, { font: font, size: 100, color: sc.fg, x: mx, y: H / 2, track: 0.06, lead: 1.2, maxW: side ? W * 0.44 : W * 0.72, maxH: H * 0.26, maxSize: H * 0.16 });
        jzAnimate(ctx, L, { mi: 0 });
        return jzBB(L);
    }
});

/* ================================================================ 16 zoomRepeat \u2014 \u9023\u7D9A\u62E1\u5927 */
// N copies of the lyric behind it, each cycling through the zoom levels L = (k + phase) mod N: scale q^L, fade in near
// L 0.3..0.95 and out towards Lmax (endless zoom tunnel); a thick background-coloured stroke keeps the lyric clear
jzReg('layout', 'zoomRepeat', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), dir: rng.pick([1, 1, -1]), style: rng.pick(['alt', 'alt', 'outline', 'fill']),
            twist: rng.chance(0.35) ? rng.range(3, 7) * rng.pick([1, -1]) : 0, q: rng.range(1.38, 1.6), speed: rng.range(0.35, 0.6) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = ctx.u;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), dir = jzP(ctx, 'dir', 1) < 0 ? -1 : 1, style = jzP(ctx, 'style', 'alt');
        var twist = jzP(ctx, 'twist', 0), q = jzClamp(jzP(ctx, 'q', 1.5), 1.1, 3), speed = jzP(ctx, 'speed', 0.5);
        var mt = lb2_lines(ctx, c.text, 9, 5);
        var o = { font: font, size: 100, color: sc.fg, x: W / 2, y: H / 2, track: 0.03, lead: 1.1, maxW: W * 0.64, maxH: H * 0.3, maxSize: H * 0.2 };
        var m = lb2_measure(ctx, mt, o), size = m.size;
        var Lmax = Math.log(Math.max(W / Math.max(1, m.w), H / Math.max(1, m.h)) * 2.6) / Math.log(q);
        var N = Math.min(9, Math.ceil(Lmax) + 1) + 1; if (N % 2) N++;
        var HD = lb2_HD(ctx) + 'var N=' + N + ',Q=' + jzN(q) + ',LM=' + jzN(Lmax) + ';var e=oe(time/0.5);var ph=time*' + jzN(speed * dir) + '+(1-e)*' + jzN(1.5 * dir) + ';';
        for (var k = N - 1; k >= 0; k--) {
            var outline = style === 'outline' || (style === 'alt' && k % 2 === 0);
            // a faint copy = the bright colour at low opacity (not a dark opaque mix); kept out of the ghosts (ghost: false in the browser)
            var cp = { font: font, size: size, x: W / 2, y: H / 2, track: 0.03, lead: 1.1, name: 'zoom copy ' + k, color: sc.sub };
            if (outline) { cp.fill = false; cp.stroke = Math.max(1.2 * u, size * 0.01); cp.strokeColor = sc.sub; }
            var C = lb2_ng(lb2_txt(ctx, mt, cp));
            var Lk = HD + 'var L=((' + k + '+ph)%N+N)%N;';
            jzSetExpr(jzXf(C, 'ADBE Scale'), Lk + 'var s=Math.pow(Q,L);[value[0]*s,value[1]*s]');
            jzSetExpr(jzXf(C, 'ADBE Opacity'), Lk + 'sm(0.3,0.95,L)*(1-sm(LM*0.35,LM,L))*e*K*' + jzN(outline ? 52.5 : 11.2));
            if (twist) jzSetExpr(jzXf(C, 'ADBE Rotate Z'), Lk + 'value+' + jzN(twist) + '*L');
        }
        // knock-out stroke in the background colour under the lyric, then the lyric
        var KO = lb2_txt(ctx, mt, { font: font, size: size, x: W / 2, y: H / 2, track: 0.03, lead: 1.1, fill: false, stroke: size * 0.2, strokeColor: sc.bg, name: 'knockout' });
        jzMotion(ctx, KO, { size: size, mi: 0 });
        lb2_ng(KO);                                           // (a helper stroke, not the lyric: ghost: false in the browser)
        var L = lb2_txt(ctx, mt, { font: font, size: size, color: sc.fg, x: W / 2, y: H / 2, track: 0.03, lead: 1.1 });
        jzAnimate(ctx, L, { mi: 0 });
        return jzBB(L);
    }
});

/* ================================================================ 17 splitHalves \u2014 \u4E0A\u4E0B\u5272\u308A */
// every line = two copies of the line clipped (layer mask) to its upper / lower half; the halves slide in from opposite
// sides (Anchor Point expression, so the cut's own motion stays untouched) and fly apart on the exit; an accent cut line
jzReg('layout', 'splitHalves', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), variant: rng.pick(['slide', 'slide', 'shear', 'duo']), dir: rng.pick([1, -1]), line: rng.pick(['full', 'short']), gap: rng.pick([0.07, 0.1, 0.13]) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = ctx.u;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), variant = jzP(ctx, 'variant', 'slide'), dir = jzP(ctx, 'dir', 1) < 0 ? -1 : 1;
        var lineMode = jzP(ctx, 'line', 'full'), gap = jzP(ctx, 'gap', 0.1);
        var mt = lb2_lines(ctx, c.text, 11, 5), lines = mt.split('\r');
        var size = lb2_measure(ctx, mt, { font: font, size: 100, track: 0.04, lead: 1.3, x: W / 2, y: H / 2, maxW: W * 0.84, maxH: H * 0.5, maxSize: H * 0.24 }).size;
        var lead = size * 1.3 + gap * size, D = W * 0.32, lw = Math.max(1.5 * u, size * 0.016), g = gap * size / 2, bb = null;
        var HD = lb2_HD(ctx);
        for (var li = 0; li < lines.length; li++) {
            var y = H / 2 + (li - (lines.length - 1) / 2) * lead, dirL = dir * (li % 2 ? -1 : 1), lw0 = 0;
            for (var h = 0; h < 2; h++) {
                var L = jzText(ctx, lines[li], { font: font, size: size, color: (variant === 'duo' && h) ? sc.accent : sc.fg, x: W / 2, y: y + (h ? g : -g), track: 0.04 });
                var r = jzRect(L), ay = r.top + r.height / 2, x0 = r.left - W * 4, x1 = r.left + r.width + W * 4;
                lw0 = r.width;
                jzMaskRect(L, x0, h ? ay : ay - size * 2, x1, h ? ay + size * 2 : ay);
                var sh = variant === 'shear' ? '+' + jzN(size * 0.14) + '*e*(1+0.25*Math.sin(time*1.7))' : '';
                jzSetExpr(jzXf(L, 'ADBE Anchor Point'), HD + 'var e=oe((time-0.04)/0.7),ex=ie(PO);var off=' + jzN(D) + '*(1-e)' + sh + '+' + jzN(W * 0.4) + '*ex;[value[0]-(' + jzN((h ? -1 : 1) * dirL) + ')*off,value[1]]');
                jzAnimate(ctx, L, { mi: li * 2 + h });
                bb = jzUnion(bb, jzBB(L));
            }
            // the cut line (+ end dots on 'short')
            var half = lineMode === 'full' ? W * 0.5 : lw0 / 2 + size * 0.7;
            var S = jzShapeLayer(ctx, 'cut line', W / 2, y), gl = jzGrp(S, 'line');
            jzAddRect(gl, half * 2, lw); jzAddFill(gl, sc.accent);
            var LE = HD + 'var le=oe((time-0.1)/0.6)*K;';
            jzGX(gl).property('ADBE Vector Scale').expression = LE + '[100*le,100]';
            jzSetExpr(jzXf(S, 'ADBE Opacity'), LE + 'le>0.001?100:0');
            if (lineMode === 'short') {           // end dots: ghost off in the browser (the line itself is ghosted)
                var DS = lb2_ng(jzShapeLayer(ctx, 'cut line dots', W / 2, y));
                for (var sd = -1; sd <= 1; sd += 2) {
                    var gd = jzGrp(DS, 'dot'); jzAddEllipse(gd, lw * 3.2, lw * 3.2); jzAddFill(gd, sc.accent);
                    jzGX(gd).property('ADBE Vector Position').expression = LE + '[' + jzN(sd * half) + '*le,0]';
                }
                jzSetExpr(jzXf(DS, 'ADBE Opacity'), LE + 'le>0.001?100:0');
            }
        }
        return bb;
    }
});

/* ================================================================ 18 columnsBig \u2014 \u5927\u5C0F\u7E26\u7D44 */
// one big vertical column (the lyric) + smaller annotation columns (the whole line, then romaji / note) typed in
// glyph by glyph beside it, a hairline rule and an accent bar / dot
// a small vertical column whose top sits at `top`: CJK = one glyph per line, latin = a line turned 90 deg
function lb2_vcol(ctx, str, o, x, top) {
    var C;
    if (lb2_isLatinStr(str)) { C = jzText(ctx, str, { font: o.font, size: o.size, color: o.color, x: x, y: 0, track: o.track || 0.06, opacity: o.opacity, rot: 90 }); jzXf(C, 'ADBE Position').setValue([x, top + jzSize(C)[0] / 2]); }
    else { C = jzText(ctx, jzVertical(str), { font: o.font, size: o.size, color: o.color, x: x, y: 0, leading: o.size * o.lead, opacity: o.opacity }); lb2_top(C, x, top); }
    return C;
}
jzReg('layout', 'columnsBig', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), fs: rng.pick(jzFontsOf(st, ['serif', 'body'])), side: rng.pick(['left', 'left', 'right']), rule: rng.chance(0.7), mark: rng.pick(['bar', 'dot', 'none']), off: rng.range(-0.05, 0.05) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, M = jzU(ctx), u = ctx.u, port = W < H, i;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), fsF = jzP(ctx, 'fs', jzFontKeyOf(ctx.st, 'serif'));
        var left = jzP(ctx, 'side', 'left') === 'left', mark = jzP(ctx, 'mark', 'bar');
        var txt = jzTrim(c.text).replace(/[\s\u3000]+/g, ' ');
        var hx = W * (port ? (left ? 0.62 : 0.38) : (left ? 0.58 : 0.4)) + jzP(ctx, 'off', 0) * W;
        var lat = lb2_isLatinStr(txt), L;
        if (lat) L = lb2_txt(ctx, txt, { font: font, size: 100, color: sc.fg, x: hx, y: H / 2, track: 0.02, rot: 90, maxW: H * 0.84, maxH: port ? W * 0.44 : W * 0.3, maxSize: H * 0.42 });
        else L = lb2_txt(ctx, jzVertical(txt), { font: font, size: 100, color: sc.fg, x: hx, y: H / 2, lead: 1.02, maxW: port ? W * 0.44 : W * 0.3, maxH: H * 0.84, maxSize: H * 0.42 });
        var size = jzFontSize(L), colH = jzSize(L)[lat ? 0 : 1], top = H / 2 - colH / 2;
        var bbM = lat ? { x0: hx - size * 0.4, x1: hx + size * 0.4, y0: top, y1: top + colH, cx: hx, cy: H / 2 } : jzBB(L);
        jzAnimate(ctx, L, { mi: 0 });
        var fs = jzClamp(M * 0.031, 14 * u, 44 * u);
        var perCol = Math.max(4, Math.floor(colH * 0.92 / (fs * 1.08)));
        var line = jzStrip(c.lineText || c.text), cols = lb2_splitL(line, perCol).split('\r');
        var rom = lb2_roma(txt), sub2 = rom ? rom : c.note ? String(c.note) : 'No.' + jzLineNo(ctx) + ' ' + jzFmtTime(c.start || 0);
        var sgn = left ? -1 : 1, x1 = hx + sgn * (size * 0.5 + fs * 1.9), nL = cols.length;
        var x2 = x1 + sgn * (nL * fs * 1.7 + fs * 0.6), xc = x1 + sgn * (nL - 1) * fs * 0.85;
        var cnt = jzCount(line), offI = 0;
        for (i = 0; i < nL; i++) {           // columns read right -> left
            var C = lb2_ng(lb2_vcol(ctx, cols[i], { font: fsF, size: fs, color: sc.fg, lead: 1.06, opacity: 0.9 }, xc + ((nL - 1) / 2 - i) * fs * 1.7, top));
            lb2_reveal(ctx, C, 0.18, cnt, offI); offI += jzCount(cols[i]);
            jzSetExpr(jzXf(C, 'ADBE Opacity'), lb2_HD(ctx) + 'value*K');
        }
        var s2 = fs * 0.72, T2 = lb2_vcol(ctx, sub2, { font: rom ? jzMonoF(ctx) : fsF, size: s2, color: sc.sub, lead: 1.18, track: 0.18, opacity: 0.9 }, x2, top);
        lb2_reveal(ctx, T2, 0.35, jzCount(sub2) + 2, 0); lb2_ng(T2);
        jzSetExpr(jzXf(T2, 'ADBE Opacity'), lb2_HD(ctx) + 'value*K');
        var lw = Math.max(u, M * 0.0014), rx = hx + sgn * (size * 0.5 + fs * 0.85);
        if (jzP(ctx, 'rule', true)) {
            var R = lb2_ng(lb2_poly(ctx, 'rule', [[rx, top], [rx, top + colH]], null, sc.sub, lw));
            jzAddTrimPaths(R.property('ADBE Root Vectors Group').property(1), JZ_FNS + '100*oc((time-0.1)/0.6)');
            jzSetExpr(jzXf(R, 'ADBE Opacity'), lb2_HD(ctx) + '70*K');
        }
        var Q = lb2_HD(ctx) + 'var x=cl((time-0.15)/0.3);var q=x<=0?0:ob(x,2)*K;';
        if (mark === 'bar') {
            var bh = Math.max(3 * u, size * 0.06), B = jzShapeLayer(ctx, 'mark bar', hx - size * 0.5, top - size * 0.28 + bh / 2), gb = jzGrp(B, 'bar');
            jzAddRect(gb, size, bh, 0, size / 2, 0); jzAddFill(gb, sc.accent);
            jzSetExpr(jzXf(B, 'ADBE Scale'), Q + '[value[0]*q,value[1]]');
        } else if (mark === 'dot') {
            var Dt = jzEllipseLayer(ctx, 'mark dot', rx, top - fs * 0.9, fs * 0.56, fs * 0.56, sc.accent);
            jzSetExpr(jzXf(Dt, 'ADBE Scale'), Q + '[value[0]*q,value[1]*q]');
        }
        return bbM;
    }
});

/* ================================================================ 19 circleWords \u2014 \u540C\u5FC3\u5186 */
// 2-3 rings of text (text on a circular mask path, tracking set so each ring closes exactly) turning in alternate
// directions around the lyric, thin guide circles and a tick dial that draws itself round
jzReg('layout', 'circleWords', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), fr: rng.pick(jzFontsOf(st, ['body', 'serif'])), rings: rng.pick([2, 3, 3]), speed: rng.range(7, 13), dir: rng.pick([1, -1]), ticks: rng.chance(0.6), guides: rng.chance(0.7) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, M = jzU(ctx), u = ctx.u, s = c.seed || 1, i, k;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), fr = jzP(ctx, 'fr', jzFontKeyOf(ctx.st, 'body'));
        var rings = jzClamp(Math.round(jzP(ctx, 'rings', 3)), 1, 3), speed = jzP(ctx, 'speed', 10), dir = jzP(ctx, 'dir', 1) < 0 ? -1 : 1;
        var cx = W / 2, cy = H / 2, gapR = M * 0.072, R0 = M * 0.46 - gapR * (rings - 1);
        var txt = jzTrim(c.text).replace(/[\s\u3000]+/g, ' '), n = jzCount(txt);
        var mt = n <= 4 ? txt : lb2_splitL(txt, Math.ceil(n / Math.ceil(n / 5)));
        var rom = lb2_roma(txt);
        var units = [jzTrim(String(c.lineText || c.text).replace(/\s+/g, ' ')) + '\u3000\u2726\u3000', (rom || (c.words && c.words.length ? c.words.join(' / ') : '') || txt) + '  \u2014  ', jzStrip(txt) + '\u30FB'];
        var lw = Math.max(u, M * 0.0014), HD = lb2_HD(ctx);
        var G = null;
        if (jzP(ctx, 'guides', true)) { G = lb2_ng(jzShapeLayer(ctx, 'ring guides', cx, cy)); jzXf(G, 'ADBE Opacity').setValue(35); }
        for (k = 0; k < rings; k++) {
            var R = R0 + k * gapR, f = jzClamp(gapR * 0.46 * (k === 1 ? 0.8 : 1), 10 * u, 52 * u), rf = k === 1 && rom ? jzMonoF(ctx) : fr;
            var uw = Math.max(1, lb2_measure(ctx, units[k % 3], { font: rf, size: f, track: 0.08, x: 0, y: 0 }).w);
            var reps = Math.max(1, Math.min(12, Math.round(Math.PI * 2 * R / uw))), str = '';
            for (i = 0; i < reps; i++) str += units[k % 3];
            var chs = jzChars(str); if (chs.length > 160) chs = chs.slice(0, 160);
            str = chs.join('');
            var T = lb2_ng(jzText(ctx, str, { font: rf, size: f, color: k === 0 ? sc.fg : sc.sub, x: 0, y: 0, align: 'left' }));
            var w0 = jzRect(T).width, extra = (Math.PI * 2 * (R - f * 0.36) - w0) / chs.length;
            jzTextDoc(T, function (td) { td.tracking = Math.round(extra / f * 1000); });
            jzTextOnPath(T, jzCircleShape(cx, cy, R - f * 0.36), null, ctx);
            var fl = [], anyStar = false; for (i = 0; i < chs.length; i++) { fl.push(chs[i] === '\u2726'); if (chs[i] === '\u2726') anyStar = true; }
            if (anyStar) jzCharColors(T, sc.accent, fl, 'JZ Star');
            var E = HD + 'var e=oe((time-' + jzN(k * 0.08) + ')/0.7);';
            jzSetExpr(jzXf(T, 'ADBE Rotate Z'), 'value+' + jzN(jzR(s, k, 3) * 360) + '+time*' + jzN(speed * (k % 2 ? -1 : 1) * dir));
            jzSetExpr(jzXf(T, 'ADBE Scale'), E + 'var z=0.9+0.1*e;[value[0]*z,value[1]*z]');
            jzSetExpr(jzXf(T, 'ADBE Opacity'), E + '90*e*K');
            if (G) {
                var rs = k === 0 ? [R + gapR * 0.5, R - gapR * 0.5] : [R + gapR * 0.5];
                for (i = 0; i < rs.length; i++) {
                    var gg = jzGrp(G, 'guide ' + k); jzAddEllipse(gg, rs[i] * 2, rs[i] * 2); jzAddStroke(gg, sc.sub, lw);
                    jzGX(gg).property('ADBE Vector Scale').expression = E + 'var z=100*(0.9+0.1*e);[z,z]';
                    jzGX(gg).property('ADBE Vector Group Opacity').expression = E + '100*e*K';
                }
            }
        }
        if (jzP(ctx, 'ticks', true)) {
            var Rt = R0 + (rings - 1) * gapR + gapR * 0.5, TK = lb2_ng(jzShapeLayer(ctx, 'tick dial', cx, cy));
            var spec = [[18, sc.accent, gapR * 0.3, 4], [6, sc.sub, gapR * 0.3, 12], [1, sc.sub, gapR * 0.14, 72]];
            var CN = HD + 'var e=oe(time/0.9)*K;var n=Math.min(72,Math.floor(e*72)+1);';
            for (i = 0; i < spec.length; i++) {
                var gt = jzGrp(TK, 'ticks ' + spec[i][3]);
                jzAddPath(gt, [[0, -Rt], [0, -Rt - spec[i][2]]], false); jzAddStroke(gt, spec[i][1], lw);
                var rp = jzVecs(gt).addProperty('ADBE Vector Filter - Repeater');
                rp.property('ADBE Vector Repeater Copies').expression = CN + 'Math.floor((n-1)/' + spec[i][0] + ')+1';
                var rt = rp.property('ADBE Vector Repeater Transform');
                rt.property('ADBE Vector Repeater Position').setValue([0, 0]);
                rt.property('ADBE Vector Repeater Rotation').setValue(360 / spec[i][3]);
            }
            jzSetExpr(jzXf(TK, 'ADBE Rotate Z'), 'value-time*' + jzN(speed * 0.5 * dir));
            jzSetExpr(jzXf(TK, 'ADBE Opacity'), HD + '60*K');
        }
        var L = lb2_txt(ctx, mt, { font: font, size: 100, color: sc.fg, x: cx, y: cy, track: 0.03, lead: 1.1, maxW: R0 * 1.45, maxH: R0 * 1.05, maxSize: R0 * 0.55 });
        jzAnimate(ctx, L, { mi: 0 });
        return jzBB(L);
    }
});

/* ================================================================ 20 dotMatrix \u2014 \u30C9\u30C3\u30C8\u8868\u793A */
// LED board: panel plate, an unlit dot grid (shape + two repeaters) switching on column by column, and the lyric
// (text layer, thick stroke) seen through an alpha track matte made of the same dot grid -> lit LEDs.
// Reveal: sweep (the matte's column count grows, a bright scan column runs ahead), scroll (text slides in by whole
// dots), random (Block Dissolve at dot size).
function lb2_dotGrid(ctx, name, x0, y0, p, cols, rows, d, round, col, colsExpr) {
    var S = jzShapeLayer(ctx, name, 0, 0), g = jzGrp(S, 'dots');
    if (round) jzAddEllipse(g, d, d, x0 + p / 2, y0 + p / 2); else jzAddRect(g, d, d, 0, x0 + p / 2, y0 + p / 2);
    jzAddFill(g, col);
    var r1 = jzVecs(g).addProperty('ADBE Vector Filter - Repeater');
    if (colsExpr) r1.property('ADBE Vector Repeater Copies').expression = colsExpr; else r1.property('ADBE Vector Repeater Copies').setValue(cols);
    r1.property('ADBE Vector Repeater Transform').property('ADBE Vector Repeater Position').setValue([p, 0]);
    var r2 = jzVecs(g).addProperty('ADBE Vector Filter - Repeater');
    r2.property('ADBE Vector Repeater Copies').setValue(rows);
    r2.property('ADBE Vector Repeater Transform').property('ADBE Vector Repeater Position').setValue([0, p]);
    return S;
}
jzReg('layout', 'dotMatrix', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(['dot', 'gothic_black', 'dot']), reveal: rng.pick(['sweep', 'sweep', 'scroll', 'random']), panel: rng.chance(0.7), col: rng.pick(['accent', 'accent', 'fg']), shape: rng.pick(['round', 'round', 'square']) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, M = jzU(ctx), u = ctx.u, i;
        var font = jzP(ctx, 'font', 'dot'), reveal = jzP(ctx, 'reveal', 'sweep'), panelOn = !!jzP(ctx, 'panel', true), round = jzP(ctx, 'shape', 'round') === 'round';
        var mt = lb2_lines(ctx, c.text, 8, 4), lines = mt.split('\r'), maxL = 1;
        for (i = 0; i < lines.length; i++) maxL = Math.max(maxL, jzCount(lines[i]) || 1);
        var D = jzClamp(Math.floor(W * 0.86 / (maxL * M * 0.011)), 10, 16), lead = 1.3;
        var m = lb2_measure(ctx, mt, { font: font, size: 100, lead: lead, x: 0, y: 0 });
        var cols = Math.ceil(m.w / 100 * D * 0.94) + 2, rows = Math.ceil(D * 0.94 * (lines.length * lead - (lead - 1))) + 2;
        var p = Math.min(W * 0.88 / cols, H * 0.62 / rows), gw = cols * p, gh = rows * p, gx = W / 2 - gw / 2, gy = H / 2 - gh / 2;
        var dark = jzRelLum(sc.bg) < 0.45;
        var panel = panelOn ? (dark ? jzMixHex(sc.bg, sc.fg, 0.05) : (jzRelLum(sc.fg) < jzRelLum(sc.ink) ? sc.fg : sc.ink)) : sc.bg;
        var cand = [jzP(ctx, 'col', 'accent') === 'accent' ? sc.accent : sc.fg, sc.accent, sc.fg, sc.accent2, sc.bg], litC = null;
        for (i = 0; i < cand.length; i++) if (cand[i] && jzContrast(cand[i], panel) >= 2.5) { litC = cand[i]; break; }
        if (!litC) litC = lb2_onCol(sc, panel);
        var dd = p * (round ? 0.76 : 0.8), HD = lb2_HD(ctx), t0 = 0.12, T = jzClamp(c.dur * 0.3, 0.25, 0.7);
        var U = HD + 'var C=' + cols + ',u=cl((time-' + jzN(t0) + ')/' + jzN(T) + ');';
        // panel (on a dark background: a faint fg plate, i.e. fg at 5 % opacity) + unlit dots (lit colour at 13 % opacity)
        if (panelOn) {
            var pd = p * 1.2, PL = jzRectLayer(ctx, 'LED panel', W / 2, H / 2, gw + pd * 2, gh + pd * 2, dark ? sc.fg : panel, { round: p * 1.2 });
            jzSetExpr(jzXf(PL, 'ADBE Opacity'), HD + jzN(dark ? 5 : 100) + '*oe(time/0.3)*K'); lb2_ng(PL);
            var PS = jzRectLayer(ctx, 'LED panel rim', W / 2, H / 2, gw + pd * 2, gh + pd * 2, null, { round: p * 1.2, stroke: litC, strokeW: Math.max(u, p * 0.12) });
            jzSetExpr(jzXf(PS, 'ADBE Opacity'), HD + '30*oe(time/0.3)*K'); lb2_ng(PS);
        }
        var OFF = lb2_dotGrid(ctx, 'LED unlit', gx, gy, p, cols, rows, dd, round, litC, HD + 'Math.ceil(cl(time/0.3)*' + cols + ')');
        jzSetExpr(jzXf(OFF, 'ADBE Opacity'), HD + '13*oe(time/0.3)*K'); lb2_ng(OFF);
        // lit dots: the lyric through a dot-grid matte
        var fsz = 0.94 * D * p;
        var L = jzText(ctx, mt, { font: font, size: fsz, color: litC, x: W / 2, y: H / 2, leading: fsz * lead, stroke: p * 0.4, strokeColor: litC });
        if (reveal === 'scroll') jzSetExpr(jzXf(L, 'ADBE Anchor Point'), U + '[value[0]-Math.round((1-oc(u))*C)*' + jzN(p) + ',value[1]]');
        if (reveal === 'random') {
            var bd = jzEffect(L, 'ADBE Block Dissolve', 'JZ LED Random');
            jzEP(bd, 2, p); jzEP(bd, 3, p); jzEP(bd, 4, 0); jzEP(bd, 5, 0);
            jzEX(bd, 1, U + '100*(1-Math.min(1,u*1.05))');
        }
        jzMotion(ctx, L, { size: fsz, mi: lb2_miAt(ctx, t0), noHold: true });
        var MT = lb2_dotGrid(ctx, 'LED matte', gx, gy, p, cols, rows, dd, round, '#FFFFFF', reveal === 'sweep' ? U + 'Math.max(0,Math.min(C,Math.floor(u*(C+4)-2)+1))' : null);
        L.trackMatteType = TrackMatteType.ALPHA;
        if (reveal === 'sweep') {        // bright scan column at the sweep front
            var SC = jzText(ctx, mt, { font: font, size: fsz, color: sc.fg, x: W / 2, y: H / 2, leading: fsz * lead, stroke: p * 0.4, strokeColor: sc.fg, name: 'LED scan' });
            jzSetExpr(jzXf(SC, 'ADBE Opacity'), U + 'u>0&&u<1?90*K:0');
            var SM = jzShapeLayer(ctx, 'LED scan matte', 0, 0), sg = jzGrp(SM, 'column');
            if (round) jzAddEllipse(sg, dd, dd, gx + p / 2, gy + p / 2); else jzAddRect(sg, dd, dd, 0, gx + p / 2, gy + p / 2);
            jzAddFill(sg, '#FFFFFF');
            var r2 = jzVecs(sg).addProperty('ADBE Vector Filter - Repeater');
            r2.property('ADBE Vector Repeater Copies').setValue(rows);
            r2.property('ADBE Vector Repeater Transform').property('ADBE Vector Repeater Position').setValue([0, p]);
            jzGX(sg).property('ADBE Vector Position').expression = U + '[Math.floor(u*(C+4)-2)*' + jzN(p) + ',0]';
            SC.trackMatteType = TrackMatteType.ALPHA; lb2_ng(SC); lb2_ng(SM);
        }
        return { x0: gx, y0: gy, x1: gx + gw, y1: gy + gh, cx: W / 2, cy: H / 2 };
    }
});

/* ================================================================ 21 depthStack \u2014 \u5965\u884C\u304D\u91CD\u306D */
// copies of the lyric shrinking towards a (swaying) vanishing point: each copy's Position / Scale follow
// s = 1 / (1 + k*0.26*depth); 'lines' adds four corner rays (rect groups whose size / angle follow the same maths)
jzReg('layout', 'depthStack', {
    plan: function (rng, cut, st) {
        var a = rng.pick([-150, -120, -60, -30, 30, 60, 120, 150, -90, 90]) + rng.range(-12, 12);
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), ang: a, copies: rng.int(5, 8), dist: rng.range(0.42, 0.62), style: rng.pick(['outline', 'outline', 'dim', 'lines']), sway: rng.chance(0.7) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, M = jzU(ctx), u = ctx.u, k;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), N = jzClamp(Math.round(jzP(ctx, 'copies', 6)), 1, 12), style = jzP(ctx, 'style', 'outline');
        var a = jzP(ctx, 'ang', 60) * Math.PI / 180, dx = Math.cos(a), dy = Math.sin(a), dist = jzP(ctx, 'dist', 0.5);
        var mt = lb2_lines(ctx, c.text, 9, 5);
        var cx = W / 2 - dx * M * 0.05, cy = H / 2 - dy * M * 0.05;
        var m = lb2_measure(ctx, mt, { font: font, size: 100, track: 0.03, lead: 1.1, x: cx, y: cy, maxW: W * 0.66, maxH: H * 0.3, maxSize: H * 0.2 }), size = m.size;
        var HD = lb2_HD(ctx) + 'var CX=' + jzN(cx) + ',CY=' + jzN(cy) + ',dp=oc(time/0.7)*K;var vx=' + jzN(cx + dx * M * dist) + ',vy=' + jzN(cy + dy * M * dist) + ';' +
            (jzP(ctx, 'sway', true) ? 'vx+=Math.sin(time*0.7)*' + jzN(M * 0.05) + ';vy+=Math.cos(time*0.55)*' + jzN(M * 0.035) + ';' : '');
        if (style === 'lines') {
            var R = lb2_ng(jzShapeLayer(ctx, 'depth rays', 0, 0)), cs = [[-1, -1], [1, -1], [1, 1], [-1, 1]];
            for (var q = 0; q < 4; q++) {
                var g = jzGrp(R, 'ray'), rr = jzAddRect(g, 10, Math.max(u, M * 0.0013));
                var E = HD + 'var sN=1/(1+' + N + '*0.26*dp);var x0=CX+' + jzN(cs[q][0] * m.w / 2) + ',y0=CY+' + jzN(cs[q][1] * m.h / 2) + ';var x1=CX+(vx-CX)*(1-sN)+' + jzN(cs[q][0] * m.w / 2) + '*sN,y1=CY+(vy-CY)*(1-sN)+' + jzN(cs[q][1] * m.h / 2) + '*sN;';
                rr.property('ADBE Vector Rect Size').expression = E + '[Math.sqrt((x1-x0)*(x1-x0)+(y1-y0)*(y1-y0)),value[1]]';
                jzAddFill(g, sc.sub);                             // (after the rect is configured: adding the fill invalidates rr)
                jzGX(g).property('ADBE Vector Position').expression = E + '[(x0+x1)/2,(y0+y1)/2]';
                jzGX(g).property('ADBE Vector Rotation').expression = E + 'Math.atan2(y1-y0,x1-x0)*180/Math.PI';
            }
            jzSetExpr(jzXf(R, 'ADBE Opacity'), HD + 'dp>0.01?50*K:0');
        }
        for (k = N; k >= 1; k--) {
            var f = k / N, o = { font: font, size: size, x: cx, y: cy, track: 0.03, lead: 1.1, name: 'depth ' + k };
            if (style === 'dim') o.color = jzMixHex(sc.bg, sc.sub, 0.55 - 0.4 * f);
            else { o.fill = false; o.stroke = Math.max(u, size * 0.014); o.strokeColor = k === 1 ? sc.accent : sc.sub; }
            var C = lb2_ng(lb2_txt(ctx, mt, o)), S = HD + 'var s1=1/(1+' + k + '*0.26*dp);';
            jzSetExpr(jzXf(C, 'ADBE Position'), S + '[value[0]+(vx-CX)*(1-s1),value[1]+(vy-CY)*(1-s1)]');
            jzSetExpr(jzXf(C, 'ADBE Scale'), S + '[value[0]*s1,value[1]*s1]');
            jzSetExpr(jzXf(C, 'ADBE Opacity'), S + 'dp<=0.001?0:' + (style === 'dim' ? '100*K' : jzN((0.85 - 0.6 * f) * 100) + '*K'));
        }
        var L = lb2_txt(ctx, mt, { font: font, size: size, color: sc.fg, x: cx, y: cy, track: 0.03, lead: 1.1 });
        jzAnimate(ctx, L, { mi: 0 });
        return jzBB(L);
    }
});

/* ================================================================ 22 typeSpecimen \u2014 \u66F8\u4F53\u898B\u672C */
// the lyric set in several typefaces on a specimen grid (2x2 / 3x3 with a big main cell / list rows): every cell
// has a rule that draws out, a mono caption (number, font name, weight) and the sample rising in
var LB2_SPEC_FONTS = ['gothic_black', 'mincho', 'round', 'dot', 'brush', 'pop', 'dela', 'tokumin', 'zenkaku', 'gothic_light', 'mincho_black', 'sansui', 'mincho_light'];
jzReg('layout', 'typeSpecimen', {
    plan: function (rng, cut, st) {
        var main = rng.pick(jzFontsOf(st, ['display', 'serif'])), pool = [], i;
        for (i = 0; i < LB2_SPEC_FONTS.length; i++) if (LB2_SPEC_FONTS[i] !== main && JZ_DATA.fonts[LB2_SPEC_FONTS[i]]) pool.push(LB2_SPEC_FONTS[i]);
        for (i = pool.length - 1; i > 0; i--) { var j = rng.int(0, i), t = pool[i]; pool[i] = pool[j]; pool[j] = t; }
        var port = cut.H > cut.W;
        return { main: main, fonts: pool.slice(0, 6), grid: port ? 'list' : rng.pick(['g2', 'g3', 'list']), num: rng.int(1, 30) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, M = jzU(ctx), u = ctx.u, i;
        var txt = jzTrim(c.text), grid = jzP(ctx, 'grid', W < H ? 'list' : 'g3'), num = jzP(ctx, 'num', 1);
        var main = jzP(ctx, 'main', jzFontKeyOf(ctx.st, 'display')), fonts = jzP(ctx, 'fonts', null);
        if (!fonts || !fonts.length) { fonts = []; for (i = 0; i < LB2_SPEC_FONTS.length; i++) if (LB2_SPEC_FONTS[i] !== main) fonts.push(LB2_SPEC_FONTS[i]); }
        var lw = Math.max(u, M * 0.0014), cap = jzClamp(M * 0.017, 11 * u, 24 * u), mono = jzMonoF(ctx);
        var cells = [], mx = W * 0.07, my = H * 0.1, gw = W - mx * 2, gh = H - my * 2, g, cw, ch;
        function F(k) { return fonts[k % fonts.length]; }
        if (grid === 'g2') {
            g = M * 0.02; cw = (gw - g) / 2; ch = (gh - g) / 2;
            for (i = 0; i < 4; i++) cells.push({ x: mx + (i % 2) * (cw + g), y: my + Math.floor(i / 2) * (ch + g), w: cw, h: ch, font: i === 0 ? main : F(i - 1), main: i === 0 });
        } else if (grid === 'g3') {
            g = M * 0.018; cw = (gw - g * 2) / 3; ch = (gh - g * 2) / 3;
            cells.push({ x: mx, y: my, w: cw * 2 + g, h: ch * 2 + g, font: main, main: true });
            var rest = [[2, 0], [2, 1], [0, 2], [1, 2], [2, 2]];
            for (i = 0; i < rest.length; i++) cells.push({ x: mx + rest[i][0] * (cw + g), y: my + rest[i][1] * (ch + g), w: cw, h: ch, font: F(i), main: false });
        } else {
            var rowsN = W < H ? 5 : 4, tot = 2.2 + (rowsN - 1), y = my;
            for (i = 0; i < rowsN; i++) { var hh = gh * (i === 0 ? 2.2 : 1) / tot; cells.push({ x: mx, y: y, w: gw, h: hh, font: i === 0 ? main : F(i - 1), main: i === 0 }); y += hh; }
        }
        var list = grid === 'list', HD = lb2_HD(ctx), bb = null;
        var RL = lb2_ng(jzShapeLayer(ctx, 'specimen rules', 0, 0));
        for (i = 0; i < cells.length; i++) {
            var cl = cells[i], d = 0.06 + i * 0.07, E = HD + 'var e=oc((time-' + jzN(d) + ')/0.4)*K;';
            var gr = jzGrp(RL, 'rule ' + (i + 1)); jzAddRect(gr, cl.w, cl.main ? lw * 3 : lw, 0, cl.w / 2, 0); jzAddFill(gr, cl.main ? sc.accent : sc.sub);
            jzGX(gr).property('ADBE Vector Position').setValue([cl.x, cl.y]);
            jzGX(gr).property('ADBE Vector Scale').expression = HD + '[100*oe((time-' + jzN(d) + ')/0.5),100]';
            jzGX(gr).property('ADBE Vector Group Opacity').expression = HD + 'time<' + jzN(d) + '?0:' + (cl.main ? 100 : 60) + '*K';
            var FI = JZ_DATA.fonts[cl.font] || {};
            var label = jzPad(num + i, 2) + '  ' + String(FI.label || cl.font).toUpperCase() + '  ' + (FI.weight || '');
            var LB = jzText(ctx, label, { font: mono, size: cap, color: cl.main ? sc.accent : sc.sub, x: cl.x, y: cl.y + cap * 1.1, align: 'left', track: 0.08 });
            jzSetExpr(jzXf(LB, 'ADBE Opacity'), E + '100*e'); lb2_ng(LB);
            var tw = list ? cl.w * (cl.main ? 1 : 0.8) : cl.w * 0.9, th = cl.h - cap * (list ? 1.8 : 2.8);
            var tx = list ? cl.x : cl.x + cl.w / 2, ty = cl.y + cap * (list ? 1.8 : 2.2) + th / 2;
            if (list && !cl.main) { tx = cl.x + cl.w * 0.2; ty = cl.y + cl.h / 2 + cap * 0.3; }
            var T = lb2_txt(ctx, txt, { font: cl.font, size: 100, color: cl.main ? sc.fg : sc.sub, x: tx, y: ty, align: list ? 'left' : 'center', track: 0.02,
                maxW: tw, maxH: th * (list ? 0.78 : 0.7), maxSize: cl.main ? H * 0.3 : H * 0.14 });
            if (cl.main) { jzAnimate(ctx, T, { mi: 0 }); bb = jzBB(T); }
            else {
                lb2_ng(T);
                jzSetExpr(jzXf(T, 'ADBE Opacity'), E + '90*e');
                jzSetExpr(jzXf(T, 'ADBE Position'), E + '[value[0],value[1]+(1-e)*' + jzN(cap * 1.5) + ']');
            }
        }
        return bb;
    }
});

/* ================================================================ 23 kanjiFocus \u2014 \u4E00\u5B57\u5F37\u8ABF */
// the first kanji as a huge faint glyph behind (slowly settling), the whole lyric small over it with that glyph in
// the accent colour, an emphasis dot above it and a hairline that draws under the lyric
function lb2_isSmallKana(ch) { return '\u3041\u3043\u3045\u3047\u3049\u3063\u3083\u3085\u3087\u308E\u30A1\u30A3\u30A5\u30A7\u30A9\u30C3\u30E3\u30E5\u30E7\u30EE\u30F5\u30F6'.indexOf(ch) >= 0; }
jzReg('layout', 'kanjiFocus', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), fs: rng.pick(jzFontsOf(st, ['serif', 'display', 'body'])), mode: rng.pick(['dim', 'outline', 'tint']), pos: rng.pick(['center', 'side', 'side']), low: rng.chance(0.45), dots: rng.chance(0.7), dir: rng.pick([1, -1]) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, M = jzU(ctx), u = ctx.u, port = W < H, i;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), fsF = jzP(ctx, 'fs', jzFontKeyOf(ctx.st, 'serif')), mode = jzP(ctx, 'mode', 'dim'), dir = jzP(ctx, 'dir', 1) < 0 ? -1 : 1;
        var raw = jzTrim(c.text), chs = jzChars(raw), fi = -1;
        for (i = 0; i < chs.length && fi < 0; i++) if (jzIsKanji(chs[i])) fi = i;
        for (i = 0; i < chs.length && fi < 0; i++) if (jzTrim(chs[i]) && !jzIsPunct(chs[i]) && !lb2_isSmallKana(chs[i])) fi = i;
        if (fi < 0) fi = 0;
        var fch = chs[fi] || raw;
        var big = port ? W * 1.05 : H * 1.02, side = !port && jzP(ctx, 'pos', 'center') === 'side';
        var bx = side ? W / 2 + dir * W * 0.2 : W / 2, HD = lb2_HD(ctx);
        var bo = { font: font, size: big, x: bx, y: H / 2, name: 'focus glyph' }, bop = 13;
        if (mode === 'tint') { bo.color = sc.accent; bop = 22; }
        else if (mode === 'outline') { bo.fill = false; bo.stroke = Math.max(1.2 * u, big * 0.004); bo.strokeColor = sc.sub; bop = 70; }
        else bo.color = sc.fg;
        var B = lb2_ng(jzText(ctx, fch, bo));      // faint = bright colour at low opacity; ghost: false in the browser
        jzSetExpr(jzXf(B, 'ADBE Scale'), HD + 'var s=(1.08-0.08*oc(time/' + jzN(Math.max(0.5, c.dur)) + '))*(1+0.04*ic(PO));[value[0]*s,value[1]*s]');
        jzSetExpr(jzXf(B, 'ADBE Opacity'), HD + jzN(bop) + '*oc(time/0.8)*K');
        // the lyric
        var mt = lb2_lines(ctx, raw, 16, 8), lines = mt.split('\r');
        var lx = side ? W / 2 - dir * W * 0.12 : W / 2, ly = jzP(ctx, 'low', false) ? H * 0.74 : H / 2;
        var L = lb2_txt(ctx, mt, { font: fsF, size: 100, color: sc.fg, x: lx, y: ly, track: 0.14, lead: 1.5, maxW: side ? W * 0.44 : W * 0.7, maxH: H * 0.2, maxSize: M * 0.075 });
        var size = jzFontSize(L), gl = jzGlyphs(mt), gi = -1;
        for (i = 0; i < gl.length && gi < 0; i++) if (gl[i] === fch) gi = i;
        if (gi >= 0) { var fl = []; for (i = 0; i < gl.length; i++) fl.push(i === gi); jzCharColors(L, sc.accent, fl, 'JZ Focus'); }
        jzAnimate(ctx, L, { mi: 0 });
        var bb = jzBB(L);
        if (gi >= 0 && jzP(ctx, 'dots', true)) {        // emphasis dot above the focus glyph
            var acc = 0, li = 0, ci = 0;
            for (i = 0; i < lines.length; i++) { var n = jzChars(lines[i]).length; if (gi < acc + n) { li = i; ci = gi - acc; break; } acc += n; }
            var lc = jzChars(lines[li]), mo = { font: fsF, size: size, track: 0.14, x: 0, y: 0 };
            var wl = lb2_measure(ctx, lines[li], mo).w, w1 = lb2_measure(ctx, lc.slice(0, ci + 1).join(''), mo).w, w0 = ci > 0 ? lb2_measure(ctx, lc.slice(0, ci).join(''), mo).w + size * 0.14 : 0;
            var gx = lx - wl / 2 + (w0 + w1) / 2, gy = ly + (li - (lines.length - 1) / 2) * size * 1.5;
            var Dt = jzEllipseLayer(ctx, 'focus dot', gx, gy - size * 0.78, size * 0.15, size * 0.15, sc.accent);
            jzSetExpr(jzXf(Dt, 'ADBE Scale'), HD + 'var x=cl((time-0.35)/0.25);var q=x<=0?0:ob(x,2)*K;[value[0]*q,value[1]*q]');
        }
        var y = bb.y1 + size * 0.7, UL = jzShapeLayer(ctx, 'underline', bb.x0, y), gu = jzGrp(UL, 'line');
        jzAddRect(gu, bb.x1 - bb.x0, Math.max(u, M * 0.0013), 0, (bb.x1 - bb.x0) / 2, 0); jzAddFill(gu, sc.sub);
        jzSetExpr(jzXf(UL, 'ADBE Scale'), HD + '[value[0]*oe((time-0.2)/0.6)*K,value[1]]');
        jzXf(UL, 'ADBE Opacity').setValue(60); lb2_ng(UL);
        return bb;
    }
});

/* ================================================================ 24 halfVertical \u2014 \u7E26\u6A2A\u6DF7\u690D */
// the lyric bent into an L: a horizontal arm and a vertical arm (broken at a natural boundary), a thin bracket that
// draws round the outside of the corner (Trim Paths) and an accent square popping on it
jzReg('layout', 'halfVertical', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), shape: rng.pick(['rowCol', 'rowCol', 'colRow']), guide: rng.pick(['bracket', 'tick', 'bracket']) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, M = jzU(ctx), u = ctx.u, port = W < H, i, k;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), rowCol = jzP(ctx, 'shape', 'rowCol') !== 'colRow';
        var txt = jzTrim(c.text).replace(/[\s\u3000]+/g, ' '), arr = jzChars(txt), n = arr.length;
        // break point: near the target share, at a chunk boundary / space, never before small kana or punctuation
        var tgt = n * (port ? (rowCol ? 0.38 : 0.62) : (rowCol ? 0.6 : 0.4)), segB = {}, pos = 0, ch = jzChunk(txt);
        for (i = 0; i < ch.length; i++) { while (pos < n && (arr[pos] === ' ')) pos++; pos += jzChars(ch[i]).length; segB[pos] = 1; }
        var cutAt = Math.max(1, Math.round(tgt)), bs = -1e9;
        for (k = 1; k < n; k++) {
            var pa = arr[k - 1], pb = arr[k], s0 = -Math.abs(k - tgt) * 1.2;
            if (segB[k]) s0 += 2;
            if (pa === ' ' || pb === ' ') s0 += 3;
            if (lb2_isSmallKana(pb) || jzIsPunct(pb) || pb === '\u30FC') s0 -= 8;
            if (lb2_isSmallKana(pa)) s0 -= 3;
            if (jzIsHira(pa) && !jzIsHira(pb)) s0 += 1.5;
            if (s0 > bs) { bs = s0; cutAt = k; }
        }
        var A = jzTrim(arr.slice(0, cutAt).join('')) || arr[0] || txt, B = jzTrim(arr.slice(cutAt).join(''));
        var tr = 0.06, nA = jzCount(A), nB = jzCount(B);
        var latA = !rowCol && lb2_isLatinStr(A), latB = rowCol && B && lb2_isLatinStr(B);     // vertical latin arm = a line turned 90 deg
        var wA = (rowCol || latA) ? lb2_measure(ctx, A, { font: font, size: 100, track: tr }).w : (nA * 1.06 - 0.06) * 100;
        var wB = B ? ((!rowCol || latB) ? lb2_measure(ctx, B, { font: font, size: 100, track: tr }).w : (nB * 1.06 - 0.06) * 100) : 0;
        var wU = rowCol ? wA / 100 : 1.25 + wB / 100, hU = rowCol ? 1.25 + wB / 100 : wA / 100;
        var size = Math.min(W * 0.82 / wU, H * 0.8 / hU, M * 0.26), x0 = W / 2 - wU * size / 2, y0 = H / 2 - hU * size / 2;
        var bb = null, corner, LA, LB;
        if (rowCol) {
            LA = jzText(ctx, A, { font: font, size: size, color: sc.fg, x: x0, y: y0 + size / 2, align: 'left', track: tr });
            var lastX = x0 + jzSize(LA)[0] - (jzIsLatin(arr[cutAt - 1] || '') ? jzSize(LA)[0] / Math.max(1, nA) / 2 : size * 0.45);
            jzAnimate(ctx, LA, { mi: 0 }); bb = jzBB(LA);
            if (B) {
                if (latB) { LB = jzText(ctx, B, { font: font, size: size, color: sc.fg, x: lastX, y: 0, track: tr, rot: 90 }); jzXf(LB, 'ADBE Position').setValue([lastX, y0 + size * 1.25 + jzSize(LB)[0] / 2]); }
                else { LB = jzText(ctx, jzVertical(B), { font: font, size: size, color: sc.fg, x: lastX, y: 0, leading: size * (1 + tr) }); lb2_top(LB, lastX, y0 + size * 1.25); }
                jzAnimate(ctx, LB, { mi: 3 });
                bb = jzUnion(bb, { x0: lastX - size / 2, x1: lastX + size / 2, y0: y0 + size * 1.25, y1: y0 + hU * size, cx: lastX, cy: y0 + size * 1.25 + (hU - 1.25) * size / 2 });
            }
            corner = [lastX, y0 + size / 2];
        } else {
            var lastY;
            if (latA) { LA = jzText(ctx, A, { font: font, size: size, color: sc.fg, x: x0 + size / 2, y: 0, track: tr, rot: 90 }); jzXf(LA, 'ADBE Position').setValue([x0 + size / 2, y0 + jzSize(LA)[0] / 2]); lastY = y0 + jzSize(LA)[0] - size * 0.3; }
            else { LA = jzText(ctx, jzVertical(A), { font: font, size: size, color: sc.fg, x: x0 + size / 2, y: 0, leading: size * (1 + tr) }); lb2_top(LA, x0 + size / 2, y0); lastY = y0 + (nA - 1) * size * (1 + tr) + size / 2; }
            jzAnimate(ctx, LA, { mi: 0 }); bb = { x0: x0, x1: x0 + size, y0: y0, y1: y0 + hU * size, cx: x0 + size / 2, cy: y0 + hU * size / 2 };
            if (B) { LB = jzText(ctx, B, { font: font, size: size, color: sc.fg, x: x0 + size * 1.25, y: lastY, align: 'left', track: tr }); jzAnimate(ctx, LB, { mi: 3 }); bb = jzUnion(bb, jzBB(LB)); }
            corner = [x0 + size / 2, lastY];
        }
        var g = size * 0.42, lw = Math.max(1.2 * u, M * 0.0016), HD = lb2_HD(ctx);
        if (jzP(ctx, 'guide', 'bracket') === 'bracket') {
            var pts = rowCol ? [[x0 - g * 0.3, y0 - g * 0.55], [corner[0] + size / 2 + g * 0.55, y0 - g * 0.55], [corner[0] + size / 2 + g * 0.55, y0 + hU * size + g * 0.3]]
                : [[x0 - g * 0.55, y0 - g * 0.3], [x0 - g * 0.55, corner[1] + size / 2 + g * 0.55], [x0 + wU * size + g * 0.3, corner[1] + size / 2 + g * 0.55]];
            var G = lb2_ng(lb2_poly(ctx, 'corner bracket', pts, null, sc.sub, lw));
            jzAddTrimPaths(G.property('ADBE Root Vectors Group').property(1), HD + '100*oc((time-0.15)/0.7)*K');
            jzXf(G, 'ADBE Opacity').setValue(80);
        }
        var cs = size * 0.12, px = rowCol ? corner[0] + size / 2 + g * 0.55 : x0 - g * 0.55, py = rowCol ? y0 - g * 0.55 : corner[1] + size / 2 + g * 0.55;
        var Q = jzRectLayer(ctx, 'corner square', px, py, cs, cs, sc.accent);
        jzSetExpr(jzXf(Q, 'ADBE Scale'), HD + 'var x=cl((time-0.3)/0.25);var q=x<=0?0:ob(x,2)*K;[value[0]*q,value[1]*q]');
        return bb;
    }
});

/* ================================================================ 25 curtain \u2014 \u5E55 */
// theatre curtain over the lyric: two side panels (with a bowing inner edge, pleats and tie-backs), a top/bottom
// shutter (with a small line/time label) or a rising curtain with a scalloped valance; opens after the cut starts,
// closes again on the exit. Panels are shape layers moved by Position expressions.
jzReg('layout', 'curtain', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), variant: rng.pick(['side', 'side', 'shutter', 'rise']), col: rng.pick(['velvet', 'velvet', 'accent', 'ink']), drape: rng.chance(0.7), pleats: rng.chance(0.75) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, M = jzU(ctx), u = ctx.u, i;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), variant = jzP(ctx, 'variant', 'side'), colK = jzP(ctx, 'col', 'velvet'), drape = !!jzP(ctx, 'drape', true), pleats = !!jzP(ctx, 'pleats', true);
        var mt = lb2_lines(ctx, c.text, 10, 5), t0 = 0.02, T = jzClamp(c.dur * 0.16, 0.22, 0.48);
        var L = lb2_txt(ctx, mt, { font: font, size: 100, color: sc.fg, x: W / 2, y: H / 2, track: 0.05, lead: 1.15, maxW: W * (drape && variant === 'side' ? 0.7 : 0.8), maxH: H * (variant === 'shutter' ? 0.34 : 0.44), maxSize: H * 0.2 });
        jzAnimate(ctx, L, { mi: lb2_miAt(ctx, t0 + T * 0.25) });
        var dark = jzRelLum(sc.bg) < 0.45;
        var panel = colK === 'velvet' ? jzMixHex(sc.bg, sc.accent, dark ? 0.38 : 0.6) : lb2_plateCol(sc, colK === 'accent' ? [sc.accent, sc.ink] : [sc.ink, sc.accent]);
        var shade = jzMixHex(panel, sc.bg, 0.22), lite = jzMixHex(panel, lb2_onCol(sc, panel), 0.12), lw = Math.max(u, M * 0.002);
        var OP = lb2_HD(ctx) + 'var op=ioc((time-' + jzN(t0) + ')/' + jzN(T) + ')*(1-ioc(PO));';
        function pleatGrp(S, x, xExpr, y, h, k) {
            var g = jzGrp(S, 'pleat ' + k); jzAddRect(g, lw * 3, h); jzAddFill(g, k % 2 ? shade : lite, 55);
            if (xExpr) jzGX(g).property('ADBE Vector Position').expression = OP + '[' + xExpr + ',' + jzN(y) + ']'; else jzGX(g).property('ADBE Vector Position').setValue([x, y]);
        }
        if (variant === 'side') {
            var rest = drape ? W * 0.075 : -W * 0.02, tie = sc.accent === panel ? sc.fg : sc.accent;
            for (var sd = -1; sd <= 1; sd += 2) {           // -1 = left panel (inner edge at layer x 0, panel towards -x)
                var P = lb2_ng(jzShapeLayer(ctx, sd < 0 ? 'curtain left' : 'curtain right', W / 2, 0));
                var XE = sd < 0 ? '(' + jzN(W / 2) + '+' + jzN(rest - W / 2) + '*op)' : '(' + jzN(W / 2) + '+' + jzN(W / 2 - rest) + '*op)';
                jzSetExpr(jzXf(P, 'ADBE Position'), OP + '[' + XE + ',value[1]]');
                if (drape) {
                    var gt = jzGrp(P, 'tie-back'); jzAddRect(gt, W * 0.012 + 2, H * 0.018, 0, sd < 0 ? -W * 0.006 + 1 : W * 0.006 - 1, H * 0.62 + H * 0.009); jzAddFill(gt, tie);
                    jzGX(gt).property('ADBE Vector Group Opacity').expression = OP + '100*cl((op-0.5)*2)';
                }
                if (pleats) for (i = 1; i < 7; i++) pleatGrp(P, 0, sd < 0 ? '-' + XE + '*' + jzN(1 - i / 7) : '(' + jzN(W) + '-' + XE + ')*' + jzN(i / 7), H / 2, H, i);
                var gbul = jzGrp(P, 'bulge'); jzAddPath(gbul, [[0, -5], [-sd * W * 0.02, H * 0.5], [0, H + 5]], true); jzAddFill(gbul, panel);
                jzGX(gbul).property('ADBE Vector Scale').expression = OP + '[100*Math.sin(Math.PI*cl(op)),100]';
                var gp = jzGrp(P, 'panel'); jzAddRect(gp, W * 0.6 + 10, H + 10, 0, sd * (W * 0.3 + 5), H / 2); jzAddFill(gp, panel);
            }
        } else if (variant === 'shutter') {
            var rs = drape ? H * 0.12 : -H * 0.02;
            var YT = '(' + jzN(H / 2) + '+' + jzN(rs - H / 2) + '*op)', YB = '(' + jzN(H / 2) + '+' + jzN(H / 2 - rs) + '*op)';
            for (var sh = -1; sh <= 1; sh += 2) {
                var PS = lb2_ng(jzShapeLayer(ctx, sh < 0 ? 'shutter top' : 'shutter bottom', 0, H / 2)), gs = jzGrp(PS, 'panel');
                jzAddRect(gs, W + 10, H * 0.6 + 10, 0, W / 2, sh * (H * 0.3 + 5)); jzAddFill(gs, panel);
                jzSetExpr(jzXf(PS, 'ADBE Position'), OP + '[value[0],' + (sh < 0 ? YT : YB) + ']');
            }
            if (drape) {
                var fs = jzClamp(H * 0.018, 11 * u, 22 * u);
                var LB = jzText(ctx, jzLineNo(ctx) + ' \uFF0F ' + jzFmtTime(c.start || 0), { font: jzMonoF(ctx), size: fs, color: lb2_onCol(sc, panel), x: W * 0.05, y: rs / 2, align: 'left', track: 0.3 });
                jzSetExpr(jzXf(LB, 'ADBE Position'), OP + '[value[0],' + YT + '/2]');
                jzSetExpr(jzXf(LB, 'ADBE Opacity'), OP + '100*cl((op-0.9)*10)'); lb2_ng(LB);
            }
        } else {
            // rising curtain with a scalloped valance: the panel's bottom edge rides on the layer position
            var rr = drape ? H * 0.1 : -H * 0.05, m = 9, sw = W / m, dip = H * 0.035;
            var PR = lb2_ng(jzShapeLayer(ctx, 'curtain', 0, H + 5)), pts = [[-5, -H - 10], [W + 5, -H - 10], [W + 5, 0]];
            for (i = m; i >= 0; i--) { pts.push([i * sw, 0]); if (i > 0) pts.push([i * sw - sw / 2, dip]); }
            pts.push([-5, 0]);
            if (pleats) for (i = 1; i < 7; i++) pleatGrp(PR, W * i / 7, null, -H / 2 - 5, H + 10, i);
            var gr = jzGrp(PR, 'panel'); jzAddPath(gr, pts, true); jzAddFill(gr, panel);
            jzSetExpr(jzXf(PR, 'ADBE Position'), OP + '[value[0],' + jzN(H + 5) + '+' + jzN(rr - H - 5) + '*op]');
        }
        // (the panels are kept out of the cut's time-shifted ghost copies \u2014 the browser draws no ghosts here \u2014 so they leave
        // no coloured trails while they move)
        return jzBB(L);
    }
});

/* ================================================================ 26 equalizer \u2014 \u30A4\u30B3\u30E9\u30A4\u30B6\u30FC */
// one bar per glyph (+ thin spectrum bars between) driven by an expression level = noise + a decaying pulse every
// `tempo` s (a sin-hash, not random(): AE seeds random() per property, and bar / cap / text must agree); each glyph is parented to its bar layer, so it rides on the bar top; bars / blocks / mirror styles, peak caps
function lb2_eqLevel(ctx, tempo) {
    return 'var TP=' + jzN(tempo) + ',SD2=' + ((ctx.cut.seed || 1) % 9973) + ';function lv(x,t){var v=0.5+0.5*noise([t*3.1+x*0.83,SD2*0.37]);v=0.25+0.6*v;var k=Math.floor(t/TP),ph=t-k*TP;' +
        'var hs=Math.sin(k*127.1+Math.round(x*3)*311.7+SD2*0.713)*43758.5453;var pu=Math.exp(-ph*7)*(0.3+0.7*(hs-Math.floor(hs)));v=v*0.75+pu*0.45;return Math.max(0.06,Math.min(1,v));}';
}
// bar groups drawn downward from the bar top (group origin = top); HE defines h; oy = extra y expression for the origin
function lb2_eqBar(S, HE, ox, oy, w, col, alpha, o) {
    var g, pre = function (y) { return HE + '[' + jzN(ox) + ',' + oy + '+' + y + ']'; };
    if (o.style === 'blocks') {
        var parts = [[col, 0, 'Math.min(' + o.kA + ',Math.floor(h/' + jzN(o.seg) + '))'], [o.colB, o.kA, 'Math.max(0,Math.floor(h/' + jzN(o.seg) + ')-' + o.kA + ')']];
        for (var q = 0; q < 2; q++) {
            g = jzGrp(S, q ? 'blocks top' : 'blocks');
            jzAddRect(g, w, o.seg - o.gap, 0, 0, -(parts[q][1] + 0.5) * o.seg); jzAddFill(g, parts[q][0], alpha * 100);
            var rp = jzVecs(g).addProperty('ADBE Vector Filter - Repeater');
            rp.property('ADBE Vector Repeater Copies').expression = HE + parts[q][2];
            rp.property('ADBE Vector Repeater Transform').property('ADBE Vector Repeater Position').setValue([0, -o.seg]);
            jzGX(g).property('ADBE Vector Position').expression = pre('h');
        }
    } else {
        // (each rect is configured before its fill is added: adding a sibling invalidates the rect reference)
        g = jzGrp(S, 'bar'); var r = jzAddRect(g, w, 10);
        r.property('ADBE Vector Rect Size').expression = HE + '[value[0],Math.max(0,h)]';
        jzAddFill(g, col, alpha * 100);
        jzGX(g).property('ADBE Vector Position').expression = pre('h/2');
        if (o.style === 'mirror') {
            var gm = jzGrp(S, 'reflection'), rm = jzAddRect(gm, w, 10);
            rm.property('ADBE Vector Rect Size').expression = HE + '[value[0],Math.max(0,h*0.35)]';
            jzAddFill(gm, col, alpha * 22);
            jzGX(gm).property('ADBE Vector Position').expression = pre('h+' + jzN(o.lw * 2) + '+h*0.175');
        }
    }
}
jzReg('layout', 'equalizer', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display'])), style: rng.pick(['bars', 'blocks', 'blocks', 'mirror']), thin: rng.pick([0, 2, 3]), peaks: rng.chance(0.7), col: rng.pick(['accent', 'accent', 'duo', 'fg']), tempo: rng.range(0.42, 0.55) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, M = jzU(ctx), u = ctx.u, i, j, r, q;
        var chs = jzChars(jzTrim(c.text).replace(/[\s\u3000]+/g, ' ')), n = chs.length;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), style = jzP(ctx, 'style', 'blocks'), thin = Math.round(jzP(ctx, 'thin', 2)), peaks = !!jzP(ctx, 'peaks', true), colK = jzP(ctx, 'col', 'accent');
        var rows = W < H && n > 6 ? 2 : 1, per = Math.ceil(n / rows), sw = W * 0.86 / per;
        var size = Math.min(sw * 0.78, H * (rows > 1 ? 0.1 : 0.14)), bw = Math.min(sw * 0.6, size * 1.15);
        var colA = lb2_plateCol(sc, [sc.accent, sc.fg]), colB = lb2_plateCol(sc, [sc.accent2, sc.sub, sc.fg]), lw = Math.max(u, M * 0.0016);
        var HD = lb2_HD(ctx) + lb2_eqLevel(ctx, jzP(ctx, 'tempo', 0.5)) + 'var e=oe(time/0.45);';
        var bb = null, pkH = Math.max(3 * u, sw * 0.05), pkOff = peaks ? sw * 0.1 : 0, seg = Math.max(4 * u, sw * 0.14);
        for (r = 0; r < rows; r++) {
            var cnt = Math.min(per, n - r * per); if (cnt <= 0) break;
            var yb = rows > 1 ? (r ? H * 0.84 : H * 0.47) : H * 0.74, hmax = (rows > 1 ? H * 0.26 : H * 0.44) - size * 0.4, x0 = W / 2 - cnt * sw / 2;
            var bo = { style: style, seg: seg, gap: seg * 0.28, kA: Math.floor(hmax / seg * 0.72) + 1, colB: colB, lw: lw };
            // base line
            var BL = lb2_ng(jzShapeLayer(ctx, 'eq base', x0 - sw * 0.2, yb)), gl = jzGrp(BL, 'line');
            jzAddRect(gl, cnt * sw + sw * 0.4, lw, 0, (cnt * sw + sw * 0.4) / 2, 0); jzAddFill(gl, sc.sub);
            jzSetExpr(jzXf(BL, 'ADBE Scale'), HD + '[value[0]*e,value[1]]');
            jzSetExpr(jzXf(BL, 'ADBE Opacity'), HD + '70*K');
            // thin spectrum bars
            if (thin > 0) {
                var TS = lb2_ng(jzShapeLayer(ctx, 'eq spectrum', 0, 0));
                for (j = 0; j < cnt; j++) for (q = 1; q <= thin; q++) {
                    var xi = j + q / (thin + 1);
                    var HT = HD + 'var h=' + jzN(hmax * 0.8) + '*lv(' + jzN(xi + r * 7 + 0.5) + ',time)*e*K;';
                    lb2_eqBar(TS, HT, x0 + xi * sw, '(' + jzN(yb) + '-h)', sw * 0.06, sc.sub, 0.55, bo);
                }
            }
            for (j = 0; j < cnt; j++) {
                i = r * per + j;
                if (chs[i] === ' ') continue;
                var x = x0 + (j + 0.5) * sw, X = jzN(j + 0.5 + r * 7);
                var HB = HD + 'var h=' + jzN(hmax) + '*lv(' + X + ',time)*e*K;';
                var col = colK === 'duo' ? (j % 2 ? colB : colA) : colK === 'fg' ? sc.fg : colA;
                var BR = lb2_ng(jzShapeLayer(ctx, 'eq bar ' + (i + 1), x, yb));
                jzSetExpr(jzXf(BR, 'ADBE Position'), HB + '[value[0],value[1]-h]');
                if (peaks) {
                    var gp = jzGrp(BR, 'peak'); jzAddRect(gp, bw, pkH); jzAddFill(gp, sc.fg);
                    var PK = HB + 'var pk=0;for(var k=0;k<6;k++){var tau=k*0.1;pk=Math.max(pk,lv(' + X + ',time-tau)-tau*0.55);}var ph=' + jzN(hmax) + '*pk*e*K;';
                    jzGX(gp).property('ADBE Vector Position').expression = PK + '[0,h-ph-' + jzN(sw * 0.04 + pkH / 2) + ']';
                    jzGX(gp).property('ADBE Vector Group Opacity').expression = PK + '90*e*K';
                }
                lb2_eqBar(BR, HB, 0, '0', bw, col, colK === 'fg' ? 0.35 : 0.9, bo);
                var T = jzText(ctx, chs[i], { font: font, size: size, color: sc.fg, x: x, y: yb - size * 0.62 - pkOff });
                T.parent = BR;
                jzAnimate(ctx, T, { mi: i });
            }
            bb = jzUnion(bb, { x0: x0, x1: x0 + cnt * sw, y0: yb - hmax * 0.7 - size * 1.2, y1: yb, cx: W / 2, cy: yb - hmax * 0.35 });
        }
        return bb;
    }
});

/* ================================================================ 27 tape \u2014 \u30C6\u30FC\u30D7 */
// strips of tape with torn ends (shape polygons, faint stripes, edge shading) that unroll from their left end \u2014 a
// layer mask whose Mask Expansion grows with time clips the strip and the text on it; stack / single / cross + a label piece
function lb2_tapeStrip(ctx, o) {
    var h = o.h, L = o.L, tooth = h * 0.09, m = 7, i, s = ctx.cut.seed || 1, pts = [[-L / 2, -h / 2], [L / 2, -h / 2]];
    for (i = 1; i <= m; i++) pts.push([L / 2 + (i % 2 ? tooth : -tooth * 0.3) * (0.6 + 0.8 * jzR(s, o.k, i, 1)), -h / 2 + h * i / m]);
    pts.push([-L / 2, h / 2]);
    for (i = m - 1; i >= 1; i--) pts.push([-L / 2 + (i % 2 ? -tooth : tooth * 0.3) * (0.6 + 0.8 * jzR(s, o.k, i, 2)), -h / 2 + h * i / m]);
    var S = lb2_ng(jzShapeLayer(ctx, o.name || 'tape', o.x, o.y)), g;
    jzXf(S, 'ADBE Rotate Z').setValue(o.ang);
    var edge = o.dark ? '#000000' : '#FFFFFF';
    g = jzGrp(S, 'edges'); jzAddRect(g, L, h * 0.08, 0, 0, -h / 2 + h * 0.04); jzAddRect(g, L, h * 0.08, 0, 0, h / 2 - h * 0.04); jzAddFill(g, edge, 12);
    if (o.lines) { g = jzGrp(S, 'stripes'); for (i = 1; i < 6; i++) jzAddRect(g, L, Math.max(ctx.u, h * 0.012), 0, 0, -h / 2 + h * i / 6); jzAddFill(g, lb2_onCol(ctx.sc, o.col), 7); }
    g = jzGrp(S, 'tape'); jzAddPath(g, pts, true); jzAddFill(g, jzMixHex(ctx.sc.bg, o.col, 0.9));     // opaque (the browser's 90 %): no ghost shows through
    lb2_tapeClip(ctx, S, -L / 2, 0, h, o);
    jzSetExpr(jzXf(S, 'ADBE Opacity'), lb2_tapeU(ctx, o) + 'u>0?100*K:0');
    return S;
}
function lb2_tapeU(ctx, o) { return lb2_HD(ctx) + 'var u=oc((time-' + jzN(o.t0) + ')/' + jzN(o.dur) + '),VL=' + jzN(o.L) + '*u;'; }
// unrolling clip in layer space (browser: clip rect from -h to -h + L*u in strip coords): a rectangle whose right edge
// starts one strip-height left of the strip's left end (lx) and grows by Mask Expansion
function lb2_tapeClip(ctx, Ly, lx, cy, h, o) {
    var mk = jzMaskRect(Ly, lx - h * 5, cy - h * 3, lx - h, cy + h * 3);
    jzSetExpr(mk.property('ADBE Mask Offset'), lb2_tapeU(ctx, o) + 'u>=1?10000:VL');
    return mk;
}
// text riding on a strip, clipped by the same unroll
function lb2_tapeText(ctx, str, o, T) {
    var X = jzText(ctx, str, T), r = jzRect(X), ax = r.left + r.width / 2, ay = r.top + r.height / 2;
    jzXf(X, 'ADBE Rotate Z').setValue(o.ang);
    lb2_tapeClip(ctx, X, ax - o.L / 2, ay, o.h, o);
    return X;
}
jzReg('layout', 'tape', {
    plan: function (rng, cut, st) {
        var n = cut.n, port = cut.H > cut.W;
        return { font: rng.pick(jzFontsOf(st, ['display', 'body'])), fs: rng.pick(jzFontsOf(st, ['body', 'serif'])),
            variant: n > 9 || (port && n > 5) ? rng.pick(['stack', 'stack', 'single']) : rng.pick(['single', 'cross', 'stack']),
            ang: rng.range(4, 11) * rng.pick([1, -1]), col: rng.pick(['accent', 'ink', 'accent']), piece: rng.chance(0.75), lines: rng.chance(0.6) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, M = jzU(ctx), u = ctx.u, s = c.seed || 1, port = W < H, i, k;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), fsF = jzP(ctx, 'fs', jzFontKeyOf(ctx.st, 'body')), variant = jzP(ctx, 'variant', 'single');
        var ang = jzP(ctx, 'ang', 6), colK = jzP(ctx, 'col', 'accent'), lines = !!jzP(ctx, 'lines', true), dark = jzRelLum(sc.bg) < 0.45;
        var tapeC = lb2_plateCol(sc, colK === 'accent' ? [sc.accent, sc.ink] : [sc.ink, sc.accent]);
        var tapeC2 = colK === 'accent' ? lb2_plateCol(sc, [sc.ink, sc.fg]) : lb2_plateCol(sc, [sc.accent, sc.fg]);
        var noHold = lb2_plateHold(ctx), txt = jzTrim(c.text), bb = null;
        if (variant === 'stack') {
            var parts = [], src = c.words && c.words.length > 1 ? c.words : [txt];
            for (i = 0; i < src.length; i++) if (jzTrim(src[i])) parts.push(jzTrim(src[i]));
            if (parts.length > 4) { k = Math.ceil(parts.length / 4); var qq = []; for (i = 0; i < parts.length; i += k) qq.push(parts.slice(i, i + k).join('')); parts = qq; }
            if (parts.length <= 1 && jzCount(txt) > (port ? 5 : 9)) parts = jzSplitLines(txt, Math.ceil(jzCount(txt) / 2)).split('\r');
            if (!parts.length) parts = [txt];
            var np = parts.length, longest = 1;
            for (i = 0; i < np; i++) longest = Math.max(longest, lb2_measure(ctx, parts[i], { font: font, size: 100, track: 0.05 }).w / 100);
            var size = Math.min(W * 0.72 / longest, H * 0.62 / (np * 1.75), M * 0.17), h = size * 1.5;
            for (i = 0; i < np; i++) {
                var Lp = lb2_measure(ctx, parts[i], { font: font, size: size, track: 0.05 }).w + size * 1.2;
                var y = H / 2 + (i - (np - 1) / 2) * h * 1.12, x = W / 2 + (jzR(s, i, 3) * 2 - 1) * W * 0.05;
                var col = i % 3 === 1 ? tapeC2 : tapeC;
                var o = { x: x, y: y, ang: (i % 2 ? -1 : 1) * Math.abs(ang) * 0.45 + (jzR(s, i, 4) * 2 - 1) * 1.5, L: Lp, h: h, col: col, k: i, t0: 0.03 + i * 0.12, dur: 0.3, lines: lines, dark: dark, name: 'tape ' + (i + 1) };
                lb2_tapeStrip(ctx, o);
                var TX = lb2_tapeText(ctx, parts[i], o, { font: font, size: size, color: lb2_onCol(sc, col), x: x, y: y, track: 0.05 });
                jzAnimate(ctx, TX, { mi: lb2_miAt(ctx, 0.05 + i * 0.12), noHold: noHold });
                bb = jzUnion(bb, { x0: x - Lp / 2, x1: x + Lp / 2, y0: y - h / 2, y1: y + h / 2, cx: x, cy: y });
            }
            return bb;
        }
        var mt = lb2_lines(ctx, txt, 11, 6), nL = mt.split('\r').length;
        var mm = lb2_measure(ctx, mt, { font: font, size: 100, track: 0.05, lead: 1.15, x: W / 2, y: H / 2, maxW: W * 0.7, maxH: H * 0.3, maxSize: H * 0.17 }), sz = mm.size;
        var Lm = mm.w + sz * 1.6, hm = sz * (nL * 1.15 - 0.15) + sz * 0.75;
        if (variant === 'cross') {
            var unit = jzTrim(String(c.lineText || c.text).replace(/\s+/g, ' ')), fs = hm * 0.26;
            var L2 = Math.min(Math.sqrt(W * W + H * H) * 0.9, lb2_measure(ctx, unit, { font: fsF, size: fs, track: 0.1 }).w * 1.3 + fs * 6);
            var oc = { x: W / 2 + Lm * 0.15, y: H / 2 + hm * 0.1, ang: -ang * 2.4, L: L2, h: hm * 0.5, col: tapeC2, k: 9, t0: 0, dur: 0.4, lines: lines, dark: dark, name: 'tape cross' };
            lb2_tapeStrip(ctx, oc);
            var CT = lb2_tapeText(ctx, unit, oc, { font: fsF, size: fs, color: lb2_onCol(sc, tapeC2), x: oc.x, y: oc.y, track: 0.1 });
            jzSetExpr(jzXf(CT, 'ADBE Opacity'), lb2_tapeU(ctx, oc) + 'u>0?100*K:0'); lb2_ng(CT);
        }
        var om = { x: W / 2, y: H / 2, ang: ang, L: Lm, h: hm, col: tapeC, k: 0, t0: 0.06, dur: 0.34, lines: lines, dark: dark, name: 'tape' };
        lb2_tapeStrip(ctx, om);
        var TM = lb2_tapeText(ctx, mt, om, { font: font, size: sz, color: lb2_onCol(sc, tapeC), x: W / 2, y: H / 2, track: 0.05, leading: sz * 1.15 });
        jzAnimate(ctx, TM, { mi: lb2_miAt(ctx, 0.1), noHold: noHold });
        if (jzP(ctx, 'piece', true)) {
            var ph = hm * 0.42, pl = ph * 2.6, ex = W / 2 + Math.cos(ang * Math.PI / 180) * Lm * 0.5, ey = H / 2 + Math.sin(ang * Math.PI / 180) * Lm * 0.5;
            var rom = lb2_roma(txt), lab = rom ? rom.substr(0, 12) : 'No.' + jzLineNo(ctx);
            var op = { x: ex - ph * 0.3, y: ey - ph * 0.4, ang: ang - 38 * (ang < 0 ? -1 : 1), L: pl, h: ph, col: tapeC2, k: 5, t0: 0.32, dur: 0.18, lines: lines, dark: dark, name: 'tape piece' };
            lb2_tapeStrip(ctx, op);
            var PT = lb2_tapeText(ctx, lab, op, { font: jzMonoF(ctx), size: Math.min(ph * 0.34, pl * 0.7 / Math.max(3, lab.length) * 1.6), color: lb2_onCol(sc, tapeC2), x: op.x, y: op.y, track: 0.12 });
            jzSetExpr(jzXf(PT, 'ADBE Opacity'), lb2_tapeU(ctx, op) + 'u>0?100*K:0'); lb2_ng(PT);
        }
        return { x0: W / 2 - Lm / 2, x1: W / 2 + Lm / 2, y0: H / 2 - hm / 2, y1: H / 2 + hm / 2, cx: W / 2, cy: H / 2 };
    }
});

// ================================================================ pack layoutsC part 1 (AE port of src/11p_layoutsC.js, entries 1-16)
// magazine, headlineDeck, contents, footnote, proofread, numbered, poster, swissGrid, dictionary, ema, ransom, newspaper,
// vinyl, cassette, bookSpine, polaroid
//
// Techniques used here:
// - print objects that move as a whole (pages, plaques, cassettes, polaroids, the spinning newspaper) are one shape layer
//   (or a null) whose transform is driven by expressions; the texts that ride on them are parented to it, so the cut's
//   enter / hold / exit expressions on the lyric stay untouched.
// - secondary copy (body rows, greeked columns, leaders, crop marks) are text / shape layers faded or trimmed in with
//   expressions on `time` (0 = cut start); lc1_H(ctx) = jzTH(ctx) (DUR IN OS OD PO K + easings) + ioc (inOutCubic).
// - glyph positions of the lyric (footnote marks, proofreading marks) are estimated like the browser's layoutText
//   (exact for full-width Japanese) and scaled to the AE text layer's measured width.

var LC1_CY = 0.38;       // glyph centre above the baseline (em)
var LC1_FX = 'function ioc(x){x=cl(x);return x<0.5?4*x*x*x:1-Math.pow(-2*x+2,3)/2;}\n';
var LC1_VROT = '\u30FC\u301C\uFF5E\u2026\u2025\u2015\u2014-()\uFF08\uFF09\u300C\u300D\u300E\u300F\u3010\u3011\u3008\u3009\u300A\u300B\u3014\u3015[]\uFF3B\uFF3D\u2192\u2190:\uFF1A;\uFF1B=\uFF1D';
var LC1_SMALL = '\u3041\u3043\u3045\u3047\u3049\u3063\u3083\u3085\u3087\u308E\u30A1\u30A3\u30A5\u30A7\u30A9\u30C3\u30E3\u30E5\u30E7\u30EE\u30F5\u30F6';
var LC1_VPUNCT = '\u3001\u3002\uFF0C\uFF0E';

/* ================================================================ small helpers */
function lc1_H(ctx) { return jzTH(ctx) + LC1_FX; }
function lc1_k(ctx) { return jzU(ctx) / 1080; }                                 // browser design px -> comp px
function lc1_ls(ctx) { var k = lc1_k(ctx); return jzClamp(jzU(ctx) * 0.024, 14 * k, 34 * k); }   // smallSize
function lc1_pad3(n) { return jzPad(n, 3); }
function lc1_lineN(ctx) { return Math.max(0, ctx.cut.line || 0) + 1; }
function lc1_isSmall(ch) { return LC1_SMALL.indexOf(ch) >= 0 && ch.length > 0; }
function lc1_isBad(ch) { return lc1_isSmall(ch) || jzIsPunct(ch) || ch === '\u30FC' || ch === ' '; }
function lc1_romaOf(t) { if (!t || jzHasLatin(t)) return null; var r = jzRomaji(jzStrip(t)); return r ? r.toUpperCase() : null; }
function lc1_vtext(t) { return jzHasLatin(t) ? jzFlat(t) : jzStrip(t); }
function lc1_deck(ctx) { var c = ctx.cut; if (c.lineText && jzStrip(c.lineText) !== jzStrip(c.text)) return jzFlat(c.lineText); return c.note || jzRomajiOf(ctx) || null; }
function lc1_meta(ctx) { return 'No.' + jzLineNo(ctx) + '  \uFF0F  ' + jzFmtTime(ctx.cut.start) + '  \uFF0F  ' + jzCount(ctx.cut.text) + (jzHasLatin(ctx.cut.text) ? ' CHARS' : '\u5B57'); }
function lc1_cut(s, n) { var a = jzChars(String(s)); return a.length > n ? a.slice(0, n).join('') : String(s); }
function lc1_ell(s, maxC) { var a = jzChars(String(s)); return a.length > maxC ? a.slice(0, maxC - 1).join('') + '\u2026' : String(s); }
function lc1_plateHold(ctx) { return jzIndexOf(['still', 'jitter', 'breathe', 'glitchtick'], ctx.cut.hold || 'still') < 0; }
function lc1_mi(ctx, t) { return Math.max(0, t) / (ctx.cut.stagger || 0.04); }  // motion index whose entrance starts at local time t
var LC1_KNUM = '\u3007\u4E00\u4E8C\u4E09\u56DB\u4E94\u516D\u4E03\u516B\u4E5D';
function lc1_kanjiNum(n) {
    n = Math.max(0, Math.floor(n));
    if (n < 10) return LC1_KNUM.charAt(n);
    if (n < 20) return '\u5341' + (n % 10 ? LC1_KNUM.charAt(n % 10) : '');
    if (n < 100) return LC1_KNUM.charAt(Math.floor(n / 10)) + '\u5341' + (n % 10 ? LC1_KNUM.charAt(n % 10) : '');
    return String(n);
}
function lc1_box(x0, y0, x1, y1) { return { x0: x0, y0: y0, x1: x1, y1: y1, cx: (x0 + x1) / 2, cy: (y0 + y1) / 2 }; }
// true comp-space bbox of an unparented, unrotated layer
function lc1_bb(L) {
    var r = jzRect(L), a = jzXf(L, 'ADBE Anchor Point').value, p = jzXf(L, 'ADBE Position').value, s = jzXf(L, 'ADBE Scale').value;
    var x0 = p[0] + (r.left - a[0]) * s[0] / 100, y0 = p[1] + (r.top - a[1]) * s[1] / 100;
    return lc1_box(x0, y0, x0 + r.width * s[0] / 100, y0 + r.height * s[1] / 100);
}

/* ---- colour (scheme colours only) */
function lc1_onCol(sc, fill) {
    var c = [sc.bg, sc.fg, sc.ink, sc.accent, sc.sub], best = null, bv = 0;
    for (var i = 0; i < c.length; i++) { if (!c[i] || String(c[i]).toUpperCase() === String(fill).toUpperCase()) continue; var k = jzContrast(c[i], fill); if (k > bv) { bv = k; best = c[i]; } }
    return bv >= 2.4 ? best : (jzLum(fill) > 0.5 ? '#111111' : '#FFFFFF');
}
function lc1_plateCol(sc, pref, against, min) {
    var bgc = against || sc.bg; if (min == null) min = 1.6;
    for (var i = 0; i < pref.length; i++) if (pref[i] && jzContrast(pref[i], bgc) >= min) return pref[i];
    return jzContrast(sc.fg, bgc) >= jzContrast(sc.bg, bgc) ? sc.fg : sc.bg;
}
function lc1_card(sc) {
    var fill = jzLightest(sc), text = lc1_onCol(sc, fill);
    return { fill: fill, edge: jzContrast(fill, sc.bg) < 1.4, text: text, acc: lc1_plateCol(sc, [sc.accent, sc.accent2, sc.ink], fill, 2), faint: jzMixHex(fill, text, 0.22), line: jzMixHex(fill, text, 0.35) };
}
function lc1_shA(sc) { return jzLum(sc.bg) > 0.5 ? 0.22 : 0.5; }
// hard offset shadow under a paper object (browser shadowR: offset (0.6d, d) in the darkest colour)
function lc1_shadow(S, sc, d) {
    var e = jzEffect(S, 'ADBE Drop Shadow', 'JZ Shadow'); if (!e) return;
    jzEP(e, 1, jzHex(jzDarkest(sc))); jzEP(e, 2, lc1_shA(sc) * 255); jzEP(e, 3, 149); jzEP(e, 4, d * 1.166); jzEP(e, 5, 0);
}

/* ---- text splitting (balanced chunks at natural boundaries; = splitK / brk in the browser pack) */
function lc1_bounds(word) { var out = {}, ch = jzChunk(word), acc = 0; for (var i = 0; i < ch.length; i++) { acc += jzChars(ch[i]).length; out[acc] = 1; } return out; }
function lc1_split2(word, force) {
    var ch = jzChars(word), n = ch.length, sb = lc1_bounds(word), best = Math.max(1, Math.floor(n / 2)), bs = -1e9;
    for (var c = 1; c < n; c++) {
        var a = ch[c - 1], b = ch[c];
        if (!force && ((c === 1 && !jzIsKanji(a)) || (c === n - 1 && !jzIsKanji(b)))) continue;
        var s = -Math.abs(c - n / 2) * 0.9;
        if (a === ' ' || a === '\u3000' || (jzIsPunct(a) && a !== '\u30FC')) s += 5;
        if (jzIsHira(a) && !jzIsHira(b) && !lc1_isBad(b)) s += 3;
        if (sb[c]) s += 2;
        if (lc1_isBad(b)) s -= 6;
        if (jzIsKanji(a) && jzIsKanji(b)) s -= 2;
        if (jzIsKanji(a) && jzIsHira(b)) s -= sb[c] ? 0.5 : 2.5;
        if (jzIsHira(a) && jzIsHira(b) && !sb[c]) s -= 2.5;
        if (s > bs) { bs = s; best = c; }
    }
    if (!force && bs < -1.5 && !jzHasLatin(word)) return [word];
    var out = [], p1 = jzTrim(ch.slice(0, best).join('')), p2 = jzTrim(ch.slice(best).join(''));
    if (p1) out.push(p1);
    if (p2) out.push(p2);
    return out;
}
function lc1_splitK(text, k, force) {
    var t = jzTrim(String(text || '')), i;
    if (!t) return [''];
    var latin = jzHasLatin(t), pcs = t.split(/\s+/), words = [], sp = [];
    for (var pi = 0; pi < pcs.length; pi++) {
        var raw = latin ? [pcs[pi]] : jzChunk(pcs[pi]);
        for (i = 0; i < raw.length; i++) { var w0 = jzTrim(raw[i]); if (w0) { words.push(w0); sp.push(false); } }
        if (!latin && sp.length) sp[sp.length - 1] = pi < pcs.length - 1;
    }
    if (!words.length) { words = [t]; sp = [false]; }
    k = Math.max(1, Math.min(k, jzCount(t)));
    var hard = {}, parts, guard;
    for (guard = 0; words.length < k && guard < 16; guard++) {
        var bi = -1, bl = 1;
        for (i = 0; i < words.length; i++) { var l = jzCount(words[i]); if (l > bl && !hard[words[i]]) { bl = l; bi = i; } }
        if (bi < 0) break;
        parts = latin ? [words[bi]] : lc1_split2(words[bi]);
        if (parts.length < 2 && force && words.length < force) parts = lc1_split2(words[bi], true);
        if (parts.length < 2) { hard[words[bi]] = 1; continue; }
        sp.splice(bi, 1, false, sp[bi]); words.splice(bi, 1, parts[0], parts[1]);
    }
    var sep = latin ? ' ' : '';
    if (k <= 1) return [words.join(sep)];
    if (words.length <= k) return words;
    function part(ws) {
        var pre = [0], n = ws.length, j;
        for (j = 0; j < n; j++) pre.push(pre[j] + jzCount(ws[j]) + 0.5);
        var tgt = pre[n] / k, best = null, bestS = 1e18, cnt = 0;
        function rec(start, g, cuts) {
            if (++cnt > 5000) return;
            if (g === k - 1) {
                var s = 0, prev = 0, all = cuts.concat([n]);
                for (var q = 0; q < all.length; q++) { var d = pre[all[q]] - pre[prev] - tgt; s += d * d; prev = all[q]; if (q < all.length - 1 && sp[all[q] - 1]) s -= tgt * tgt * 0.25; }
                if (s < bestS) { bestS = s; best = all; }
                return;
            }
            for (var c = start + 1; c <= n - (k - 1 - g); c++) rec(c, g + 1, cuts.concat([c]));
        }
        rec(0, 0, []);
        var out = [], prv = 0;
        if (!best) { for (j = 0; j < n; j++) out.push([ws[j]]); return out; }
        for (j = 0; j < best.length; j++) { out.push(ws.slice(prv, best[j])); prv = best[j]; }
        return out;
    }
    var groups = part(words);
    for (var it = 0; it < 3; it++) {
        var mx = -1, mn = 1e9, gi = 0;
        for (i = 0; i < groups.length; i++) { var sum = 0; for (var q2 = 0; q2 < groups[i].length; q2++) sum += jzCount(groups[i][q2]); if (sum > mx) { mx = sum; gi = i; } if (sum < mn) mn = sum; }
        if (mx <= mn * 1.7 + 1) break;
        var g0 = groups[gi], bw = null;
        for (i = 0; i < g0.length; i++) { if (!hard[g0[i]] && jzCount(g0[i]) >= 3 && (!bw || jzCount(g0[i]) > jzCount(bw))) bw = g0[i]; }
        if (!bw) break;
        parts = latin ? [bw] : lc1_split2(bw);
        if (parts.length < 2) { hard[bw] = 1; continue; }
        var wi = jzIndexOf(words, bw);
        sp.splice(wi, 1, false, sp[wi]); words.splice(wi, 1, parts[0], parts[1]);
        groups = part(words);
    }
    var res = [];
    for (i = 0; i < groups.length; i++) res.push(groups[i].join(sep));
    return res;
}
function lc1_brk(text, maxPer) { var t = jzTrim(String(text || '')), n = jzCount(t); if (n <= maxPer) return t; return lc1_splitK(t, Math.ceil(n / maxPer)).join('\r'); }
function lc1_charUnits(text) { var a = jzChars(jzStrip(text)), out = []; for (var i = 0; i < a.length; i++) { if (out.length && lc1_isBad(a[i])) out[out.length - 1] += a[i]; else out.push(a[i]); } return out; }
function lc1_chunks(ctx) { var ch = jzP(ctx, 'chunks', null), out = [], i; if (ch && ch.length) { for (i = 0; i < ch.length; i++) if (ch[i]) out.push(String(ch[i])); } if (!out.length) out.push(jzTrim(ctx.cut.text)); return out; }
function lc1_lines(s) { return String(s).split(/\r\n|\r|\n/); }

/* ---- text layers & measuring */
// o: font, size, color, x, y, align ('left' -> x = left edge, 'right' -> right edge, else centre), track, lead (em), fill, stroke,
//    strokeColor, opacity, rot, name; y = vertical centre of the block
function lc1_T(ctx, str, o) {
    var s = String(str).replace(/\r\n|\n/g, '\r'), multi = /\r/.test(s), size = o.size || 100;
    var L = jzText(ctx, s, { font: o.font, size: size, color: o.color || ctx.sc.fg, x: o.x || 0, y: o.y || 0, align: o.align, track: o.track || 0,
        leading: multi ? size * (o.lead || 1.2) : null, fill: o.fill, stroke: o.stroke, strokeColor: o.strokeColor, opacity: o.opacity, rot: o.rot, name: o.name });
    if (!o.lyric) jzNoGhost(L);          // secondary copy: the browser draws it with ghost off
    return L;
}
function lc1_meas(ctx, str, font, size, o) {
    o = o || {};
    var L = lc1_T(ctx, str, { font: font, size: size, x: -9999, y: -9999, align: 'left', track: o.track, lead: o.lead }), r = jzRect(L);
    L.remove();
    return { w: Math.max(1, r.width), h: Math.max(1, r.height) };
}
function lc1_fitSize(ctx, str, font, aw, ah, o) { var m = lc1_meas(ctx, str, font, 100, o); return 100 * Math.min(aw / m.w, ah / m.h); }
// vertical block (columns right -> left): size that fits aw x ah; lines = columns
function lc1_vfit(lines, aw, ah, o) {
    var lead = o.lead || 1.2, tr = o.track || 0, mr = 1;
    for (var i = 0; i < lines.length; i++) mr = Math.max(mr, jzCount(lines[i]));
    return Math.min(aw / (1 + (lines.length - 1) * lead), ah / (mr * (1 + tr) - tr));
}
function lc1_vmeas(lines, size, o) {
    var lead = o.lead || 1.2, tr = o.track || 0, mr = 1;
    for (var i = 0; i < lines.length; i++) mr = Math.max(mr, jzCount(lines[i]));
    return { w: size * (1 + (lines.length - 1) * lead), h: size * (mr * (1 + tr) - tr) };
}
// best line break + size for a text block that must fit aw x ah (browser fitBlock); returns { text ('\r' breaks), size, lines }
function lc1_fitBlock(ctx, text, font, aw, ah, o, maxLines) {
    o = o || {};
    var t = jzTrim(String(text || '')), n = Math.max(1, jzCount(t)), best = null, seen = {}, L, i;
    var cands = [];
    for (L = 1; L <= Math.min(maxLines, n); L++) {
        var s0 = L === 1 ? t : lc1_brk(t, Math.ceil(n / L));
        cands.push(s0);
        // the browser's word segmenter can break inside words the panel's chunker keeps whole: also try a forced split
        if (L > 1 && lc1_lines(s0).length < L && !jzHasLatin(t)) cands.push(lc1_splitK(t, L, L).join('\r'));
    }
    for (var ci = 0; ci < cands.length; ci++) {
        var s = cands[ci];
        if (seen[s]) continue; seen[s] = 1;
        var ls = lc1_lines(s), nl = ls.length;
        if (nl > maxLines) continue;
        var size = o.vertical ? lc1_vfit(ls, aw, ah, o) : lc1_fitSize(ctx, s, font, aw, ah, o);
        var lone = false;
        if (nl > 1 && n > 2) for (i = 0; i < nl; i++) if (jzCount(ls[i]) < 2) lone = true;
        var score = size * (1 - 0.06 * (nl - 1)) * (lone ? 0.8 : 1);
        if (!best || score > best.score) best = { text: s, size: size, lines: nl, score: score };
    }
    return best || { text: t, size: o.vertical ? lc1_vfit([t], aw, ah, o) : lc1_fitSize(ctx, t, font, aw, ah, o), lines: 1, score: 0 };
}
// block size as the browser lays it out: w measured in AE, h = lines x lead
function lc1_blockM(L, str, size, lead) { var n = lc1_lines(str).length; return { w: jzRect(L).width, h: size * (1 + (n - 1) * (lead || 1.2)) }; }
// the lyric (or a part of it): text + the cut's motion; o as lc1_T plus mi, noHold, treat (false = plain)
function lc1_main(ctx, str, o) { o.lyric = true; var L = lc1_T(ctx, str, o); jzAnimate(ctx, L, { mi: o.mi || 0, noHold: o.noHold, treat: o.treat }); return L; }

/* ---- glyph layout estimate (browser layoutText): full-width glyphs = 1 em */
function lc1_adv(ch) {
    var c = ch.charCodeAt(0);
    if (ch === ' ') return 0.3;
    if (c < 0x80) {
        if (/[A-Z]/.test(ch)) return /[MW]/.test(ch) ? 0.9 : (ch === 'I' ? 0.32 : 0.7);
        if (/[a-z]/.test(ch)) return /[mw]/.test(ch) ? 0.88 : /[ijl]/.test(ch) ? 0.28 : /[frt]/.test(ch) ? 0.42 : 0.61;
        if (/[0-9]/.test(ch)) return 0.62;
        return /[.,:;!'|]/.test(ch) ? 0.28 : 0.42;
    }
    if (c < 0x250) return 0.56;
    if (c >= 0xFF61 && c <= 0xFF9F) return 0.5;
    return 1;
}
// glyph centres (spaces skipped) relative to the anchor: x from the left edge (align left) / centre, y from the block centre;
// kx rescales x so the widest line matches the AE layer's measured width
function lc1_gpts(str, size, o) {
    o = o || {};
    var lines = lc1_lines(str), nL = lines.length, lead = (o.lead || 1.2) * size, tr = (o.track || 0) * size, out = [], raw = [], maxW = 1, li, j;
    for (li = 0; li < nL; li++) {
        var a = jzChars(lines[li]), ws = [], w = 0;
        for (j = 0; j < a.length; j++) { ws.push(lc1_adv(a[j]) * size); w += ws[j] + (j < a.length - 1 ? tr : 0); }
        maxW = Math.max(maxW, w); raw.push({ a: a, ws: ws, w: w });
    }
    var kx = o.w ? o.w / maxW : 1;
    for (li = 0; li < nL; li++) {
        var R = raw[li], x = o.align === 'left' ? 0 : o.align === 'right' ? -R.w : -R.w / 2, y = (li - (nL - 1) / 2) * lead;
        for (j = 0; j < R.a.length; j++) {
            if (R.a[j] !== ' ' && R.a[j] !== '\u3000') out.push({ ch: R.a[j], x: (x + R.ws[j] / 2) * kx, y: y, w: R.ws[j] * kx, h: size, li: li, i: out.length });
            x += R.ws[j] + tr;
        }
    }
    return out;
}

/* ---- vertical text: glyphs of ONE text layer placed in columns (right -> left); one glyph per line + per-glyph offsets */
function lc1_cells(ctx, gl, cells, o) {
    var size = o.size, k;
    var L = jzText(ctx, gl.join('\r'), { font: o.font, size: size, color: o.color || ctx.sc.fg, x: 0, y: 0, leading: size, name: o.name, fill: o.fill, stroke: o.stroke, strokeColor: o.strokeColor });
    var ox = cells[0][0], oy = cells[0][1] + LC1_CY * size;
    jzXf(L, 'ADBE Anchor Point').setValue([o.px - ox, o.py - oy]);
    jzXf(L, 'ADBE Position').setValue([o.px, o.py]);
    var offs = [], rots = [], anyO = false, anyR = false;
    for (k = 0; k < gl.length; k++) {
        var dx = cells[k][0] - ox, dy = cells[k][1] - (oy + k * size - LC1_CY * size);
        offs.push([dx, dy]); rots.push(cells[k][2] || 0);
        if (Math.abs(dx) + Math.abs(dy) > 0.05) anyO = true;
        if (cells[k][2]) anyR = true;
    }
    if (anyO) jzCharOffsets(L, offs, 'JZ Columns');
    if (anyR) jzCharRotations(L, rots, 'JZ Upright');
    if (o.opacity != null) jzXf(L, 'ADBE Opacity').setValue(o.opacity * 100);
    if (!o.lyric) jzNoGhost(L);
    return L;
}
// o: font, size, color, x (block centre), top (top edge), lead (column pitch, em), track (glyph gap, em) -> { L, bb }
function lc1_vcols(ctx, lines, o) {
    var size = o.size, lead = o.lead || 1.2, st = size * (1 + (o.track || 0)), cols = lines.length, gl = [], cells = [], i, j, maxN = 1;
    for (i = 0; i < cols; i++) {
        var ch = jzChars(lc1_vtext(lines[i])), cx = o.x + ((cols - 1) / 2 - i) * lead * size;
        maxN = Math.max(maxN, ch.length);
        for (j = 0; j < ch.length; j++) {
            var cc = ch[j], vx = 0, vy = 0, rot = 0;
            if (cc === ' ') continue;
            if (lc1_isSmall(cc)) { vx = 0.11 * size; vy = -0.11 * size; }
            if (LC1_VPUNCT.indexOf(cc) >= 0) { vx = 0.3 * size; vy = -0.3 * size; }
            if (LC1_VROT.indexOf(cc) >= 0 || /[A-Za-z0-9]/.test(cc)) rot = 90;
            gl.push(cc); cells.push([cx + vx, o.top + size / 2 + j * st + vy, rot]);
        }
    }
    if (!gl.length) { gl.push(' '); cells.push([o.x, o.top + size / 2, 0]); }
    var bw = size * (1 + (cols - 1) * lead), bh = maxN * st - (st - size);
    var L = lc1_cells(ctx, gl, cells, { font: o.font, size: size, color: o.color, name: o.name, px: o.x, py: o.top + bh / 2, fill: o.fill, stroke: o.stroke, strokeColor: o.strokeColor, opacity: o.opacity, lyric: o.lyric });
    if (o.rot) jzXf(L, 'ADBE Rotate Z').setValue(o.rot);
    return { L: L, bb: lc1_box(o.x - bw / 2, o.top, o.x + bw / 2, o.top + bh), w: bw, h: bh };
}

/* ---- expression-driven secondary layers */
function lc1_e(d, len, ease) { return (ease || 'oe') + '((time-' + jzN(d) + ')/' + jzN(Math.max(0.01, len)) + ')'; }   // browser tin(env, d, len, ease)
function lc1_op(ctx, L, expr, alpha, head) {
    if (alpha != null) jzXf(L, 'ADBE Opacity').setValue(jzClamp(alpha, 0, 1) * 100);
    jzSetExpr(jzXf(L, 'ADBE Opacity'), lc1_H(ctx) + (head || '') + 'value*Math.max(0,Math.min(1,' + expr + '))');
}
// fade in (outCubic by default) and follow the cut's exit
function lc1_fade(ctx, L, d, len, alpha, ease) { lc1_op(ctx, L, (ease || 'oc') + '((time-' + jzN(d) + ')/' + jzN(len || 0.4) + ')*K', alpha); }
// fade + slide in from (dx, dy)
function lc1_slide(ctx, L, d, len, dx, dy, alpha) {
    jzSetExpr(jzXf(L, 'ADBE Position'), lc1_H(ctx) + 'var a=oc((time-' + jzN(d) + ')/' + jzN(len) + ');[value[0]+' + jzN(dx) + '*(1-a),value[1]+' + jzN(dy) + '*(1-a)]');
    lc1_fade(ctx, L, d, len, alpha);
}
// AE draws the first group of a shape layer on top: bring a new group to the front so groups stack in creation order
// (moveTo invalidates the reference, so the fresh one is returned)
function lc1_front(g) { var par = g.parentProperty; if (g.propertyIndex > 1) { g.moveTo(1); return par.property(1); } return g; }
// every shape / null / secondary text layer of this pack is main-pass-only in the browser (ghost off): jzNoGhost
function lc1_S(ctx, name, x, y) { return jzNoGhost(jzShapeLayer(ctx, name, x || 0, y || 0)); }
function lc1_sub(g, name) { var q = jzVecs(g).addProperty('ADBE Vector Group'); if (name) q.name = name; return q; }
function lc1_rect(S, name, w, h, x, y, fill, o) {
    o = o || {};
    var g = jzGrp(S, name); jzAddRect(g, Math.max(0.5, w), Math.max(0.5, h), o.round || 0, x || 0, y || 0);
    if (o.stroke) jzAddStroke(g, o.stroke, o.sw || 2, o.sop);
    if (fill) jzAddFill(g, fill, o.fop);
    if (o.op != null) jzGX(g).property('ADBE Vector Group Opacity').setValue(o.op * 100);
    return lc1_front(g);
}
function lc1_ellipse(S, name, w, h, x, y, fill, o) {
    o = o || {};
    var g = jzGrp(S, name); jzAddEllipse(g, Math.max(0.5, w), Math.max(0.5, h), x || 0, y || 0);
    if (o.stroke) jzAddStroke(g, o.stroke, o.sw || 2, o.sop);
    if (fill) jzAddFill(g, fill, o.fop);
    if (o.op != null) jzGX(g).property('ADBE Vector Group Opacity').setValue(o.op * 100);
    return lc1_front(g);
}
function lc1_path(S, name, pts, col, w, o) {
    o = o || {};
    var g = jzGrp(S, name); jzAddPath(g, pts, !!o.closed);
    if (o.fill) jzAddFill(g, o.fill, o.fop);
    if (col) { var st = jzAddStroke(g, col, w || 2, o.sop); if (o.cap) try { st.property('ADBE Vector Stroke Line Cap').setValue(o.cap); } catch (e) {} if (o.join) try { st.property('ADBE Vector Stroke Line Join').setValue(o.join); } catch (e2) {} if (o.dash) lc1_dash(st, o.dash[0], o.dash[1]); }
    if (o.op != null) jzGX(g).property('ADBE Vector Group Opacity').setValue(o.op * 100);
    return lc1_front(g);
}
// (each dash entry is added AND set before the next one is added: in AE adding to the Dashes group invalidates the
//  references already held to its other entries)
function lc1_dashP(D, mn, v) {
    var p = null;
    try { p = D.addProperty(mn); } catch (e) { p = null; }
    if (!p) { try { p = D.property(mn); } catch (e2) { p = null; } }
    if (p) p.setValue(v);
}
function lc1_dash(st, d, gp) {
    var D = st.property('ADBE Vector Stroke Dashes');
    lc1_dashP(D, 'ADBE Vector Stroke Dash 1', d);
    lc1_dashP(D, 'ADBE Vector Stroke Gap 1', gp);
}
function lc1_trim(ctx, g, eExpr, sExpr, head) {
    var t = jzVecs(g).addProperty('ADBE Vector Filter - Trim');
    jzSetExpr(t.property('ADBE Vector Trim End'), lc1_H(ctx) + (head || '') + '100*Math.max(0,Math.min(1,' + eExpr + '))');
    if (sExpr) jzSetExpr(t.property('ADBE Vector Trim Start'), lc1_H(ctx) + (head || '') + '100*Math.max(0,Math.min(1,' + sExpr + '))');
    return t;
}
function lc1_gOp(ctx, g, expr, head) { jzSetExpr(jzGX(g).property('ADBE Vector Group Opacity'), lc1_H(ctx) + (head || '') + 'value*Math.max(0,Math.min(1,' + expr + '))'); }
function lc1_gSc(ctx, g, sxE, syE, head) { jzSetExpr(jzGX(g).property('ADBE Vector Scale'), lc1_H(ctx) + (head || '') + '[value[0]*(' + sxE + '),value[1]*(' + (syE || sxE) + ')]'); }
function lc1_gRot(ctx, g, expr, head) { jzSetExpr(jzGX(g).property('ADBE Vector Rotation'), lc1_H(ctx) + (head || '') + 'value+(' + expr + ')'); }
// line from pts[0] along pts drawn progressively with eExpr (0..1); alpha = opacity
function lc1_line(ctx, name, pts, col, w, alpha, eExpr, o) {
    var S = lc1_S(ctx, name, 0, 0), g = lc1_path(S, name, pts, col, w, o);
    if (eExpr) lc1_trim(ctx, g, eExpr, null, o && o.head);
    if (alpha != null && alpha < 1) jzXf(S, 'ADBE Opacity').setValue(Math.max(0, alpha) * 100);
    return S;
}
// bar from x0 towards dir (+1 right / -1 left) of width w, height h centred on y; grows with eExpr
function lc1_bar(ctx, name, x0, y, w, h, col, eExpr, dir, alpha, head) {
    dir = dir || 1;
    var S = lc1_S(ctx, name, x0, y); lc1_rect(S, name, w, h, dir * w / 2, 0, col);
    if (alpha != null && alpha < 1) jzXf(S, 'ADBE Opacity').setValue(alpha * 100);
    jzSetExpr(jzXf(S, 'ADBE Scale'), lc1_H(ctx) + (head || '') + 'var e=Math.max(0,' + eExpr + ');[value[0]*e,value[1]]');
    return S;
}
// vertical bar from y0 downwards (dir 1) / upwards (-1)
function lc1_vbar(ctx, name, x, y0, w, h, col, eExpr, dir, alpha, head) {
    dir = dir || 1;
    var S = lc1_S(ctx, name, x, y0); lc1_rect(S, name, w, h, 0, dir * h / 2, col);
    if (alpha != null && alpha < 1) jzXf(S, 'ADBE Opacity').setValue(alpha * 100);
    jzSetExpr(jzXf(S, 'ADBE Scale'), lc1_H(ctx) + (head || '') + 'var e=Math.max(0,' + eExpr + ');[value[0],value[1]*e]');
    return S;
}
// dotted leader (round dots of radius r every `step` px) from p0 to p1, grows with eExpr
function lc1_leader(ctx, name, p0, p1, col, alpha, step, r, eExpr) {
    return lc1_line(ctx, name, [p0, p1], col, r * 2, alpha, eExpr, { cap: 2, dash: [0.01, step] });
}
// parent a layer to P and place it at (x, y) in P's space (P's anchor is its origin)
function lc1_parent(L, P, x, y) { L.setParentWithJump(P); jzXf(L, 'ADBE Position').setValue([x, y]); }
function lc1_null(ctx, name, x, y) {
    var N = ctx.comp.layers.addNull(ctx.comp.duration); N.name = name;
    jzXf(N, 'ADBE Anchor Point').setValue([0, 0]); jzXf(N, 'ADBE Position').setValue([x, y]);
    return jzNoGhost(N);
}
function lc1_matte(L, M) {
    var ok = false;
    if (typeof L.setTrackMatte === 'function') { try { L.setTrackMatte(M, TrackMatteType.ALPHA); ok = true; } catch (e) { ok = false; } }
    if (!ok) { try { L.trackMatteType = TrackMatteType.ALPHA; } catch (e2) { jzWarn('matte: ' + e2.toString()); } }
}
// rows of real body copy (browser bodyRows): row strings of ~cpr glyphs, short paragraph ends at random
function lc1_bodyRows(src, w, fs, rows, seed) {
    var ch = jzChars(src), out = [], r, j;
    if (!ch.length || rows <= 0) return out;
    var lat = jzHasLatin(src), cpr = Math.max(2, Math.floor(w / (fs * (lat ? 0.56 : 1.04)))), off = (seed | 0) % ch.length;
    for (r = 0; r < rows && r < 80; r++) {
        var last = r === rows - 1 || jzR(seed, r, 5) < 0.14, m = last ? Math.max(2, Math.floor(cpr * (0.3 + 0.5 * jzR(seed, r, 6)))) : cpr, row = '';
        for (j = 0; j < m; j++) row += ch[(off + j) % ch.length];
        off += m; out.push(row);
    }
    return out;
}
// one left-aligned text layer holding body rows; first row centred on y; rows appear with rvExpr (0..1, row-wise like the browser)
function lc1_rowsText(ctx, rows, o) {
    if (!rows.length) return null;
    var fs = o.size, lat = jzHasLatin(rows.join('')), cpr = Math.max(2, Math.floor(o.w / (fs * (lat ? 0.56 : 1.04))));
    var tr = lat ? 0.02 : jzClamp((o.w / cpr - fs) / fs, 0, 0.2);
    var L = lc1_T(ctx, rows.join('\r'), { font: o.font, size: fs, color: o.color, x: o.x, y: o.y + (rows.length - 1) * o.lh / 2, align: 'left', track: tr, lead: o.lh / fs, name: o.name || 'body copy', opacity: o.alpha });
    if (o.rv) {
        var C = [], acc = 0;
        for (var i = 0; i < rows.length; i++) { acc += jzChars(rows[i]).length; C.push(acc); }
        jzAnimator(L, 'JZ Rows', [['ADBE Text Opacity', 0]], lc1_H(ctx) + (o.head || '') + 'var C=' + jzArrExpr(C) + ',NR=' + C.length + ';var i=textIndex-1,r=0;while(r<NR-1&&i>=C[r])r++;var rv=' + o.rv + ';(1-cl(rv*NR*1.2-r))*100');
    }
    return L;
}
function lc1_shp(g, sh) { var p = jzVecs(g).addProperty('ADBE Vector Shape - Group'); p.property('ADBE Vector Shape').setValue(sh); return p; }
// circular arc (degrees, screen angles a0 -> a1) as an open bezier Shape
function lc1_arcShape(cx, cy, r, a0, a1) {
    var segs = Math.max(1, Math.ceil(Math.abs(a1 - a0) / 90)), da = (a1 - a0) / segs, k = 4 / 3 * Math.tan(da * Math.PI / 720) * r, V = [], I = [], O = [];
    for (var i = 0; i <= segs; i++) {
        var a = (a0 + da * i) * Math.PI / 180, c = Math.cos(a), s = Math.sin(a);
        V.push([cx + c * r, cy + s * r]); I.push(i ? [s * k, -c * k] : [0, 0]); O.push(i < segs ? [-s * k, c * k] : [0, 0]);
    }
    var sh = new Shape(); sh.vertices = V; sh.inTangents = I; sh.outTangents = O; sh.closed = false;
    return sh;
}
// text colour switch for the "current" row (contents): CUR is defined in head
function lc1_curCol(ctx, L, hex, i, head) { return jzAnimator(L, 'JZ Current', [['ADBE Text Fill Color', jzHex(hex)]], lc1_H(ctx) + head + '(CUR==' + i + ')?100:0'); }
function lc1_strokeOf(g) { return jzVecs(g).property('ADBE Vector Graphic - Stroke'); }
function lc1_fillOf(g) { return jzVecs(g).property('ADBE Vector Graphic - Fill'); }
function lc1_colExpr(a, b, cond) { var A = jzHex(a), B = jzHex(b); return cond + '?[' + jzN(B[0]) + ',' + jzN(B[1]) + ',' + jzN(B[2]) + ',1]:[' + jzN(A[0]) + ',' + jzN(A[1]) + ',' + jzN(A[2]) + ',1]'; }
function lc1_mixExpr(a, b, t) { var A = jzHex(a), B = jzHex(b); return '[' + jzN(A[0]) + '+' + jzN(B[0] - A[0]) + '*' + t + ',' + jzN(A[1]) + '+' + jzN(B[1] - A[1]) + '*' + t + ',' + jzN(A[2]) + '+' + jzN(B[2] - A[2]) + '*' + t + ',1]'; }

/* ================================================================== 1 magazine \u2014 \u898B\u958B\u304D */
jzReg('layout', 'magazine', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif', 'serif'])), qf: rng.pick(jzFontsOf(st, ['serif'])), variant: rng.pick(['headline', 'vertical', 'plate']), folio: 2 * rng.int(6, 90),
            kicker: rng.pick(['FEATURE', 'ESSAY', 'INTERVIEW', 'COLUMN', 'STORY']), seed: rng.int(1, 9999), img: rng.pick(['sun', 'bars', 'arc']) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), k = lc1_k(ctx), port = jzPortrait(ctx), C = lc1_card(sc), i;
        var font = jzP(ctx, 'font', jzSerifF(ctx)), qf = jzP(ctx, 'qf', jzSerifF(ctx)), variant = jzP(ctx, 'variant', 'headline'), folio = jzP(ctx, 'folio', 24), kicker = jzP(ctx, 'kicker', 'FEATURE');
        var seed = jzP(ctx, 'seed', 7), img = jzP(ctx, 'img', 'sun'), mono = jzMonoF(ctx), t0 = jzTrim(c.text), latin = jzHasLatin(c.text);
        var pw, ph;
        if (!port) { ph = Math.min(H * 0.84, W * 0.44 / 0.72); pw = ph * 0.72; } else { pw = W * 0.88; ph = Math.min(H * 0.445, pw * 1.02); }
        var cx = W / 2, cy = H / 2, vert = variant === 'vertical' && !latin, plate = variant === 'plate';
        var Ax = port ? cx - pw / 2 : vert ? cx : cx - pw, Ay = port ? cy - ph : cy - ph / 2, sideB = port ? 1 : vert ? -1 : 1;
        var ls = jzClamp(pw * 0.03, 11 * k, 26 * k), mx = pw * 0.1, my = ph * 0.075, lw = Math.max(1, u * 0.0014);
        var HM = 'var OUT=1-cl((PO-0.72)/0.28),AA=oc(time/0.28)*OUT,UF=ioc((time-0.06)/0.5),FO=ioc(PO),Q=UF*(1-2*FO),CA=AA*cl((Q-0.3)/0.5);';
        var gx = port ? Ax : cx, gy = port ? cy : Ay;
        var scQ = port ? '[value[0],value[1]*Q]' : '[value[0]*Q,value[1]]';
        // ---- page A
        var PA = lc1_S(ctx, 'page A', Ax, Ay);
        lc1_rect(PA, 'page', pw, ph, pw / 2, ph / 2, C.fill, C.edge ? { stroke: C.line, sw: lw, sop: 70 } : null);
        lc1_shadow(PA, sc, u * 0.016);
        lc1_op(ctx, PA, 'AA', 1, HM);
        // ---- page B (unfolds from the gutter; content rides on it)
        function bx(xp) { return (!port && sideB < 0) ? xp - pw : xp; }
        var PB = lc1_S(ctx, 'page B', gx, gy);
        lc1_rect(PB, 'page', pw, ph, bx(pw / 2), ph / 2, C.fill, C.edge ? { stroke: C.line, sw: lw, sop: 70 } : null);
        lc1_shadow(PB, sc, u * 0.016);
        jzSetExpr(jzXf(PB, 'ADBE Scale'), lc1_H(ctx) + HM + scQ);
        lc1_op(ctx, PB, 'Q>0.002?AA:0', 1, HM);
        var bX = mx, bY = my, iw = pw - mx * 2, ih = ph - my * 2, imgH = ih * (plate ? 0.3 : 0.46);
        var imgC = lc1_plateCol(sc, [img === 'sun' ? jzDarkest(sc) : sc.accent, sc.ink, sc.fg], C.fill, 1.8), iy = plate ? bY + ih - imgH : bY;
        var oc2 = lc1_plateCol(sc, [sc.accent, sc.accent2, C.fill], imgC, 1.6);
        var IM = lc1_S(ctx, 'page B picture', 0, 0);
        lc1_rect(IM, 'picture', iw, imgH, bx(bX + iw / 2), iy + imgH / 2, imgC);
        if (img === 'sun') { var sr = Math.min(iw, imgH) * 0.3; lc1_ellipse(IM, 'sun', sr * 2, sr * 2, bx(bX + iw * 0.62), iy + imgH * 0.52, oc2); }
        else if (img === 'bars') for (i = 0; i < 5; i++) { var bh0 = imgH * 0.75 * (1 - 0.5 * jzR(seed, i, 9)), by0 = iy + imgH * (0.25 + 0.5 * jzR(seed, i, 9)); lc1_rect(IM, 'bar ' + i, iw * 0.08, bh0, bx(bX + iw * (0.12 + i * 0.16) + iw * 0.04), by0 + bh0 / 2, oc2); }
        else { var ga = jzGrp(IM, 'arc'); lc1_shp(ga, lc1_arcShape(bx(bX + iw * 0.5), iy + imgH * 1.02, imgH * 0.7, 180, 360)); jzAddStroke(ga, oc2, Math.max(3 * k, imgH * 0.09)); lc1_front(ga); }
        lc1_parent(IM, PB, 0, 0); lc1_op(ctx, IM, 'CA', 1, HM);
        var fs = jzClamp(pw * 0.022, 9 * k, 20 * k), lh = fs * 1.75, src = jzFlat(c.lineText || c.text) + (latin ? '. ' : '\u3002'), colW = (iw - fs * 1.5) / 2;
        var ty = plate ? bY + ls * 2 : iy + imgH + fs * 2, tAvail = plate ? ih - imgH - ls * 3 : ih - imgH - fs * 2, qc = lc1_deck(ctx), r0 = ty, rows;
        var bRows = [];
        if (plate && qc) {
            var qb = lc1_fitBlock(ctx, '\u300C' + qc + '\u300D', qf, iw, tAvail * 0.42, { lead: 1.3 }, 3), qs = Math.min(qb.size, pw * 0.07);
            var QT = lc1_T(ctx, qb.text, { font: qf, size: qs, lead: 1.3, align: 'left', x: gx + bx(bX), y: gy + ty + qs * qb.lines * 0.65, color: C.acc, name: 'pull quote' });
            lc1_parent(QT, PB, bx(bX), ty + qs * qb.lines * 0.65); lc1_op(ctx, QT, 'CA', 1, HM); bRows.push(QT);
            r0 = ty + qs * (qb.lines * 1.3 + 0.8); rows = Math.max(0, Math.floor((bY + ih - imgH - fs - r0) / lh));
        } else rows = Math.max(0, Math.floor((plate ? ih - imgH - fs * 2 : tAvail) / lh));
        for (i = 0; i < 2; i++) {
            var BR = lc1_rowsText(ctx, lc1_bodyRows(src, colW, fs, rows, seed + i * 77), { font: jzBodyF(ctx), size: fs, w: colW, lh: lh, x: 0, y: 0, color: C.text, rv: 'UF', head: HM, name: 'body column ' + (i + 1) });
            if (!BR) continue;
            lc1_parent(BR, PB, bx(bX + i * (colW + fs * 1.5)), r0 + (rows - 1) * lh / 2);
            lc1_op(ctx, BR, 'CA*0.55', 1, HM);
        }
        var FB = lc1_T(ctx, lc1_pad3(folio + 1), { font: mono, size: ls * 0.85, align: 'right', color: C.text, name: 'folio B' });
        lc1_parent(FB, PB, bx(pw - mx * 0.5), ph - my * 0.45); lc1_op(ctx, FB, 'CA*0.8', 1, HM);
        var SH = lc1_S(ctx, 'page B shade', 0, 0); lc1_rect(SH, 'shade', pw, ph, bx(pw / 2), ph / 2, jzDarkest(sc));
        lc1_parent(SH, PB, 0, 0); lc1_op(ctx, SH, '(Q>0.002&&Q<0.98)?AA*(1-Q)*0.35:0', 1, HM);
        // ---- gutter shade
        var gw = pw * 0.07, GS = lc1_S(ctx, 'gutter', port ? Ax + pw / 2 : cx, port ? cy : Ay + ph / 2);
        lc1_rect(GS, 'gutter', port ? pw : gw * 0.9, port ? gw * 0.9 : ph, 0, 0, C.text);
        var gb = jzEffect(GS, 'ADBE Gaussian Blur 2', 'JZ Soft'); jzEP(gb, 1, gw * 0.55); jzEP(gb, 2, port ? 3 : 2);
        lc1_op(ctx, GS, 'AA*0.16', 1, HM);
        // ---- page A content
        var FA = 'oc((time-0.12)/0.4)*OUT', ax = Ax + mx, ay = Ay + my, aw = pw - mx * 2, ah = ph - my * 2, bb = null, L, T, size, fb;
        if (plate) {
            var plC = lc1_plateCol(sc, [jzDarkest(sc), sc.accent, sc.ink], C.fill, 2.2), tc = lc1_onCol(sc, plC);
            var PL = lc1_vbar(ctx, 'plate', Ax + pw / 2, Ay + ph, pw, ph, plC, 'ioc(time/0.45)*OUT', -1, null, HM);
            lc1_op(ctx, PL, 'AA', 1, HM);
            var BG = lc1_T(ctx, jzChars(jzStrip(t0))[0] || '', { font: font, size: ph * 0.7, x: Ax + pw * 0.62, y: Ay + ph * 0.36, color: tc, name: 'big initial' });
            lc1_op(ctx, BG, '0.07*ioc(time/0.45)*OUT', 1, HM);
            T = lc1_T(ctx, kicker + '  \u2014  No.' + jzLineNo(ctx), { font: mono, size: ls, track: 0.2, align: 'left', x: ax, y: ay + ls * 0.5, color: tc, name: 'kicker' }); lc1_op(ctx, T, FA, 1, HM);
            fb = lc1_fitBlock(ctx, t0, font, aw, ah * 0.5, { lead: 1.12, track: 0.02 }, 4); size = Math.min(fb.size, u * 0.15);
            var mh = size * (1 + (fb.lines - 1) * 1.12);
            L = lc1_main(ctx, fb.text, { font: font, size: size, x: ax, y: Ay + ph - my - ls * 2 - mh / 2, align: 'left', lead: 1.12, track: 0.02, color: tc, noHold: lc1_plateHold(ctx) });
            T = lc1_T(ctx, lc1_pad3(folio), { font: mono, size: ls * 0.85, align: 'left', x: Ax + mx * 0.5, y: Ay + ph - my * 0.45, color: tc, name: 'folio A' }); lc1_op(ctx, T, FA + '*0.8', 1, HM);
            bb = lc1_bb(L);
        } else if (vert) {
            fb = lc1_fitBlock(ctx, jzStrip(t0), font, aw * (port ? 0.6 : 0.55), ah * 0.86, { vertical: true, lead: 1.25, track: 0.04 }, 3);
            size = Math.min(fb.size, u * 0.15);
            var vl = lc1_lines(fb.text), vm = lc1_vmeas(vl, size, { lead: 1.25, track: 0.04 });
            var V = lc1_vcols(ctx, vl, { font: font, size: size, x: ax + aw - vm.w / 2, top: ay + ls * 2.2, lead: 1.25, track: 0.04, color: C.text, name: t0, lyric: true });
            jzAnimate(ctx, V.L, { mi: 0, noHold: lc1_plateHold(ctx) }); bb = V.bb;
            T = lc1_T(ctx, kicker, { font: mono, size: ls, track: 0.2, align: 'right', x: ax + aw, y: ay + ls * 0.5, color: C.acc, name: 'kicker' }); lc1_op(ctx, T, FA, 1, HM);
            var gw2 = aw - vm.w - size * 0.8, gh = ah * 0.55, vfs = jzClamp(pw * 0.024, 9 * k, 20 * k);
            if (gw2 > ls * 3) {
                var vch = jzChars(jzStrip(c.lineText || t0)), vc = [], vcs = [];
                for (i = 0; i < vch.length; i++) if (!jzIsLatin(vch[i])) vc.push(vch[i]);
                if (vc.length) {
                    var per = Math.max(2, Math.floor(gh / (vfs * 1.02))), ncol = Math.min(Math.floor(gw2 / (vfs * 1.7)), Math.max(1, Math.floor(260 / per))), off = seed % vc.length;
                    for (var cI = 0; cI < ncol; cI++) { var last = cI === ncol - 1 || jzR(seed, cI, 5) < 0.14, mm = last ? Math.max(2, Math.floor(per * (0.3 + 0.5 * jzR(seed, cI, 6)))) : per, s = ''; for (var j = 0; j < mm; j++) s += vc[(off + j) % vc.length]; off += mm; vcs.push(s); }
                    if (vcs.length) {
                        var VB = lc1_vcols(ctx, vcs, { font: jzBodyF(ctx), size: vfs, x: ax + gw2 - vcs.length * vfs * 1.7 / 2, top: ay + ah - gh, lead: 1.7, track: 0.02, color: C.text, name: 'vertical body' });
                        lc1_op(ctx, VB.L, FA + '*0.55', 1, HM);
                    }
                }
            }
            lc1_line(ctx, 'rule', [[ax, ay + ah - gh - ls], [ax + gw2, ay + ah - gh - ls]], C.acc, Math.max(2 * k, lw * 2), null, lc1_e(0.12, 0.4, 'oc') + '*OUT', { head: HM });
            T = lc1_T(ctx, lc1_pad3(folio), { font: mono, size: ls * 0.85, align: 'right', x: Ax + pw - mx * 0.5, y: Ay + ph - my * 0.45, color: C.text, name: 'folio A' }); lc1_op(ctx, T, FA + '*0.8', 1, HM);
        } else {
            var SQ = lc1_S(ctx, 'kicker mark', ax + ls * 0.4, ay + ls * 0.5); lc1_rect(SQ, 'mark', ls * 0.8, ls * 0.8, 0, 0, C.acc); lc1_op(ctx, SQ, FA, 1, HM);
            T = lc1_T(ctx, kicker + '  No.' + jzLineNo(ctx), { font: mono, size: ls, track: 0.2, align: 'left', x: ax + ls * 1.4, y: ay + ls * 0.5, color: C.text, name: 'kicker' }); lc1_op(ctx, T, FA, 1, HM);
            fb = lc1_fitBlock(ctx, t0, font, aw, ah * 0.52, { lead: 1.1, track: 0.01 }, 4); size = Math.min(fb.size, u * 0.15);
            var mh2 = size * (1 + (fb.lines - 1) * 1.1), hy = ay + ls * 2.4 + mh2 / 2;
            L = lc1_main(ctx, fb.text, { font: font, size: size, x: ax, y: hy, align: 'left', lead: 1.1, track: 0.01, color: C.text, noHold: lc1_plateHold(ctx) });
            bb = lc1_bb(L);
            var ry = hy + mh2 / 2 + size * 0.35;
            var RL = lc1_line(ctx, 'rule', [[ax, ry], [ax + aw, ry]], C.text, Math.max(2 * k, lw * 2), null, lc1_e(0.15, 0.6, 'ioc') + '*OUT', { head: HM });
            lc1_op(ctx, RL, '0.9*AA', 1, HM);
            var deck = lc1_deck(ctx) || lc1_meta(ctx), ds = jzClamp(pw * 0.034, 11 * k, 30 * k), dper = Math.max(4, Math.floor(aw / (ds * 1.05)));
            var dl = lc1_lines(jzSplitLines(deck, dper)).slice(0, 3);
            T = lc1_T(ctx, dl.join('\r'), { font: jzSerifF(ctx), size: ds, lead: 1.55, align: 'left', x: ax, y: ry + ds * 1.2 + (dl.length - 1) * ds * 0.78, color: C.text, name: 'deck' }); lc1_op(ctx, T, FA + '*0.75', 1, HM);
            T = lc1_T(ctx, lc1_pad3(folio) + '   ' + lc1_cut(jzRomajiOf(ctx) || 'JIZURA', 18), { font: mono, size: ls * 0.85, track: 0.1, align: 'left', x: Ax + mx * 0.5, y: Ay + ph - my * 0.45, color: C.text, name: 'folio A' }); lc1_op(ctx, T, FA + '*0.8', 1, HM);
        }
        // ---- exit: page B folds over page A (blank back of the page)
        var PF = lc1_S(ctx, 'page B (folding)', gx, gy);
        lc1_rect(PF, 'page', pw, ph, bx(pw / 2), ph / 2, C.fill, C.edge ? { stroke: C.line, sw: lw, sop: 70 } : null);
        var fsd = lc1_rect(PF, 'shade', pw, ph, bx(pw / 2), ph / 2, jzDarkest(sc));
        lc1_gOp(ctx, fsd, '(1+Q)*0.3', HM);
        jzSetExpr(jzXf(PF, 'ADBE Scale'), lc1_H(ctx) + HM + scQ);
        lc1_op(ctx, PF, 'Q<-0.002?AA:0', 1, HM);
        return bb;
    }
});

/* ================================================================== 2 headlineDeck \u2014 \u898B\u51FA\u3057\u3068\u30EA\u30FC\u30C9 */
jzReg('layout', 'headlineDeck', {
    plan: function (rng, cut, st) {
        var port = cut.H > cut.W * 1.08;
        return { font: rng.pick(jzFontsOf(st, ['display', 'display', 'serif'])), variant: port ? rng.pick(['top', 'bottom']) : rng.pick(['top', 'bottom', 'split']),
            kicker: rng.pick(['\u7279\u96C6', 'FEATURE', 'COVER STORY', 'ESSAY', '\u9023\u8F09', 'REPORT']), mark: rng.pick(['none', 'bar', 'none', 'dot']), dbl: rng.chance(0.5) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), k = lc1_k(ctx), port = jzPortrait(ctx), i;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), variant = jzP(ctx, 'variant', 'top'), mark = jzP(ctx, 'mark', 'none'), dbl = jzP(ctx, 'dbl', false), kick = jzP(ctx, 'kicker', 'FEATURE');
        var mono = jzMonoF(ctx), mx = W * (port ? 0.08 : 0.075), aw = W - mx * 2, split = variant === 'split' && !port, hw = split ? aw * 0.6 : aw, t0 = jzTrim(c.text);
        var fb = lc1_fitBlock(ctx, t0, font, hw, H * (split ? 0.62 : 0.46), { lead: 1.02, track: -0.01 }, port ? 5 : 3);
        var size = Math.min(fb.size, u * (port ? 0.27 : 0.24)), nL = fb.lines, mh = size * (1 + (nL - 1) * 1.02);
        var ls = lc1_ls(ctx) * 1.1, ds = jzClamp(u * 0.032, 16 * k, 44 * k), dc = lc1_deck(ctx);
        var dw = split ? aw * 0.32 : Math.min(aw, port ? aw : aw * 0.62), dper = Math.max(5, Math.floor(dw / (ds * (jzHasLatin(dc || '') ? 0.55 : 1.02))));
        var dlines = dc ? lc1_lines(jzSplitLines(dc, dper)).slice(0, 4) : [lc1_meta(ctx)];
        var dfont = dc ? jzSerifF(ctx) : mono, dsz = dc ? ds : ls, deckH = dlines.length * ds * 1.6, kickH = ls * 2.2, lw = Math.max(2 * k, u * 0.003);
        var top, hy, ry, dy, tot;
        if (split) { top = H / 2 - mh / 2; hy = H / 2; ry = null; dy = H / 2 - deckH / 2; }
        else if (variant === 'bottom') { tot = deckH + ls * 1.6 + mh + kickH; top = Math.max(H * 0.08, H * 0.9 - tot); dy = top; ry = top + deckH + ls * 0.6; hy = ry + ls + kickH + mh / 2; }
        else { tot = kickH + mh + size * 0.3 + ls + deckH; top = Math.max(H * 0.07, (H - tot) / 2 - H * 0.03); hy = top + kickH + mh / 2; ry = hy + mh / 2 + size * 0.22; dy = ry + ls * 1.2; }
        // kicker tab
        var ky = hy - mh / 2 - ls * 1.1, KT = lc1_T(ctx, kick + '  ' + jzLineNo(ctx), { font: mono, size: ls, track: 0.2, align: 'left', x: mx + ls * 0.6, y: ky, color: lc1_onCol(sc, sc.accent), name: 'kicker' });
        var kw = jzRect(KT).width + ls * 1.2, KP = lc1_bar(ctx, 'kicker tab', mx, ky, kw, ls * 1.6, sc.accent, lc1_e(0.05, 0.4) + '*K');
        lc1_op(ctx, KP, 'K', 1); KP.moveAfter(KT);
        lc1_op(ctx, KT, 'cl(' + lc1_e(0.05, 0.4) + '*K*2-1)', 1);
        // headline
        var L = lc1_main(ctx, fb.text, { font: font, size: size, x: mx, y: hy, align: 'left', lead: 1.02, track: -0.01, color: sc.fg });
        var bb = lc1_bb(L);
        if (mark !== 'none') {
            var lastW = lc1_meas(ctx, lc1_lines(fb.text)[nL - 1], font, size, { track: -0.01 }).w, ly = hy + (nL - 1) / 2 * size * 1.02, me = lc1_e(c.inDur * 0.8, 0.45, 'ioc') + '*K';
            if (mark === 'bar') lc1_bar(ctx, 'headline bar', mx, ly + size * 0.5 + Math.max(4 * k, size * 0.07) / 2, lastW, Math.max(4 * k, size * 0.07), sc.accent, me);
            else {
                var D = lc1_S(ctx, 'headline dot', mx + lastW + size * 0.25, ly + size * 0.3); lc1_ellipse(D, 'dot', size * 0.18, size * 0.18, 0, 0, sc.accent);
                jzSetExpr(jzXf(D, 'ADBE Scale'), lc1_H(ctx) + 'var e=' + me + ';var s=e<=0?0:ob(e,2);[value[0]*s,value[1]*s]');
            }
        }
        // rules
        var re = lc1_e(0.1, 0.7, 'ioc') + '*K';
        if (split) {
            var vx = mx + aw * 0.64, vh = Math.max(mh, deckH) / 2, VS = lc1_S(ctx, 'column rule', 0, 0), vg = lc1_path(VS, 'rule', [[vx, H / 2 - vh], [vx, H / 2 + vh]], sc.fg, lw * 0.6);
            lc1_trim(ctx, vg, '0.5+0.5*(' + re + ')', '0.5-0.5*(' + re + ')'); jzXf(VS, 'ADBE Opacity').setValue(80);
            if (dbl) lc1_line(ctx, 'top rule', [[mx, H * 0.1], [mx + aw, H * 0.1]], sc.fg, lw, null, re);
        } else {
            lc1_line(ctx, 'rule', [[mx, ry], [mx + aw, ry]], sc.fg, lw, null, re);
            if (dbl) lc1_line(ctx, 'rule 2', [[mx, ry + lw * 2.5], [mx + aw, ry + lw * 2.5]], sc.fg, lw * 0.4, 0.8, re);
        }
        // deck
        var dx = split ? mx + aw * 0.68 : mx;
        for (i = 0; i < dlines.length; i++) {
            var DL = lc1_T(ctx, dlines[i], { font: dfont, size: dsz, track: dc ? 0 : 0.12, align: 'left', x: dx, y: dy + ds * 0.8 + i * ds * 1.6, color: i === 0 && dc ? sc.fg : sc.sub, name: 'deck ' + (i + 1) });
            lc1_slide(ctx, DL, c.inDur * 0.6 + 0.08 * i, 0.4, 0, ds * 0.5);
        }
        var by = split ? dy + deckH + ls * 1.5 : (variant === 'bottom' ? top - ls * 1.4 : dy + deckH + ls * 0.8);
        if (dc && by < H * 0.95 && by > H * 0.04) {
            var BYL = lc1_T(ctx, '\u2014 ' + jzFmtTime(c.start) + '  /  ' + lc1_cut(jzRomajiOf(ctx) || 'No.' + jzLineNo(ctx), 22), { font: mono, size: ls * 0.85, track: 0.12, align: split ? 'left' : 'right', x: split ? dx : mx + aw, y: by, color: sc.sub, name: 'byline' });
            lc1_fade(ctx, BYL, c.inDur + 0.15, 0.4);
        }
        return bb;
    }
});

/* ================================================================== 3 contents \u2014 \u76EE\u6B21 */
jzReg('layout', 'contents', {
    plan: function (rng, cut, st) {
        var n = cut.n, port = cut.H > cut.W * 1.08, kk = n <= 4 ? 1 : n <= 9 ? 2 : n <= 14 ? 3 : 4;
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), chunks: lc1_splitK(cut.text, kk, kk), variant: port || jzHasLatin(cut.text) ? 'rows' : rng.pick(['rows', 'rows', 'tate']),
            page0: rng.int(3, 40), step: rng.int(6, 22), mark: rng.pick(['bar', 'tri', 'dot']), ctx: rng.chance(0.75) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), k = lc1_k(ctx), port = jzPortrait(ctx), i;
        var chunks = lc1_chunks(ctx), nk = chunks.length, font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), page0 = jzP(ctx, 'page0', 12), step = jzP(ctx, 'step', 12), mark = jzP(ctx, 'mark', 'bar');
        var ls = lc1_ls(ctx), lw = Math.max(1.2 * k, u * 0.0016), mono = jzMonoF(ctx), serif = jzSerifF(ctx), bb = null, pages = [];
        for (i = 0; i < nk; i++) pages.push(lc1_pad3(page0 + i * step));
        var ctxRows = jzP(ctx, 'ctx', true) ? [['\u5E8F', '000'], ['\u7D42', lc1_pad3(page0 + nk * step + 14)]] : null;
        var CH = 'var NK=' + nk + ',CUR=Math.min(NK-1,Math.floor(cl(time/Math.max(0.3,DUR*0.92))*NK));';
        var L, T, a;
        if (jzP(ctx, 'variant', 'rows') === 'tate' && !jzHasLatin(c.text)) {
            var cols = nk + (ctxRows ? 2 : 0), colW = Math.min(W * 0.86 / (cols + 1), u * 0.22), hTop = H * 0.14, hBot = H * 0.86, x0 = W / 2 + (cols + 1.2) * colW / 2;
            var size = colW * 0.7;
            for (i = 0; i < nk; i++) size = Math.min(size, (hBot - hTop) * 0.62 / Math.max(1, jzCount(chunks[i])) / 1.03);
            var TT = lc1_vcols(ctx, ['\u76EE\u6B21'], { font: serif, size: colW * 0.5, x: x0 - colW * 0.45, top: hTop, track: 0.6, color: sc.fg, name: 'title' });
            lc1_fade(ctx, TT.L, 0, 0.4);
            lc1_line(ctx, 'title rule', [[x0 - colW * 1.05, hTop], [x0 - colW * 1.05, hBot]], sc.fg, lw, null, lc1_e(0.05, 0.6, 'ioc') + '*K');
            var ci = 0;
            var colX = function (cc) { return x0 - colW * 1.2 - (cc + 0.5) * colW; };
            var drawCtxV = function (lab, pg, cc, d) {
                var A = lc1_e(d, 0.4, 'oc') + '*K', V1 = lc1_vcols(ctx, [lab], { font: serif, size: size * 0.6, x: colX(cc), top: hTop, color: sc.sub, name: lab });
                lc1_op(ctx, V1.L, A, 0.5);
                var LD = lc1_leader(ctx, 'leader ' + lab, [colX(cc), hTop + size * 1.2], [colX(cc), hBot - ls * 3.5], sc.sub, null, ls * 0.6, Math.max(1 * k, ls * 0.07), null);
                lc1_op(ctx, LD, A, 0.5);
                var V2 = lc1_vcols(ctx, [pg], { font: mono, size: ls * 0.9, x: colX(cc), top: hBot - ls * 2.7, color: sc.sub, name: pg });
                lc1_op(ctx, V2.L, A, 0.5);
            };
            if (ctxRows) drawCtxV(ctxRows[0][0], ctxRows[0][1], ci++, 0.05);
            for (i = 0; i < nk; i++) {
                var x = colX(ci++), txt = jzStrip(chunks[i]);
                var VM = lc1_vcols(ctx, [txt], { font: font, size: size, x: x, top: hTop, track: 0.03, color: sc.fg, name: txt, lyric: true });
                jzAnimate(ctx, VM.L, { mi: i * 3 }); bb = jzUnion(bb, VM.bb);
                var endY = hTop + jzCount(txt) * size * 1.03, ly = endY + size * 0.4, py = hBot - ls * 2.7;
                a = lc1_e(0.15 + i * 0.1, 0.5, 'oc') + '*K';
                var LV = lc1_leader(ctx, 'leader ' + (i + 1), [x, ly], [x, py - ls * 0.9], sc.sub, null, ls * 0.6, Math.max(1.2 * k, ls * 0.08), a);
                jzSetExpr(lc1_strokeOf(LV.property('ADBE Root Vectors Group').property(1)).property('ADBE Vector Stroke Color'), lc1_H(ctx) + CH + lc1_colExpr(sc.sub, sc.accent, '(CUR==' + i + ')'));
                lc1_op(ctx, LV, a, 1);
                var PG = lc1_vcols(ctx, [pages[i]], { font: mono, size: ls, x: x, top: py, color: sc.fg, name: 'page ' + pages[i] });
                lc1_curCol(ctx, PG.L, sc.accent, i, CH); lc1_op(ctx, PG.L, a, 1);
                var MK = lc1_S(ctx, 'current ' + (i + 1), x, hTop - ls * 1.6 + ls * 0.175); lc1_rect(MK, 'mark', colW, ls * 0.35, 0, 0, sc.accent);
                lc1_op(ctx, MK, '(CUR==' + i + ')?cl((' + a + ')*2-0.5):0', 1, CH);
            }
            if (ctxRows) drawCtxV(ctxRows[1][0], ctxRows[1][1], ci++, 0.1 + nk * 0.1);
            return bb || lc1_box(colX(cols - 1) - colW / 2, hTop, x0, hBot);
        }
        // horizontal rows
        var bw = W * (port ? 0.84 : 0.66), bx = (W - bw) / 2, numW = ls * 3.4, pgW = ls * 5, tw = bw - numW - pgW - ls * 2, cxH = ls * 2.6;
        var rowH0 = Math.min((H * 0.66 - (ctxRows ? cxH * 2 : 0)) / nk, u * 0.26), sz = rowH0 * 0.64;
        for (i = 0; i < nk; i++) sz = Math.min(sz, lc1_fitSize(ctx, chunks[i], font, tw * 0.92, rowH0 * 0.7, { track: 0.03 }));
        var rowH = Math.max(sz * 1.62, ls * 2.4), blockH = nk * rowH + (ctxRows ? cxH * 2 : 0), y0 = H / 2 - blockH / 2 + ls * 1.8, hy = y0 - ls * 2.8;
        T = lc1_T(ctx, '\u76EE\u6B21', { font: serif, size: ls * 2.1, track: 0.5, align: 'left', x: bx, y: hy - ls * 0.2, color: sc.fg, name: 'title' }); lc1_fade(ctx, T, 0, 0.4);
        T = lc1_T(ctx, 'CONTENTS', { font: mono, size: ls * 0.85, track: 0.3, align: 'right', x: bx + bw, y: hy, color: sc.sub, name: 'CONTENTS' }); lc1_fade(ctx, T, 0, 0.4);
        var le = lc1_e(0.05, 0.6, 'ioc') + '*K';
        lc1_line(ctx, 'head rule', [[bx, hy + ls * 1.4], [bx + bw, hy + ls * 1.4]], sc.fg, lw * 1.6, null, le);
        lc1_line(ctx, 'foot rule', [[bx + bw, y0 + blockH + ls * 0.2], [bx, y0 + blockH + ls * 0.2]], sc.fg, lw, 0.7, le);
        var off0 = ctxRows ? cxH : 0;
        var drawCtx = function (lab, pg, y, d) {
            var A = lc1_e(d, 0.4, 'oc') + '*K', T1 = lc1_T(ctx, lab + '\u7AE0', { font: serif, size: ls * 1.1, align: 'left', x: bx + numW, y: y, color: sc.sub, name: lab });
            lc1_op(ctx, T1, A, 0.45);
            var LD = lc1_leader(ctx, 'leader ' + lab, [bx + numW + ls * 3.4, y + ls * 0.3], [bx + bw - pgW, y + ls * 0.3], sc.sub, null, ls * 0.55, Math.max(1 * k, ls * 0.07), null);
            lc1_op(ctx, LD, A, 0.45);
            var T2 = lc1_T(ctx, pg, { font: mono, size: ls, align: 'right', x: bx + bw, y: y, color: sc.sub, name: pg }); lc1_op(ctx, T2, A, 0.45);
        };
        if (ctxRows) drawCtx(ctxRows[0][0], ctxRows[0][1], y0 + cxH * 0.5, 0.05);
        for (i = 0; i < nk; i++) {
            var y = y0 + off0 + (i + 0.5) * rowH;
            a = lc1_e(0.1 + i * 0.1, 0.5, 'oc') + '*K';
            var NT = lc1_T(ctx, jzPad(i + 1, 2), { font: mono, size: ls * 1.2, align: 'left', x: bx, y: y, color: sc.sub, name: 'no ' + (i + 1) });
            lc1_curCol(ctx, NT, sc.accent, i, CH); lc1_op(ctx, NT, a, 1);
            L = lc1_main(ctx, chunks[i], { font: font, size: sz, x: bx + numW, y: y, align: 'left', track: 0.03, color: sc.fg, mi: i * 3 });
            var lb = lc1_bb(L); bb = jzUnion(bb, lb);
            var tx1 = lb.x1 + ls * 0.8, lx1 = bx + bw - pgW;
            if (lx1 - tx1 > ls) {
                var LR = lc1_leader(ctx, 'leader ' + (i + 1), [tx1, y + sz * 0.3], [lx1, y + sz * 0.3], sc.sub, null, ls * 0.55, Math.max(1.2 * k, ls * 0.08), a);
                jzSetExpr(lc1_strokeOf(LR.property('ADBE Root Vectors Group').property(1)).property('ADBE Vector Stroke Color'), lc1_H(ctx) + CH + lc1_colExpr(sc.sub, sc.accent, '(CUR==' + i + ')'));
                lc1_op(ctx, LR, a, 1);
            }
            var PT = lc1_T(ctx, pages[i], { font: mono, size: ls * 1.4, align: 'right', x: bx + bw, y: y, color: sc.fg, name: 'page ' + pages[i] });
            lc1_curCol(ctx, PT, sc.accent, i, CH); lc1_op(ctx, PT, a, 1);
            var MS = lc1_S(ctx, 'current ' + (i + 1), 0, 0);
            if (mark === 'bar') lc1_rect(MS, 'bar', ls * 0.35, sz * 0.9, bx - ls * 1.1 + ls * 0.175, y, sc.accent);
            else if (mark === 'tri') lc1_path(MS, 'tri', [[bx - ls * 1.3, y - ls * 0.5], [bx - ls * 0.4, y], [bx - ls * 1.3, y + ls * 0.5]], null, 0, { closed: true, fill: sc.accent });
            else lc1_ellipse(MS, 'dot', ls * 0.6, ls * 0.6, bx - ls * 0.9, y, sc.accent);
            lc1_op(ctx, MS, '(CUR==' + i + ')?cl((' + a + ')*2-0.4):0', 1, CH);
        }
        if (ctxRows) drawCtx(ctxRows[1][0], ctxRows[1][1], y0 + off0 + nk * rowH + cxH * 0.5, 0.1 + nk * 0.1);
        return bb || lc1_box(bx, y0, bx + bw, y0 + blockH);
    }
});

/* ================================================================== 4 footnote \u2014 \u811A\u6CE8 */
jzReg('layout', 'footnote', {
    plan: function (rng, cut, st) {
        var n = cut.n, kk = n <= 3 ? 1 : n <= 8 ? 2 : 3;
        return { font: rng.pick(jzFontsOf(st, ['serif', 'display', 'serif'])), chunks: lc1_splitK(cut.text, kk), marks: rng.pick(['num', 'kome', 'star']), align: rng.pick(['left', 'center', 'left']), top: rng.chance(0.5) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), k = lc1_k(ctx), port = jzPortrait(ctx), i;
        var chunks = lc1_chunks(ctx), nk = chunks.length, latin = jzHasLatin(c.text), t0 = chunks.join(latin ? ' ' : ''), font = jzP(ctx, 'font', jzSerifF(ctx)), marks = jzP(ctx, 'marks', 'num');
        var mx = W * 0.09, aw = W - mx * 2, ls = lc1_ls(ctx), mono = jzMonoF(ctx), notesH = nk * ls * 1.95 + ls * 2.2, areaTop = H * 0.12, areaBot = H - notesH - H * 0.1;
        var fb = lc1_fitBlock(ctx, t0, font, aw * (port ? 0.95 : 0.88), (areaBot - areaTop) * 0.8, { lead: 1.25, track: 0.03 }, port ? 5 : 3);
        var size = Math.min(fb.size, u * 0.17), left = jzP(ctx, 'align', 'left') === 'left', X = left ? mx : W / 2, Y = (areaTop + areaBot) / 2;
        var L = lc1_main(ctx, fb.text, { font: font, size: size, x: X, y: Y, align: left ? 'left' : 'center', lead: 1.25, track: 0.03, color: sc.fg });
        var bb = lc1_bb(L);
        var gl = lc1_gpts(fb.text, size, { lead: 1.25, track: 0.03, align: left ? 'left' : 'center', w: jzRect(L).width });
        function markOf(q) { return marks === 'num' ? String(q + 1) : marks === 'kome' ? '\u203B' + (q + 1) : '*' + (q + 1); }
        var acc = 0, ms = Math.max(ls * 1.05, size * 0.3);
        for (i = 0; i < nk; i++) {
            acc += jzCount(chunks[i]);
            var g = gl[Math.min(gl.length - 1, acc - 1)];
            if (!g) continue;
            var t = c.inDur * 0.7 + 0.12 + i * 0.14;
            var M = lc1_T(ctx, markOf(i), { font: mono, size: ms, align: 'left', x: X + g.x + g.w * 0.5, y: Y + g.y - size * 0.42, color: sc.accent, name: 'mark ' + (i + 1) });
            jzSetExpr(jzXf(M, 'ADBE Scale'), lc1_H(ctx) + 'var q=cl((time-' + jzN(t) + ')/0.25);var s=q<=0?0:ob(q,2.2)*K;[value[0]*s,value[1]*s]');
        }
        var ny = H - notesH - H * 0.04 + ls, rw = Math.min(aw * 0.32, u * 0.5), re = lc1_e(c.inDur * 0.6, 0.5, 'ioc') + '*K';
        lc1_line(ctx, 'footnote rule', [[mx, ny], [mx + rw, ny]], sc.fg, Math.max(1.2 * k, u * 0.0018), 0.9, re);
        var nfs = ls * 1.15;
        for (i = 0; i < nk; i++) {
            var d = c.inDur * 0.7 + 0.2 + i * 0.12, y = ny + ls * 1.5 + i * ls * 1.95, rom = lc1_romaOf(chunks[i]);
            var note = jzFlat(chunks[i]) + '\u3000' + (rom ? rom : jzFmtTime(c.start + c.dur * i / nk)) + (i === nk - 1 && c.lineText && jzStrip(c.lineText) !== jzStrip(c.text) ? '\u3000\uFF0F\u3000' + lc1_cut(jzFlat(c.lineText), 24) : '');
            var NM = lc1_T(ctx, markOf(i), { font: mono, size: nfs, align: 'left', x: mx, y: y, color: sc.accent, name: 'note mark ' + (i + 1) });
            lc1_slide(ctx, NM, d, 0.4, ls, 0);
            var NT = lc1_T(ctx, note, { font: jzBodyF(ctx), size: nfs, track: 0.05, align: 'left', x: mx + ls * 2.4, y: y, color: sc.sub, name: 'note ' + (i + 1) });
            lc1_slide(ctx, NT, d, 0.4, ls, 0);
        }
        var FO = lc1_T(ctx, '\u2014 ' + lc1_pad3(lc1_lineN(ctx) * 7 + 3) + ' \u2014', { font: mono, size: ls * 0.8, track: 0.2, x: W / 2, y: H - H * 0.045, color: sc.sub, name: 'folio' });
        lc1_fade(ctx, FO, 0.2, 0.4, 0.7);
        if (jzP(ctx, 'top', false)) {
            var NO = lc1_T(ctx, 'NOTES  ' + jzLineNo(ctx), { font: mono, size: ls * 0.8, track: 0.3, align: 'right', x: W - mx, y: H * 0.06, color: sc.sub, name: 'NOTES' });
            lc1_fade(ctx, NO, 0.2, 0.4, 0.7);
            lc1_line(ctx, 'notes rule', [[W - mx, H * 0.06 + ls], [W - mx - rw, H * 0.06 + ls]], sc.sub, 1 * k, 0.6, re);
        }
        return bb;
    }
});

/* ================================================================== 5 proofread \u2014 \u6821\u6B63\u5237\u308A */
var LC1_PROOF = { circle: '\u30DE\u30DE', wave: '\u5F37\u8ABF', box: '\u592A\u5B57', dots: '\u30A4\u30AD' };
jzReg('layout', 'proofread', {
    plan: function (rng, cut, st) {
        var all = ['circle', 'wave', 'box', 'dots'], first = rng.pick(['circle', 'circle', 'box']), rest = [];
        for (var i = 0; i < all.length; i++) if (all[i] !== first) rest.push(all[i]);
        var marks = [first, rng.pick(rest)];
        return { font: rng.pick(jzFontsOf(st, ['serif', 'serif', 'display'])), pen: rng.chance(0.6) ? 'klee' : rng.pick(jzFontsOf(st, ['body'])), marks: marks,
            stamp: rng.chance(0.65), tombo: rng.chance(0.8), g1: rng.int(0, 99), g2: rng.int(0, 99), rot: rng.range(-8, 8) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), k = lc1_k(ctx), port = jzPortrait(ctx), i, j;
        var font = jzP(ctx, 'font', jzSerifF(ctx)), penF = jzP(ctx, 'pen', 'klee'), marksP = jzP(ctx, 'marks', ['circle', 'box']), t0 = jzTrim(c.text), ls = lc1_ls(ctx);
        var HP = 'var OP=1-oc(PO*1.15);';
        var aw = W * (port ? 0.7 : 0.62), ah = H * (port ? 0.34 : 0.4);
        var fb = lc1_fitBlock(ctx, t0, font, aw, ah, { lead: 1.45, track: 0.06 }, port ? 5 : 3), size = Math.min(fb.size, u * 0.14);
        var cx = W / 2 - (port ? 0 : W * 0.06), cy = H / 2 + (port ? H * 0.02 : 0);
        var L = lc1_main(ctx, fb.text, { font: font, size: size, x: cx, y: cy, lead: 1.45, track: 0.06, color: sc.fg });
        var bb = lc1_bb(L), mw = jzRect(L).width, mh = size * (1 + (fb.lines - 1) * 1.45), lw = Math.max(1, u * 0.0014);
        var pad = size * 0.75, x0 = cx - mw / 2 - pad, x1 = cx + mw / 2 + pad, y0 = cy - mh / 2 - pad, y1 = cy + mh / 2 + pad;
        // crop marks (\u30C8\u30F3\u30DC)
        if (jzP(ctx, 'tombo', true)) {
            var g = size * 0.18, TL = size * 0.55, TS = lc1_S(ctx, 'crop marks', 0, 0), tg = jzGrp(TS, 'tombo'), cn = [[x0, y0, -1, -1], [x1, y0, 1, -1], [x0, y1, -1, 1], [x1, y1, 1, 1]];
            for (i = 0; i < 4; i++) {
                var X = cn[i][0], Y = cn[i][1], dx = cn[i][2], dy = cn[i][3];
                jzAddPath(tg, [[X + dx * g, Y], [X + dx * (g + TL), Y]]); jzAddPath(tg, [[X + dx * g * 1.8, Y + dy * g * 0.8], [X + dx * (g + TL), Y + dy * g * 0.8]]);
                jzAddPath(tg, [[X, Y + dy * g], [X, Y + dy * (g + TL)]]); jzAddPath(tg, [[X + dx * g * 0.8, Y + dy * g * 1.8], [X + dx * g * 0.8, Y + dy * (g + TL)]]);
            }
            var cm = [[cx, y0 - g - TL / 2, 0], [cx, y1 + g + TL / 2, 0], [x0 - g - TL / 2, cy, 1], [x1 + g + TL / 2, cy, 1]];
            for (i = 0; i < 4; i++) {
                var qx = cm[i][0], qy = cm[i][1];
                if (cm[i][2]) { jzAddPath(tg, [[qx - TL / 2, qy], [qx + TL / 2, qy]]); jzAddPath(tg, [[qx, qy - TL * 0.9], [qx, qy + TL * 0.9]]); }
                else { jzAddPath(tg, [[qx - TL * 0.9, qy], [qx + TL * 0.9, qy]]); jzAddPath(tg, [[qx, qy - TL / 2], [qx, qy + TL / 2]]); }
            }
            jzAddStroke(tg, sc.sub, lw);
            lc1_trim(ctx, tg, 'oc(time/0.5)*OP', null, HP);
            lc1_op(ctx, TS, '0.8*oc(time/0.5)*OP', 1, HP);
            TS.moveAfter(L);
        }
        // red-pen marks
        var pen = lc1_plateCol(sc, [sc.accent, sc.accent2, sc.fg]), gl = lc1_gpts(fb.text, size, { lead: 1.45, track: 0.06, align: 'center', w: mw });
        if (!gl.length) return bb;
        for (i = 0; i < gl.length; i++) { gl[i].x += cx; gl[i].y += cy; }
        var pw = Math.max(2 * k, size * 0.035), ns = Math.max(ls * 1.5, size * 0.42), t1 = c.inDur * 0.8 + 0.1;
        var nChunks = c.words && c.words.length ? c.words : [t0], used = {};
        function pickGlyph(seed, pred) {
            var cand = [], list = [], q;
            for (q = 0; q < gl.length; q++) if (!used[gl[q].i]) { list.push(gl[q]); if (pred(gl[q].ch)) cand.push(gl[q]); }
            if (cand.length) list = cand;
            var gg = list.length ? list[seed % list.length] : gl[0];
            used[gg.i] = 1; return gg;
        }
        var marks = gl.length <= 4 ? [marksP[0]] : marksP, noteRight = !port;
        for (var mi = 0; mi < marks.length; mi++) {
            var mk = marks[mi], seed = mi ? jzP(ctx, 'g2', 30) : jzP(ctx, 'g1', 10), T = t1 + mi * 0.35;
            var HE = HP + 'var E=cl((time-' + jzN(T) + ')/0.45);', anchor = null;
            var PS = lc1_S(ctx, 'pen ' + mk, 0, 0);
            if (mk === 'circle') {
                var gc = pickGlyph(seed, function (ch) { return jzIsKanji(ch) || jzIsKata(ch); }), pts = [];
                for (j = 0; j <= 40; j++) { var uu = j / 40, an = -2.2 + uu * Math.PI * 2 * 1.12, kk = 1 + (jzR(seed, j >> 2, 7) * 2 - 1) * 0.05 + uu * 0.06; pts.push([gc.x + Math.cos(an) * gc.w * 0.72 * kk, gc.y + Math.sin(an) * gc.h * 0.66 * kk]); }
                lc1_trim(ctx, lc1_path(PS, 'circle', pts, pen, pw, { cap: 2, join: 2 }), 'oc(cl(E*1.6))', null, HE);
                anchor = [gc.x + gc.w * 0.5, gc.y - gc.h * 0.55];
            } else {
                var g0 = pickGlyph(seed, function (ch) { return !jzIsPunct(ch); }), want = Math.max(2, Math.min(4, jzCount(nChunks[0] || ''))), run = [g0];
                for (j = 0; j < gl.length; j++) { if (run.length >= want) break; var gq = gl[j], lastR = run[run.length - 1]; if (gq.i > lastR.i && gq.li === g0.li && !used[gq.i] && gq.i === lastR.i + 1) run.push(gq); }
                var rx0 = 1e9, rx1 = -1e9, ry = g0.y;
                for (j = 0; j < run.length; j++) { used[run[j].i] = 1; rx0 = Math.min(rx0, run[j].x - run[j].w / 2); rx1 = Math.max(rx1, run[j].x + run[j].w / 2); }
                if (mk === 'box') {
                    var q = size * 0.14;
                    lc1_trim(ctx, lc1_path(PS, 'box', [[rx0 - q, ry - size * 0.62], [rx1 + q, ry - size * 0.6], [rx1 + q * 0.8, ry + size * 0.6], [rx0 - q * 1.1, ry + size * 0.62], [rx0 - q, ry - size * 0.7]], pen, pw, { cap: 2, join: 2 }), 'oc(cl(E*1.5))', null, HE);
                    anchor = [rx1 + q, ry - size * 0.6];
                } else if (mk === 'wave') {
                    var wp = [], yy = ry + size * 0.66;
                    for (j = 0; j <= 30; j++) { var xx = jzLerp(rx0, rx1, j / 30); wp.push([xx, yy + Math.sin(j / 30 * (rx1 - rx0) / (size * 0.18)) * size * 0.06]); }
                    lc1_trim(ctx, lc1_path(PS, 'wave', wp, pen, pw, { cap: 2, join: 2 }), 'ioc(cl(E*1.5))', null, HE);
                    anchor = [rx1, yy];
                } else {
                    for (j = 0; j < run.length; j++) {
                        var dg = lc1_ellipse(PS, 'dot ' + j, pw * 2.4, pw * 2.4, 0, 0, pen);
                        jzGX(dg).property('ADBE Vector Position').setValue([run[j].x, ry + size * 0.7]);
                        lc1_gSc(ctx, dg, 'cl(E*1.6*' + run.length + '-' + j + ')', null, HE);
                    }
                    anchor = [rx1, ry + size * 0.7];
                }
            }
            lc1_op(ctx, PS, 'OP', 1, HP);
            // leader to the margin + handwritten note
            var nx, ny;
            if (noteRight) { nx = Math.min(W * 0.93 - ns * 2.2, x1 + size * 0.7 + mi * ns * 0.4); ny = y0 + (mi ? mh * 0.7 : mh * 0.15); }
            else { nx = jzClamp(anchor[0] + (mi ? ns * 2 : -ns * 2), W * 0.1, W * 0.85); ny = y0 - size * 0.9 - mi * ns * 1.6; }
            var mxp = noteRight ? [jzLerp(anchor[0], nx, 0.5), anchor[1] - size * 0.25] : [anchor[0], jzLerp(anchor[1], ny, 0.5)];
            var LE = HE + 'var LE=oc(cl(E*1.4-0.4));';
            var LL = lc1_line(ctx, 'pen leader ' + mk, [anchor, mxp, [nx - ns * 0.2, ny]], pen, pw * 0.7, null, 'LE', { head: LE, cap: 2, join: 2 });
            lc1_op(ctx, LL, 'OP', 1, HP);
            var NT = lc1_T(ctx, LC1_PROOF[mk] || '\u30DE\u30DE', { font: penF, size: ns, align: 'left', x: nx, y: ny, rot: -4, color: pen, name: 'note ' + mk });
            lc1_op(ctx, NT, 'cl((LE-0.6)/0.4)*OP', 1, LE);
        }
        // \u6821\u4E86 stamp
        if (jzP(ctx, 'stamp', true)) {
            var ts = c.inDur + 0.55 + marks.length * 0.25, S = Math.max(ls * 4.2, size * 1.05);
            var sx = port ? W * 0.78 : Math.min(W * 0.9 - S * 0.6, x1 + S * 0.5), sy = port ? Math.min(H * 0.88, y1 + S * 0.9) : y1 - S * 0.1;
            var HS = HP + 'var X=(time-' + jzN(ts) + ')/0.18,SA=X<=0?0:cl(X*3)*OP*0.9;';
            var ST = lc1_S(ctx, 'stamp', sx, sy), w2 = S * 1.25, h2 = S * 0.72;
            lc1_rect(ST, 'outer', w2, h2, 0, 0, null, { round: S * 0.08, stroke: pen, sw: Math.max(2 * k, S * 0.05) });
            lc1_rect(ST, 'inner', w2 - S * 0.14, h2 - S * 0.14, 0, 0, null, { round: S * 0.05, stroke: pen, sw: Math.max(1 * k, S * 0.02) });
            jzXf(ST, 'ADBE Rotate Z').setValue(jzP(ctx, 'rot', 0));
            jzSetExpr(jzXf(ST, 'ADBE Scale'), lc1_H(ctx) + HS + 'var s=1.5+(1-1.5)*ob(cl(X),1.4);[value[0]*s,value[1]*s]');
            lc1_op(ctx, ST, 'SA', 1, HS);
            var S1 = lc1_T(ctx, '\u6821\u4E86', { font: jzSerifF(ctx), size: S * 0.36, track: 0.2, color: pen, name: 'stamp text' }); lc1_parent(S1, ST, 0, -S * 0.06); lc1_op(ctx, S1, 'SA', 1, HS);
            var S2 = lc1_T(ctx, jzFmtTime(c.start), { font: jzMonoF(ctx), size: S * 0.12, color: pen, name: 'stamp time' }); lc1_parent(S2, ST, 0, S * 0.2); lc1_op(ctx, S2, 'SA', 1, HS);
        }
        return bb;
    }
});

/* ================================================================== 6 numbered \u2014 \u756A\u53F7\u4ED8\u304D */
jzReg('layout', 'numbered', {
    plan: function (rng, cut, st) {
        var n = cut.n, port = cut.H > cut.W * 1.08, kk = n <= 3 ? Math.min(3, n) : n <= 7 ? 2 : n <= 12 ? 3 : 4;
        var chunks = n <= 3 ? lc1_charUnits(cut.text).slice(0, 3) : lc1_splitK(cut.text, kk, kk);
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), numFont: rng.pick(jzFontsOf(st, ['display'])), chunks: chunks,
            variant: port ? rng.pick(['rows', 'rows', 'behind']) : rng.pick(['cols', 'rows', 'behind']), num: rng.pick(['outline', 'accent', 'dim']), label: rng.pick(['STEP', 'PART', 'No.', 'SCENE']) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), k = lc1_k(ctx), port = jzPortrait(ctx), i;
        var chunks = lc1_chunks(ctx), nk = chunks.length, font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), numFont = jzP(ctx, 'numFont', jzFontKeyOf(ctx.st, 'display'));
        var num = jzP(ctx, 'num', 'outline'), label = jzP(ctx, 'label', 'STEP'), variant = jzP(ctx, 'variant', 'rows'), ls = lc1_ls(ctx), lw = Math.max(1.2 * k, u * 0.0018), mono = jzMonoF(ctx), bb = null, L;
        // numerals rise inside a clip box (layer mask + a text Position animator), one after another; they sink on exit
        function drawNum(q, size, x, y, align, big, box) {
            var o = { font: numFont, size: size, x: x, y: y, align: align, track: -0.02, color: sc.fg, name: 'number ' + (q + 1) };
            if (num === 'outline' || big) { o.fill = false; o.stroke = Math.max(1.5 * k, size * (big ? 0.008 : 0.012)); o.strokeColor = num === 'accent' ? sc.accent : sc.fg; o.opacity = big ? 0.55 : 1; }
            else if (num === 'accent') o.color = sc.accent;
            else { o.color = sc.sub; o.opacity = 0.45; }
            var N = lc1_T(ctx, jzPad(q + 1, 2), o);
            var p = jzXf(N, 'ADBE Position').value, a = jzXf(N, 'ADBE Anchor Point').value;
            jzMaskRect(N, box[0] - p[0] + a[0], box[1] - p[1] + a[1], box[0] + box[2] - p[0] + a[0], box[1] + box[3] - p[1] + a[1]);
            jzAnimator(N, 'JZ Rise', [['ADBE Text Position 3D', [0, size * 1.1, 0]]], lc1_H(ctx) + '(1-' + lc1_e(0.04 + q * 0.1, 0.55) + '+ic(PO))*100');
            return N;
        }
        function lab(q, x, y, align, a) {
            var T = lc1_T(ctx, label, { font: mono, size: ls * 0.85, track: 0.3, align: align, x: x, y: y, color: q === 0 ? sc.accent : sc.sub, name: 'label' });
            lc1_op(ctx, T, a, 1);
        }
        if (variant === 'cols' && !port && nk <= 3) {
            var cw = W * 0.86 / nk, x0 = W * 0.07, csize = u * 0.2;
            for (i = 0; i < nk; i++) csize = Math.min(csize, lc1_fitSize(ctx, chunks[i], font, cw * 0.86, H * 0.3, { track: 0.02 }));
            var nsz = Math.min(cw * 0.42, Math.max(csize * 1.5, u * 0.14), H * 0.3), top = H / 2 - (nsz * 1.05 + csize * 1.4) / 2 + ls;
            for (i = 0; i < nk; i++) {
                var x = x0 + i * cw, re = lc1_e(0.1 + i * 0.08, 0.6, 'ioc') + '*K', xl = x + cw * 0.07;
                if (i > 0) { var VS = lc1_S(ctx, 'divider ' + i, 0, 0), vg = lc1_path(VS, 'divider', [[x, H / 2 - H * 0.28], [x, H / 2 + H * 0.28]], sc.sub, lw); lc1_trim(ctx, vg, '0.5+0.5*' + re, '0.5-0.5*' + re); jzXf(VS, 'ADBE Opacity').setValue(70); }
                lab(i, xl, top - ls * 0.9, 'left', lc1_e(0.1 + i * 0.1, 0.4, 'oc') + '*K');
                drawNum(i, nsz, xl, top + nsz * 0.5, 'left', false, [x, top, cw, nsz * 1.02]);
                lc1_line(ctx, 'rule ' + (i + 1), [[xl, top + nsz * 1.05], [xl + cw * 0.82, top + nsz * 1.05]], sc.fg, lw * 1.4, null, re);
                L = lc1_main(ctx, chunks[i], { font: font, size: csize, x: xl, y: top + nsz * 1.05 + csize * 0.85, align: 'left', track: 0.02, color: sc.fg, mi: i * 3 });
                bb = jzUnion(bb, lc1_bb(L));
            }
            return bb;
        }
        if (variant === 'behind') {
            var bw = W * (port ? 0.8 : 0.66), rowH = H * (port ? 0.66 : 0.72) / nk, cs = u * 0.2;
            for (i = 0; i < nk; i++) cs = Math.min(cs, lc1_fitSize(ctx, chunks[i], font, bw * 0.8, rowH * 0.62, { track: 0.02 }));
            for (i = 0; i < nk; i++) {
                var y = H / 2 + (i - (nk - 1) / 2) * rowH, sh = nk > 1 ? (i / (nk - 1) - 0.5) * bw * 0.2 : 0, cmw = lc1_meas(ctx, chunks[i], font, cs, { track: 0.02 }).w;
                var xb = W / 2 + sh - cmw / 2, nz = Math.min(rowH * 1.02, u * 0.5), nw = lc1_meas(ctx, '00', numFont, nz).w;
                var nx = jzClamp(xb + Math.min(cmw, nw) * 0.3, W * 0.04 + nw / 2, W * 0.96 - nw / 2);
                drawNum(i, nz, nx, y, 'center', true, [0, y - rowH * 0.75, W, rowH * 1.5]);
                L = lc1_main(ctx, chunks[i], { font: font, size: cs, x: xb, y: y, align: 'left', track: 0.02, color: sc.fg, mi: i * 3 });
                bb = jzUnion(bb, lc1_bb(L));
            }
            return bb;
        }
        var bw2 = W * (port ? 0.86 : 0.7), bx = (W - bw2) / 2, rh = Math.min(H * (port ? 0.62 : 0.72) / nk, u * (port ? 0.3 : 0.34)), nsz2 = rh * (port ? 0.56 : 0.72);
        var nW = lc1_meas(ctx, '00', numFont, nsz2).w + nsz2 * 0.3, cs2 = u * 0.2, y0 = H / 2 - nk * rh / 2;
        for (i = 0; i < nk; i++) cs2 = Math.min(cs2, lc1_fitSize(ctx, chunks[i], font, (bw2 - nW) * 0.96, rh * 0.6, { track: 0.02 }));
        for (i = 0; i < nk; i++) {
            var yy = y0 + (i + 0.5) * rh, re2 = lc1_e(0.06 + i * 0.08, 0.6, 'ioc') + '*K';
            lc1_line(ctx, 'row rule ' + (i + 1), [[bx, y0 + (i + 1) * rh], [bx + bw2, y0 + (i + 1) * rh]], sc.sub, lw, 0.8, re2);
            if (i === 0) { lc1_line(ctx, 'top rule', [[bx + bw2, y0], [bx, y0]], sc.fg, lw * 1.6, null, re2); lab(0, bx + bw2, y0 - ls * 0.9, 'right', lc1_e(0.1, 0.4, 'oc') + '*K'); }
            drawNum(i, nsz2, bx, yy + nsz2 * 0.02, 'left', false, [bx - 2, yy - rh / 2 + 2, nW, rh - 4]);
            L = lc1_main(ctx, chunks[i], { font: font, size: cs2, x: bx + nW, y: yy, align: 'left', track: 0.02, color: sc.fg, mi: i * 3 });
            bb = jzUnion(bb, lc1_bb(L));
        }
        return bb;
    }
});

/* ================================================================== 7 poster \u2014 \u30DD\u30B9\u30BF\u30FC */
jzReg('layout', 'poster', {
    plan: function (rng, cut, st) {
        var n = cut.n, port = cut.H > cut.W * 1.08, nl = n <= 3 ? 1 : Math.min(4, Math.ceil(n / (port ? 3.2 : 4.6)));
        return { font: rng.pick(jzFontsOf(st, ['display'])), lines: lc1_splitK(cut.text, nl, nl), variant: rng.pick(['stack', 'stack', 'block', 'tate']),
            dot: rng.chance(0.55), head: rng.pick(['LYRIC', 'SIDE A', 'VOL.', 'LIVE', 'TOUR']), ang: rng.pick([0, 0, -90]) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), k = lc1_k(ctx), port = jzPortrait(ctx), i;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), variant = jzP(ctx, 'variant', 'stack'), lines = jzP(ctx, 'lines', null), latin = jzHasLatin(c.text), mono = jzMonoF(ctx);
        var li = []; if (lines && lines.length) { for (i = 0; i < lines.length; i++) if (lines[i]) li.push(lines[i]); } if (!li.length) li.push(jzTrim(c.text));
        var ls = lc1_ls(ctx), mx = W * 0.07, my = H * 0.07, aw = W - mx * 2, credH = ls * 4.2, topH = ls * 2, y0 = my + topH + ls * 0.6, y1 = H - my - credH - ls * 0.8, avH = y1 - y0;
        var block = variant === 'block', tate = variant === 'tate' && !latin, plate = block ? lc1_plateCol(sc, [sc.accent, sc.ink]) : null, tc = block ? lc1_onCol(sc, plate) : sc.fg, lw = Math.max(1.2 * k, u * 0.0016);
        if (block) lc1_vbar(ctx, 'colour block', W / 2, 0, W, y1 + ls * 0.4, plate, 'ioe(time/0.5)*(1-ic(PO))', 1);
        var T = lc1_T(ctx, jzP(ctx, 'head', 'LYRIC') + '  ' + jzLineNo(ctx), { font: mono, size: ls, track: 0.3, align: 'left', x: mx, y: my + ls * 0.5, color: tc, name: 'head' }); lc1_fade(ctx, T, 0.1, 0.4);
        T = lc1_T(ctx, jzFmtTime(c.start), { font: mono, size: ls, track: 0.2, align: 'right', x: mx + aw, y: my + ls * 0.5, color: tc, name: 'time' }); lc1_fade(ctx, T, 0.1, 0.4);
        var re = lc1_e(0.05, 0.6, 'ioc') + '*K';
        lc1_line(ctx, 'head rule', [[mx, my + ls * 1.5], [mx + aw, my + ls * 1.5]], tc, lw, 0.8, re);
        var DQ = 'var q=cl((time-0.05)/0.45);var s=q<=0?0:ob(q,1.3)*(1-ic(PO));[value[0]*s,value[1]*s]', D, r, bb = null;
        var t = jzStrip(c.text), n = jzCount(t), per = port ? 6 : 5;
        if (tate) {
            if (!block) {
                var kc = Math.ceil(n / per), mxG0 = Math.min(per, Math.ceil(n / kc)), sz0 = Math.min(avH / (mxG0 * 0.98), aw * 0.8 / (kc * 1.14)), free = aw - kc * sz0 * 1.14;
                r = Math.min(avH * 0.36, free * 0.42);
                if (r > u * 0.06) { D = lc1_S(ctx, 'dot', mx + free * 0.48, y0 + avH / 2); lc1_ellipse(D, 'dot', r * 2, r * 2, 0, 0, sc.accent); jzSetExpr(jzXf(D, 'ADBE Scale'), lc1_H(ctx) + DQ); }
            }
        } else if (jzP(ctx, 'dot', false) && !block) {
            r = Math.min(aw, avH) * (port ? 0.34 : 0.3);
            D = lc1_S(ctx, 'dot', mx + aw - r * 0.9, y0 + r * 0.95); lc1_ellipse(D, 'dot', r * 2, r * 2, 0, 0, sc.accent); jzSetExpr(jzXf(D, 'ADBE Scale'), lc1_H(ctx) + DQ);
        }
        if (tate) {
            var cols = lc1_splitK(t, Math.ceil(n / per), Math.ceil(n / per)), mxG = 1, gap = 0.14;
            for (i = 0; i < cols.length; i++) { cols[i] = jzStrip(cols[i]); mxG = Math.max(mxG, jzCount(cols[i])); }
            var sz = Math.min(avH / (mxG * 0.98), aw * 0.8 / (cols.length * (1 + gap))), x = mx + aw - sz / 2;
            for (i = 0; i < cols.length; i++) {
                var V = lc1_vcols(ctx, [cols[i]], { font: font, size: sz, x: x, top: y0 + (avH - mxG * sz * 0.98) / 2, track: -0.02, color: tc, name: cols[i], lyric: true });
                jzAnimate(ctx, V.L, { mi: i * 3 }); bb = jzUnion(bb, V.bb);
                x -= sz * (1 + gap);
            }
        } else {
            var szs = [], tot = 0, th = 0;
            for (i = 0; i < li.length; i++) { szs.push(aw / Math.max(0.5, lc1_meas(ctx, li[i], font, 100, { track: -0.02 }).w / 100)); tot += szs[i] * 0.98; }
            var f = Math.min(1, avH / tot);
            for (i = 0; i < li.length; i++) { szs[i] *= f; th += szs[i] * 0.98; }
            var y = y0 + (avH - th) * (block ? 1 : 0.5);
            for (i = 0; i < li.length; i++) {
                y += szs[i] * 0.49;
                var L = lc1_main(ctx, li[i], { font: font, size: szs[i], x: mx, y: y, align: 'left', track: -0.02, color: tc, mi: i * 3 });
                bb = jzUnion(bb, lc1_bb(L));
                y += szs[i] * 0.49;
            }
        }
        // credits block
        var cy = H - my - credH, lw2 = Math.max(4 * k, u * 0.008);
        lc1_bar(ctx, 'credit bar', mx, cy + lw2 / 2, aw, lw2, sc.fg, re);
        var cols3 = [['DATE', jzFmtTime(c.start)], ['No.', jzLineNo(ctx) + ' / ' + jzPad(n, 2)], ['WORDS', lc1_deck(ctx) || jzRomajiOf(ctx) || jzFlat(c.text)]], cw3 = aw / 3;
        for (i = 0; i < 3; i++) {
            var a = lc1_e(0.2 + i * 0.08, 0.4, 'oc') + '*K', xc = mx + i * cw3, v = cols3[i][1];
            T = lc1_T(ctx, cols3[i][0], { font: mono, size: ls * 0.8, track: 0.3, align: 'left', x: xc, y: cy + lw2 + ls * 0.9, color: sc.sub, name: cols3[i][0] }); lc1_op(ctx, T, a, 1);
            var maxC = Math.max(3, Math.floor(cw3 * 0.9 / (ls * (jzHasLatin(v) ? 0.62 : 1.05))));
            T = lc1_T(ctx, lc1_ell(v, maxC), { font: jzBodyF(ctx), size: ls * 1.05, align: 'left', x: xc, y: cy + lw2 + ls * 2.4, color: sc.fg, name: 'credit ' + (i + 1) }); lc1_op(ctx, T, a, 1);
            if (i > 0) { var SL = lc1_line(ctx, 'credit rule ' + i, [[xc - ls * 0.5, cy + lw2 + ls * 0.4], [xc - ls * 0.5, cy + credH - ls * 0.3]], sc.sub, lw); lc1_op(ctx, SL, a, 0.6); }
        }
        return bb || lc1_box(mx, y0, mx + aw, y1);
    }
});

/* ================================================================== 8 swissGrid \u2014 \u30B9\u30A4\u30B9\u30B0\u30EA\u30C3\u30C9 */
jzReg('layout', 'swissGrid', {
    plan: function (rng, cut, st) {
        var n = cut.n, kk = n <= 4 ? 1 : n <= 9 ? 2 : 3;
        return { font: rng.pick(jzFontsOf(st, ['display'])), chunks: lc1_splitK(cut.text, kk, kk), variant: rng.pick(['a', 'b']), shape: rng.pick(['circle', 'square', 'circle', 'bar']), label: rng.chance(0.7) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), k = lc1_k(ctx), port = jzPortrait(ctx), i, j;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), variant = jzP(ctx, 'variant', 'a'), shape = jzP(ctx, 'shape', 'circle'), ls = lc1_ls(ctx), mono = jzMonoF(ctx);
        var cols = port ? 4 : 6, rows = port ? 8 : 6, m = u * 0.07, gx0 = m * 1.35, gx1 = W - m, gy0 = m, gy1 = H - m, cw = (gx1 - gx0) / cols, rh = (gy1 - gy0) / rows;
        function X(q) { return gx0 + q * cw; }
        function Y(q) { return gy0 + q * rh; }
        var lw = Math.max(1 * k, u * 0.0012), G = lc1_S(ctx, 'grid', 0, 0);
        for (i = 0; i <= cols; i++) lc1_trim(ctx, lc1_path(G, 'col ' + i, [[X(i), gy0], [X(i), gy1]], sc.sub, lw), lc1_e(i * 0.035, 0.55, 'ioc') + '*K');
        for (i = 0; i <= rows; i++) lc1_trim(ctx, lc1_path(G, 'row ' + i, [[gx0, Y(i)], [gx1, Y(i)]], sc.sub, lw), lc1_e(0.05 + i * 0.035, 0.55, 'ioc') + '*K');
        jzXf(G, 'ADBE Opacity').setValue(38);
        for (i = 0; i < cols; i++) { var CN = lc1_T(ctx, jzPad(i + 1, 2), { font: mono, size: ls * 0.7, align: 'left', x: X(i) + ls * 0.3, y: gy0 - ls * 0.6, color: sc.sub, name: 'col ' + (i + 1) }); lc1_fade(ctx, CN, 0.2, 0.4, 0.8); }
        var chunks = lc1_chunks(ctx), nk = Math.min(3, chunks.length), half = Math.ceil(cols / 2), pl = [];
        if (nk === 1) pl = [{ c: 0, r: port ? 2 : 1, span: cols, hr: port ? 2.4 : 2.6 }];
        else if (variant === 'a') pl = [{ c: 0, r: 1, span: cols, hr: port ? 2 : 2.2 }, { c: port ? 1 : half, r: rows - (nk > 2 ? 3 : 2), span: cols - (port ? 1 : half), hr: 1 }, { c: port ? 1 : half, r: rows - 2 + (port ? 0.2 : 0.1), span: cols - (port ? 1 : half), hr: 1 }];
        else for (i = 0; i < 3; i++) { var cc = Math.round(i * cols / (nk + (port ? 1.5 : 0.8))); pl.push({ c: cc, r: (port ? 1 : 0.6) + i * (rows - 1.6) / nk, span: cols - cc, hr: port ? 1.6 : 1.4 }); }
        var sizes = [];
        for (i = 0; i < nk; i++) sizes.push(Math.min(lc1_fitSize(ctx, chunks[i], font, pl[i].span * cw - cw * 0.12, pl[i].hr * rh * 0.9, { track: -0.01 }), u * 0.28));
        if (nk > 1 && variant === 'a') { var mn = 1e9; for (i = 1; i < nk; i++) mn = Math.min(mn, sizes[i]); for (i = 1; i < nk; i++) sizes[i] = Math.min(sizes[i], sizes[0] * 0.6, mn); }
        var boxes = [], bb = null, th = Math.max(3 * k, u * 0.005);
        for (i = 0; i < nk; i++) {
            var q = pl[i], sz = sizes[i], x = X(q.c) + cw * 0.06, y = Y(q.r) + sz * 0.56;
            var L = lc1_main(ctx, chunks[i], { font: font, size: sz, x: x, y: y, align: 'left', track: -0.01, color: sc.fg, mi: i * 3 });
            var w = jzRect(L).width;
            boxes.push([x, Y(q.r), x + w, Y(q.r) + sz * 1.12]);
            lc1_bar(ctx, 'rule ' + (i + 1), X(q.c), Y(q.r) - th / 2, Math.min(q.span * cw, w + cw * 0.12), th, sc.fg, lc1_e(0.12 + i * 0.1, 0.5, 'ioc') + '*K');
            bb = jzUnion(bb, lc1_bb(L));
        }
        var cands = [[cols - 2, rows - 2, 2], [0, rows - 2, 2], [cols - 2, 0, 2], [cols - 1, rows - 1, 1], [0, rows - 1, 1], [cols - 1, 0, 1]], free = null;
        for (i = 0; i < cands.length && !free; i++) {
            var cq = cands[i], hit = false;
            for (j = 0; j < boxes.length; j++) { var b = boxes[j]; if (b[0] < X(cq[0] + cq[2]) && b[2] > X(cq[0]) && b[1] < Y(cq[1] + cq[2]) && b[3] > Y(cq[1])) hit = true; }
            if (!hit) free = cq;
        }
        if (free) {
            var bw2 = free[2] * cw, bh2 = free[2] * rh, cxs = X(free[0]) + bw2 / 2, cys = Y(free[1]) + bh2 / 2, R = Math.min(bw2, bh2) * 0.46;
            var QX = 'var q=cl((time-0.25)/0.45);var s=q<=0?0:ob(q,1.2)*(1-ic(PO));', SH;
            if (shape === 'bar') { SH = lc1_S(ctx, 'accent bar', X(free[0]) + cw * 0.06, cys); lc1_rect(SH, 'bar', bw2 - cw * 0.12, rh * 0.24, (bw2 - cw * 0.12) / 2, 0, sc.accent); jzSetExpr(jzXf(SH, 'ADBE Scale'), lc1_H(ctx) + QX + '[value[0]*s,value[1]]'); }
            else {
                SH = lc1_S(ctx, 'accent ' + shape, cxs, cys);
                if (shape === 'square') lc1_rect(SH, 'square', R * 2, R * 2, 0, 0, sc.accent); else lc1_ellipse(SH, 'circle', R * 2, R * 2, 0, 0, sc.accent);
                jzSetExpr(jzXf(SH, 'ADBE Scale'), lc1_H(ctx) + QX + '[value[0]*s,value[1]*s]');
            }
        }
        if (jzP(ctx, 'label', true)) {
            var LB = lc1_T(ctx, 'JIZURA  \uFF0F  No.' + jzLineNo(ctx) + '  \uFF0F  ' + jzFmtTime(c.start), { font: mono, size: ls * 0.8, track: 0.25, x: m * 0.62, y: (gy0 + gy1) / 2, rot: -90, color: sc.sub, name: 'label' });
            lc1_fade(ctx, LB, 0.3, 0.4);
        }
        return bb;
    }
});

/* ---- greeked copy (dashed lines = rows / columns of small glyphs) and halftone "photos", as shape groups */
function lc1_greek(S, name, x, y, w, h, lh, col, seed, vertical) {
    var g = jzGrp(S, name), gz = lh / 1.45, n = Math.min(160, Math.floor((vertical ? w : h) / lh)), span = vertical ? h : w;
    for (var r = 0; r < n; r++) {
        var endP = jzR(seed, r, 3) < 0.12, ind = (r === 0 || jzR(seed, r - 1, 3) < 0.12) ? gz * 1.02 : 0;
        var len = (endP ? span * (0.2 + 0.55 * jzR(seed, r, 4)) : span) - ind;
        if (len <= gz) continue;
        if (vertical) { var xx = x + w - (r + 0.5) * lh; jzAddPath(g, [[xx, y + ind], [xx, y + ind + len]]); }
        else { var yy = y + (r + 0.5) * lh; jzAddPath(g, [[x + ind, yy], [x + ind + len, yy]]); }
    }
    var st = jzAddStroke(g, col, Math.max(1, gz * 0.72)); lc1_dash(st, gz * 0.78, gz * 0.24);
    return lc1_front(g);
}
// dot screen whose dot size follows a silhouette (kind 0 = head + shoulders, 1 = landscape); coarser than the browser's
function lc1_halftone(S, name, x, y, w, h, col, bgc, seed, kind) {
    var g = jzGrp(S, name), dg = lc1_sub(g, 'dots'), d = Math.max(4, Math.min(w, h) / 12), cols = Math.min(40, Math.ceil(w / d)), rows = Math.min(40, Math.ceil(h / d));
    var hx = 0.35 + 0.3 * jzR(seed, 1), hy = 0.36;
    for (var j = 0; j <= rows; j++) for (var i = 0; i <= cols; i++) {
        var px = (i + (j % 2) * 0.5) / cols, py = j / rows, v;
        if (kind === 0) {
            var ex = (px - hx) * w / h, dh = Math.sqrt(ex * ex + (py - hy) * (py - hy)) / 0.2, e2 = ex * 0.55, ds = Math.sqrt(e2 * e2 + ((py - 1.05) / 0.9) * ((py - 1.05) / 0.9)) / 0.36;
            v = Math.max(0, 1 - Math.min(dh, ds) * 0.85) * 0.9 + 0.12 * (1 - py);
        } else v = py > 0.62 ? 0.75 - (py - 0.62) : 0.18 + 0.5 * Math.max(0, 1 - Math.sqrt((px - hx) * (px - hx) + (py - 0.4) * (py - 0.4)) / 0.18);
        var r = d * 0.5 * Math.sqrt(jzClamp(v, 0, 1)), cx = x + px * w, cy = y + py * h;
        if (r < 0.4 || cx - r < x || cx + r > x + w || cy - r < y || cy + r > y + h) continue;
        jzAddEllipse(dg, r * 2, r * 2, cx, cy);
    }
    jzAddFill(dg, col);
    var bgG = lc1_sub(g, 'paper');                  // added after the dots = drawn behind them
    jzAddRect(bgG, Math.max(1, w), Math.max(1, h), 0, x + w / 2, y + h / 2); jzAddFill(bgG, bgc);
    return lc1_front(g);
}

/* ================================================================== 9 dictionary \u2014 \u8F9E\u66F8 */
jzReg('layout', 'dictionary', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['serif', 'display'])), variant: rng.pick(['entry', 'page', 'page']), pos: rng.pick(['\u540D', '\u9023\u8A9E', '\u611F', '\u5F62\u52D5', '\u526F']),
            page: rng.int(120, 1480), tabY: rng.range(0.2, 0.75), mark: rng.pick(['\u25C6', '\u25BC', '\u25A0']), seed: rng.int(1, 9999) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), k = lc1_k(ctx), port = jzPortrait(ctx), i;
        var font = jzP(ctx, 'font', jzSerifF(ctx)), page = jzP(ctx, 'variant', 'page') === 'page', pos = jzP(ctx, 'pos', '\u540D'), seed = jzP(ctx, 'seed', 5), t0 = jzTrim(c.text), latin = jzHasLatin(t0);
        var ls = lc1_ls(ctx), mono = jzMonoF(ctx), serif = jzSerifF(ctx), mx = W * (port ? 0.08 : 0.1), aw = W - mx * 2 - (port ? W * 0.04 : W * 0.03), lw = Math.max(1, u * 0.0014);
        var fb = lc1_fitBlock(ctx, t0, font, aw * (port ? 0.84 : 0.72), H * (port ? 0.26 : 0.3), { track: 0.02, lead: 1.1 }, port ? 3 : 2);
        var size = Math.min(fb.size, u * 0.17), nL = fb.lines, mh = size * (1 + (nL - 1) * 1.1), hx = mx + size * 0.62, hy = H * (page ? 0.44 : 0.4);
        var fs = jzClamp(u * 0.02, 11 * k, 24 * k), lh = fs * 1.8, src = jzFlat(c.lineText || t0) + (latin ? ' \u2014 ' : '\u3002');
        var top = hy - mh / 2 - size * 0.55 - ls * 1.6;
        var defs = [lc1_deck(ctx) || ('\u6B4C\u8A5E \u7B2C' + lc1_kanjiNum(lc1_lineN(ctx)) + '\u884C\u3002'), jzFmtTime(c.start) + ' \u2500 ' + jzFmtTime(c.end) + '\u3000' + jzCount(t0) + (latin ? ' chars' : '\u5B57')];
        var dfs = jzClamp(u * 0.028, 14 * k, 36 * k), defTop = hy + mh / 2 + size * 0.45, defBot = defTop + defs.length * dfs * 1.7 + dfs, T;
        var lines = lc1_lines(fb.text);
        if (page) {
            var CA = 'oc(time/0.5)*K', rowsA = Math.max(0, Math.floor((top - H * 0.1) / lh)), rowsB = Math.max(0, Math.floor((H * 0.9 - defBot) / lh));
            var RA = lc1_rowsText(ctx, lc1_bodyRows(src, aw, fs, rowsA, seed), { font: jzBodyF(ctx), size: fs, w: aw, lh: lh, x: mx, y: top - rowsA * lh, color: sc.sub, rv: CA, name: 'entries above' });
            if (RA) lc1_op(ctx, RA, CA + '*0.4', 1);
            var RB = lc1_rowsText(ctx, lc1_bodyRows(src, aw, fs, rowsB, seed + 31), { font: jzBodyF(ctx), size: fs, w: aw, lh: lh, x: mx, y: defBot + lh * 0.5, color: sc.sub, rv: CA, name: 'entries below' });
            if (RB) lc1_op(ctx, RB, CA + '*0.4', 1);
            // highlighter sweep behind the headword
            var HE = 'var HE=ioc((time-' + jzN(c.inDur * 0.6) + ')/0.5)*(1-oc(PO*1.15));';
            for (i = 0; i < nL; i++) {
                var w = lc1_meas(ctx, lines[i], font, size, { track: 0.02 }).w, ly0 = hy + (i - (nL - 1) / 2) * size * 1.1;
                var HB = lc1_bar(ctx, 'highlight ' + (i + 1), hx - size * 0.1, ly0 - size * 0.05 + size * 0.25, w + size * 0.2, size * 0.5, sc.accent, 'cl(HE*' + nL + '-' + i + ')', 1, 0.45, HE);
            }
        }
        // guide header
        var GA = lc1_e(0.1, 0.4, 'oc') + '*K';
        T = lc1_T(ctx, lc1_pad3(jzP(ctx, 'page', 300) % 1000), { font: mono, size: ls, align: 'left', x: mx, y: H * 0.055, color: sc.sub, name: 'page' }); lc1_op(ctx, T, GA, 1);
        T = lc1_T(ctx, (jzChars(jzStrip(t0))[0] || '') + '  \u2500  ' + lc1_cut(jzRomajiOf(ctx) || jzFlat(t0), 14), { font: serif, size: ls, align: 'right', x: mx + aw, y: H * 0.055, color: sc.sub, name: 'guide word' }); lc1_op(ctx, T, GA, 1);
        lc1_line(ctx, 'guide rule', [[mx, H * 0.055 + ls * 0.9], [mx + aw, H * 0.055 + ls * 0.9]], sc.sub, lw, 0.6, GA);
        // entry furniture
        var FA = lc1_e(0.05, 0.35, 'oc') + '*K';
        T = lc1_T(ctx, jzP(ctx, 'mark', '\u25C6'), { font: jzBodyF(ctx), size: size * 0.32, x: mx, y: hy - mh / 2 + size * 0.5, color: sc.accent, name: 'mark' }); lc1_op(ctx, T, FA, 1);
        var rom = jzRomajiOf(ctx);
        if (rom) { T = lc1_T(ctx, rom.toLowerCase(), { font: serif, size: ls * 1.2, track: 0.15, align: 'left', x: hx, y: hy - mh / 2 - ls * 1.2, color: sc.sub, name: 'reading' }); lc1_op(ctx, T, FA, 1); }
        var L = lc1_main(ctx, fb.text, { font: font, size: size, x: hx, y: hy, align: 'left', track: 0.02, lead: 1.1, color: sc.fg });
        var bb = lc1_bb(L);
        var lastW = lc1_meas(ctx, lines[nL - 1], font, size, { track: 0.02 }).w, ly = hy + (nL - 1) / 2 * size * 1.1;
        var pw = ls * (pos.length * 1.25 + 0.9), over = hx + lastW + size * 0.3 > W - mx - pw, px = Math.min(hx + lastW + size * 0.3, W - mx - pw), pyy = ly + (over ? size * 0.75 : 0);
        var PA = 'cl(ob((time-' + jzN(c.inDur * 0.7) + ')/0.35,1.70158)*K)';
        var PB = lc1_S(ctx, 'part of speech', px + pw / 2, pyy); lc1_rect(PB, 'box', pw, ls * 1.7, 0, 0, null, { round: ls * 0.3, stroke: sc.fg, sw: lw * 1.4 }); lc1_op(ctx, PB, PA, 1);
        T = lc1_T(ctx, pos, { font: serif, size: ls * 1.1, x: px + pw / 2, y: pyy, color: sc.fg, name: pos }); lc1_op(ctx, T, PA, 1);
        // definitions
        for (i = 0; i < defs.length; i++) {
            var d = c.inDur * 0.8 + 0.1 + i * 0.12, y = defTop + dfs * 0.8 + i * dfs * 1.7, A = lc1_e(d, 0.4, 'oc') + '*K';
            var DC = lc1_S(ctx, 'def ' + (i + 1), hx + dfs * 0.45, y); lc1_ellipse(DC, 'badge', dfs * 0.96, dfs * 0.96, 0, 0, sc.fg); lc1_op(ctx, DC, A, 1);
            T = lc1_T(ctx, String(i + 1), { font: mono, size: dfs * 0.62, x: hx + dfs * 0.45, y: y, color: sc.bg, name: 'def no ' + (i + 1) }); lc1_op(ctx, T, A, 1);
            var maxC = Math.max(4, Math.floor((aw - dfs * 2) / (dfs * (jzHasLatin(defs[i]) ? 0.55 : 1.02))));
            T = lc1_T(ctx, lc1_ell(defs[i], maxC), { font: serif, size: dfs, align: 'left', x: hx + dfs * 1.4, y: y, color: sc.fg, name: 'definition ' + (i + 1) });
            lc1_slide(ctx, T, d, 0.4, dfs, 0);
        }
        // thumb index tab on the page edge
        var tw = u * 0.075, th = u * 0.16, ty = H * 0.1 + (H * 0.8 - th) * jzP(ctx, 'tabY', 0.5), tabC = lc1_plateCol(sc, [sc.ink, sc.fg]), TQ = 'var TQ=' + lc1_e(0.15, 0.5) + '*K;';
        var EL = lc1_line(ctx, 'page edge', [[W - tw * 1.05, H * 0.04], [W - tw * 1.05, H * 0.96]], sc.sub, lw); lc1_op(ctx, EL, 'TQ', 0.35, TQ);
        var TB = lc1_S(ctx, 'thumb tab', W, ty + th / 2); lc1_rect(TB, 'tab', tw + 2, th, (tw + 2) / 2, 0, tabC);
        jzSetExpr(jzXf(TB, 'ADBE Position'), lc1_H(ctx) + TQ + '[value[0]-' + jzN(tw) + '*TQ,value[1]]');
        lc1_op(ctx, TB, 'TQ>0.01?1:0', 1, TQ);
        T = lc1_T(ctx, jzChars(jzStrip(t0))[0] || '', { font: font, size: tw * 0.62, color: lc1_onCol(sc, tabC), name: 'tab initial' }); lc1_parent(T, TB, tw * 0.5, 0);
        return bb;
    }
});

/* ================================================================== 10 ema \u2014 \u7D75\u99AC */
function lc1_emaPts(w, h, oy) { var r = h * 0.26; oy = oy || 0; return [[-w / 2, -h / 2 + r + oy], [0, -h / 2 + oy], [w / 2, -h / 2 + r + oy], [w / 2, h / 2 + oy], [-w / 2, h / 2 + oy]]; }
jzReg('layout', 'ema', {
    plan: function (rng, cut, st) {
        var font = rng.chance(0.55) ? rng.pick(['klee', 'brush']) : rng.pick(jzFontsOf(st, ['serif', 'display']));
        var vert = !jzHasLatin(cut.text) && rng.chance(0.5), emblem = rng.pick(['sun', 'wave', 'mount', 'knot']), swing = rng.range(12, 20) * rng.pick([1, -1]), back = [];
        for (var i = 0; i < 7; i++) back.push([rng.range(-1, 1), rng.range(-8, 8), rng.range(0.8, 1.05), rng.int(0, 99)]);
        return { font: font, vert: vert, emblem: emblem, swing: swing, back: back };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), k = lc1_k(ctx), port = jzPortrait(ctx), i;
        var font = jzP(ctx, 'font', 'klee'), swing = jzP(ctx, 'swing', 15), back = jzP(ctx, 'back', null), emblem = jzP(ctx, 'emblem', 'sun'), ls = lc1_ls(ctx), C = lc1_card(sc);
        if (!back || !back.length) { back = []; for (i = 0; i < 7; i++) back.push([jzR(c.seed, i, 1) * 2 - 1, jzR(c.seed, i, 2) * 16 - 8, 0.8 + 0.25 * jzR(c.seed, i, 3), Math.floor(jzR(c.seed, i, 4) * 99)]); }
        var wood = jzMixHex(C.fill, lc1_plateCol(sc, [sc.accent, sc.accent2, sc.sub], C.fill, 1.3), 0.16), woodD = jzMixHex(wood, jzDarkest(sc), 0.35), ink = lc1_onCol(sc, wood);
        var cord = lc1_plateCol(sc, [sc.accent, sc.accent2, sc.fg], wood, 1.6);
        var hw = port ? W * 0.8 : Math.min(W * 0.52, H * 0.9), ph = hw * 0.7, cordL = H * (port ? 0.06 : 0.08), rackY = port ? H / 2 - cordL - ph * 0.55 : H * 0.13;
        // rack beam
        lc1_bar(ctx, 'rack', W * 0.04, rackY, W * 0.92, u * 0.024, woodD, 'oc(time/0.45)*K');
        // plaques hanging behind
        var nb = port ? 4 : 7, bw = port ? W * 0.22 : Math.min(W * 0.12, H * 0.22), bh = bw * 0.72;
        for (i = 0; i < nb; i++) {
            var b = back[i % back.length], x = W * (0.08 + 0.84 * (i + 0.5) / nb) + b[0] * bw * 0.12, k2 = b[2], oy = bh * 0.55 * k2 + bh * 0.2;
            var BP = lc1_S(ctx, 'plaque ' + (i + 1), x, rackY);
            lc1_path(BP, 'cord', [[-bw * 0.08, -bh * 0.55 * k2 - bh * 0.2 + 2 + oy], [0, -bh * 0.3 * k2 + oy], [bw * 0.08, -bh * 0.55 * k2 - bh * 0.2 + 2 + oy]], cord, Math.max(1.5 * k, u * 0.002));
            lc1_path(BP, 'board', lc1_emaPts(bw * k2, bh * k2, oy), woodD, Math.max(1, u * 0.0016), { closed: true, fill: jzMixHex(wood, sc.bg, 0.35) });
            var gG = lc1_greek(BP, 'wish', -bw * k2 * 0.34, -bh * k2 * 0.05 + oy, bw * k2 * 0.68, bh * k2 * 0.4, bh * k2 * 0.13, ink, b[3], false);
            jzGX(gG).property('ADBE Vector Group Opacity').setValue(35);
            jzSetExpr(jzXf(BP, 'ADBE Rotate Z'), lc1_H(ctx) + 'value+' + jzN(b[1]) + '+Math.sin(time*1.1+' + i + ')*1.2');
            lc1_op(ctx, BP, 'cl(ob(cl((time-' + jzN(0.02 + i * 0.03) + ')/0.4),1.2)*2)*K*0.8', 1);
        }
        // the hero plaque swings in on its cord and settles
        var HS = 'var TD=Math.max(0,time-0.08),HA=cl(time/0.12)*(1-cl((PO-0.5)/0.5));';
        var R = lc1_S(ctx, 'ema', W / 2, rackY), PY = cordL + ph * 0.5, r = ph * 0.26;
        jzSetExpr(jzXf(R, 'ADBE Position'), lc1_H(ctx) + HS + '[value[0],value[1]+(1-ob(cl(TD/0.45),1.3))*' + jzN(-H * 0.5) + ']');
        jzSetExpr(jzXf(R, 'ADBE Rotate Z'), lc1_H(ctx) + HS + 'var SW=' + jzN(swing) + ';value+SW*Math.exp(-TD*2.6)*Math.cos(TD*6)*cl(TD/0.1)+Math.sin(time*0.9)*0.8+ic(PO)*SW');
        lc1_op(ctx, R, 'HA', 1, HS);
        lc1_path(R, 'cord', [[0, 0], [-hw * 0.06, cordL], [0, cordL + ph * 0.1], [hw * 0.06, cordL], [0, 0]], cord, Math.max(2 * k, u * 0.003), { join: 2 });
        var sh = lc1_emaPts(hw, ph, PY), shp = [];
        for (i = 0; i < sh.length; i++) shp.push([sh[i][0] + u * 0.01, sh[i][1] + u * 0.014]);
        lc1_path(R, 'shadow', shp, null, 0, { closed: true, fill: jzDarkest(sc), fop: lc1_shA(sc) * 100 });
        lc1_path(R, 'board', sh, null, 0, { closed: true, fill: wood });
        var grain = jzGrp(R, 'grain');
        for (i = 0; i < 7; i++) { var gy = -ph / 2 + r + (ph - r) * (i + 0.5) / 7 + PY; jzAddPath(grain, [[-hw / 2 + 4, gy], [-hw * 0.2, gy + ph * 0.02 * Math.sin(i)], [hw * 0.2, gy - ph * 0.02], [hw / 2 - 4, gy + ph * 0.01]]); }
        jzAddStroke(grain, woodD, Math.max(1, u * 0.0015), 12); lc1_front(grain);
        lc1_path(R, 'roof', [[-hw / 2, -ph / 2 + r + PY], [0, -ph / 2 + PY], [hw / 2, -ph / 2 + r + PY]], woodD, Math.max(4 * k, ph * 0.05), { join: 2 });
        lc1_ellipse(R, 'hole', ph * 0.07, ph * 0.07, 0, -ph / 2 + ph * 0.1 + PY, sc.bg, { stroke: woodD, sw: 1.5 * k });
        var ey = -ph / 2 + r * 1.05 + PY, es = ph * 0.1, emb = lc1_plateCol(sc, [sc.accent, sc.accent2, ink], wood, 1.8), j;
        if (emblem === 'sun') lc1_ellipse(R, 'sun', es * 2, es * 2, -hw * 0.3, ey, emb);
        else if (emblem === 'wave') for (j = 0; j < 2; j++) { var pp = []; for (i = 0; i <= 12; i++) pp.push([-hw * 0.38 + hw * 0.16 * i / 12, ey + j * es * 0.7 + Math.sin(i / 12 * Math.PI * 2) * es * 0.25]); lc1_path(R, 'wave ' + j, pp, emb, Math.max(2 * k, es * 0.18), { cap: 2 }); }
        else if (emblem === 'mount') lc1_path(R, 'mount', [[-hw * 0.4, ey + es * 0.7], [-hw * 0.3, ey - es * 0.8], [-hw * 0.2, ey + es * 0.7]], null, 0, { closed: true, fill: emb });
        else { lc1_ellipse(R, 'knot 1', es * 1.2, es * 1.2, -hw * 0.32, ey, null, { stroke: emb, sw: Math.max(2 * k, es * 0.2) }); lc1_ellipse(R, 'knot 2', es * 1.2, es * 1.2, -hw * 0.26, ey, null, { stroke: emb, sw: Math.max(2 * k, es * 0.2) }); }
        var T = lc1_T(ctx, '\u5949\u7D0D', { font: jzSerifF(ctx), size: es * 1.1, track: 0.3, x: hw * 0.3, y: ey, color: ink, name: 'hounou' });
        T.setParentWithJump(R); lc1_op(ctx, T, 'HA*0.8', 1, HS);
        // the wish (lyric), handwritten
        var t0 = jzTrim(c.text), ax = -hw * 0.42, aw = hw * 0.84, ay = -ph / 2 + r * 1.6, ah = ph / 2 - ay - ph * 0.14, fb, size, L, mi = lc1_mi(ctx, 0.35);
        if (jzP(ctx, 'vert', false) && !jzHasLatin(t0)) {
            fb = lc1_fitBlock(ctx, lc1_vtext(t0), font, aw, ah, { vertical: true, lead: 1.3, track: 0.04 }, 3); size = Math.min(fb.size, ph * 0.34);
            var vl = lc1_lines(fb.text), vm = lc1_vmeas(vl, size, { lead: 1.3, track: 0.04 });
            L = lc1_vcols(ctx, vl, { font: font, size: size, x: 0, top: ay + (ah - vm.h) / 2 + PY, lead: 1.3, track: 0.04, rot: 1.5, color: ink, name: t0, lyric: true }).L;
        } else {
            fb = lc1_fitBlock(ctx, t0, font, aw, ah, { lead: 1.2, track: 0.02 }, 3); size = Math.min(fb.size, ph * 0.3);
            L = lc1_T(ctx, fb.text, { font: font, size: size, x: 0, y: ay + ah / 2 + PY, lead: 1.2, track: 0.02, rot: -1.5, color: ink, name: t0, lyric: true });
        }
        L.setParentWithJump(R);
        jzAnimate(ctx, L, { mi: mi, noHold: true, treat: false });
        T = lc1_T(ctx, 'No.' + jzLineNo(ctx) + '  ' + jzFmtTime(c.start), { font: jzMonoF(ctx), size: ls * 0.8, track: 0.15, align: 'right', x: hw * 0.44, y: ph * 0.42 + PY, color: ink, name: 'number' });
        T.setParentWithJump(R); lc1_op(ctx, T, 'HA*0.6', 1, HS);
        return lc1_box(W / 2 - hw / 2, rackY + cordL, W / 2 + hw / 2, rackY + cordL + ph);
    }
});

/* ================================================================== 11 ransom \u2014 \u5207\u308A\u629C\u304D\u6587\u5B57 */
function lc1_slots(t) { return jzChars(jzTrim(String(t || '')).replace(/[\s\u3000]+/g, ' ')); }
jzReg('layout', 'ransom', {
    plan: function (rng, cut, st) {
        var base = jzFontsOf(st, ['display', 'serif', 'body']).concat(jzFontsOf(st, ['mono'])), extra = ['mincho_black', 'gothic_black', 'pop', 'dot', 'brush'], fonts = [], i;
        for (i = 0; i < extra.length; i++) if (JZ_FONT_CANDIDATES[extra[i]] && rng.chance(0.35)) base.push(extra[i]);
        for (i = 0; i < base.length; i++) if (jzIndexOf(fonts, base[i]) < 0) fonts.push(base[i]);
        var units = lc1_slots(cut.text), look = [];
        for (i = 0; i < units.length; i++) look.push([rng.int(0, fonts.length - 1), rng.int(0, 5), rng.range(-9, 9), rng.range(0.84, 1.16), rng.range(-0.1, 0.1), rng.int(0, 999)]);
        return { fonts: fonts, look: look, tilt: rng.range(-3, 3), shadow: rng.chance(0.7) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), k = lc1_k(ctx), port = jzPortrait(ctx), i, j;
        var units = lc1_slots(c.text), n = units.length;
        if (!n) return null;
        var perRow = port ? Math.min(n, n <= 4 ? 4 : Math.ceil(n / Math.ceil(n / 4))) : Math.min(n, n <= 8 ? 8 : Math.ceil(n / 2)), rowsA = [], cur;
        if (jzHasLatin(c.text)) {
            var words = [], i0 = 0, len = 0, target = perRow;
            cur = [];
            for (i = 0; i <= n; i++) if (i === n || units[i] === ' ') { if (i > i0) words.push([i0, i]); i0 = i + 1; }
            for (i = 0; i < words.length; i++) target = Math.max(target, words[i][1] - words[i][0]);
            for (i = 0; i < words.length; i++) {
                var wl = words[i][1] - words[i][0];
                if (cur.length && len + 1 + wl > target) { rowsA.push(cur); cur = []; len = 0; }
                if (cur.length) { cur.push(-1); len++; }
                for (j = words[i][0]; j < words[i][1]; j++) cur.push(j);
                len += wl;
            }
            if (cur.length) rowsA.push(cur);
        } else for (i = 0; i < n; i += perRow) { cur = []; for (j = i; j < Math.min(n, i + perRow); j++) cur.push(j); rowsA.push(cur); }
        perRow = 1;
        for (i = 0; i < rowsA.length; i++) perRow = Math.max(perRow, rowsA[i].length);
        var rowsN = rowsA.length, base = Math.min(W * 0.84 / (perRow * 1.18), H * 0.64 / (rowsN * 1.35), u * 0.26), place = {};
        for (i = 0; i < rowsN; i++) for (j = 0; j < rowsA[i].length; j++) if (rowsA[i][j] >= 0) place[rowsA[i][j]] = [i, j, rowsA[i].length];
        var C = lc1_card(sc), pal = [[lc1_plateCol(sc, [sc.ink, sc.fg]), null], [C.fill, null], [lc1_plateCol(sc, [sc.accent, sc.ink]), null], [lc1_plateCol(sc, [sc.accent2, sc.sub, sc.accent]), null], [jzDarkest(sc), null], [C.fill, 'line']];
        var fonts = jzP(ctx, 'fonts', null), look = jzP(ctx, 'look', []), tilt = jzP(ctx, 'tilt', 0), shadow = jzP(ctx, 'shadow', true), bb = null;
        if (!fonts || !fonts.length) fonts = ['gothic_black'];
        for (i = 0; i < n; i++) {
            var ch = units[i]; if (ch === ' ') continue;
            var lk = look[i] || [0, 0, 0, 1, 0, i], pl = place[i]; if (!pl) continue;
            var sz = base * lk[3], x = W / 2 + (pl[1] - (pl[2] - 1) / 2) * base * 1.18 + (jzR(lk[5], 1) * 2 - 1) * base * 0.05, y = H / 2 + (pl[0] - (rowsN - 1) / 2) * base * 1.35 + lk[4] * base;
            var font = fonts[lk[0] % fonts.length], fill = pal[lk[1] % pal.length][0], mode = pal[lk[1] % pal.length][1];
            if (jzContrast(fill, sc.bg) < 1.25 && mode !== 'line') mode = 'edge';
            var tc = lc1_onCol(sc, fill), t0 = 0.03 + i * jzClamp(0.4 / n, 0.025, 0.07);
            var w = sz * (Math.max(0.55, lc1_adv(ch)) + 0.26 + 0.14 * jzR(lk[5], 11)), h = sz * (1.2 + 0.18 * jzR(lk[5], 12)), pts = [];
            for (var s2 = 0; s2 < 4; s2++) for (var m = 0; m < 4; m++) {
                var f = m / 4, jit = (jzR(lk[5], s2, m) * 2 - 1) * sz * 0.045;
                if (s2 === 0) pts.push([-w / 2 + w * f, -h / 2 + jit]); else if (s2 === 1) pts.push([w / 2 + jit, -h / 2 + h * f]);
                else if (s2 === 2) pts.push([w / 2 - w * f, h / 2 + jit]); else pts.push([-w / 2 + jit, h / 2 - h * f]);
            }
            var QH = 'var Q=cl((time-' + jzN(t0) + ')/0.16);';
            var S = lc1_S(ctx, 'scrap ' + (i + 1), x, y);
            if (shadow) { var sp = []; for (j = 0; j < pts.length; j++) sp.push([pts[j][0] + sz * 0.05, pts[j][1] + sz * 0.07]); lc1_path(S, 'shadow', sp, null, 0, { closed: true, fill: jzDarkest(sc), fop: 45 }); }
            if (mode === 'line') lc1_path(S, 'scrap', pts, sc.fg, Math.max(1.5 * k, sz * 0.02), { closed: true, fill: sc.bg });
            else lc1_path(S, 'scrap', pts, mode === 'edge' ? jzMixHex(sc.fg, fill, 0.4) : null, Math.max(1 * k, sz * 0.012), { closed: true, fill: fill });
            jzXf(S, 'ADBE Rotate Z').setValue(lk[2] + tilt);
            jzSetExpr(jzXf(S, 'ADBE Scale'), lc1_H(ctx) + QH + 'var s=(1.5-0.5*oc(Q))*(1-ic(PO)*0.25);[value[0]*s,value[1]*s]');
            lc1_op(ctx, S, 'Q<=0?0:cl(Q*2.5)*(1-oc(PO*1.1))', 1, QH);
            var L = lc1_T(ctx, ch, { font: font, size: sz, x: 0, y: sz * 0.02, color: mode === 'line' ? sc.fg : tc, name: ch, lyric: true });
            L.setParentWithJump(S);
            jzAnimate(ctx, L, { mi: lc1_mi(ctx, t0), noHold: lc1_plateHold(ctx), treat: false });
            bb = jzUnion(bb, lc1_box(x - w / 2, y - h / 2, x + w / 2, y + h / 2));
        }
        return bb;
    }
});

/* ================================================================== 12 newspaper \u2014 \u65B0\u805E */
jzReg('layout', 'newspaper', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), variant: jzHasLatin(cut.text) ? 'yoko' : rng.pick(['yoko', 'tate', 'tate']), spin: rng.chance(0.45), rev: rng.chance(0.6),
            seed: rng.int(1, 9999), mast: rng.pick(['\u5B57\u9762\u65B0\u805E', '\u6B4C\u8A5E\u65B0\u5831', '\u591C\u66F4\u65B0\u805E']), issue: rng.int(1000, 29999) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), k = lc1_k(ctx), port = jzPortrait(ctx), i;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), spin = jzP(ctx, 'spin', false), rev = jzP(ctx, 'rev', true), seed = jzP(ctx, 'seed', 3), mast = String(jzP(ctx, 'mast', '\u5B57\u9762\u65B0\u805E'));
        var C = lc1_card(sc), ls = lc1_ls(ctx), pw = W * (port ? 0.92 : 0.9), ph = H * 0.9, cx = W / 2, cy = H / 2, t0 = jzTrim(c.text);
        // the sheet: classic spinning newspaper, or a slide up; everything else rides on it
        var R = lc1_S(ctx, 'newspaper', cx, cy);
        if (spin) {
            jzSetExpr(jzXf(R, 'ADBE Rotate Z'), lc1_H(ctx) + 'value+(1-oc(time/0.6))*720');
            jzSetExpr(jzXf(R, 'ADBE Scale'), lc1_H(ctx) + 'var s=Math.max(0.001,oc(time/0.6));[value[0]*s,value[1]*s]');
            jzSetExpr(jzXf(R, 'ADBE Position'), lc1_H(ctx) + '[value[0],value[1]+ic(PO)*' + jzN(H * 0.08) + ']');
        } else jzSetExpr(jzXf(R, 'ADBE Position'), lc1_H(ctx) + '[value[0],value[1]+(1-oe(time/0.45))*' + jzN(H * 0.9) + '+ic(PO)*' + jzN(H * 0.08) + ']');
        lc1_op(ctx, R, '1-ic(PO)', 1);
        lc1_shadow(R, sc, u * 0.02);
        function P(x, y) { return [x - pw / 2, y - ph / 2]; }
        function rectP(name, x, y, w, h, col, o) { return lc1_rect(R, name, w, h, x + w / 2 - pw / 2, y + h / 2 - ph / 2, col, o); }
        rectP('sheet', 0, 0, pw, ph, C.fill, C.edge ? { stroke: C.line, sw: 1 * k } : null);
        var m = pw * 0.035, tx = C.text, lw = Math.max(1, u * 0.0013), hl = lc1_plateCol(sc, [sc.accent], C.fill, 2) === sc.accent ? sc.accent : tx;
        var mw = Math.min(pw * 0.13, ph * 0.12), mh = Math.min(ph * 0.34, mw * 2.6), mX = pw - m - mw, mY = m;
        rectP('masthead', mX, mY, mw, mh, hl);
        var fs = jzClamp(u * 0.012, 7 * k, 15 * k), lh = fs * 1.45, bodyX1 = mX - m * 0.6, RV = 'cl((time-0.2)/0.9)', rules = [];
        function tier(x0, y0, x1, y1, sd) { if (x1 - x0 < lh || y1 - y0 < fs * 2) return; var g = lc1_greek(R, 'copy', x0 - pw / 2, y0 + fs * 0.4 - ph / 2, x1 - x0, y1 - y0 - fs * 0.8, lh, tx, sd, true); lc1_gOp(ctx, g, '0.42*' + RV); }
        function photo(x0, y0, w, h, kind) {
            if (w < 4 || h < 4) return;
            var g = lc1_halftone(R, 'photo', x0 - pw / 2, y0 - ph / 2, w, h, tx, jzMixHex(C.fill, tx, 0.12), seed, kind); lc1_gOp(ctx, g, RV);
            var cg = lc1_path(R, 'caption', [P(x0, y0 + h + fs * 0.6), P(x0 + w * 0.7, y0 + h + fs * 0.6)], tx, fs * 0.5); jzGX(cg).property('ADBE Vector Group Opacity').setValue(35);
        }
        function rule(x0, y, x1) { rules.push([P(x0, y), P(x1, y)]); }
        var size, fb, L, bb, mi = lc1_mi(ctx, spin ? 0.45 : 0.3), T;
        if (jzP(ctx, 'variant', 'yoko') === 'tate' && !jzHasLatin(t0)) {
            var hw = pw * (port ? 0.34 : 0.26), hh = ph - m * 2, hX1 = mX - m * 0.8;
            fb = lc1_fitBlock(ctx, jzStrip(t0), font, hw * 0.92, hh * 0.94, { vertical: true, lead: 1.1, track: -0.02 }, 2); size = fb.size;
            var vl = lc1_lines(fb.text), mm = lc1_vmeas(vl, size, { lead: 1.1, track: -0.02 });
            if (rev) rectP('reverse', hX1 - mm.w - size * 0.3, m, mm.w + size * 0.3, mm.h + size * 0.4, tx);
            var bx1 = hX1 - mm.w - size * 0.5, sub = lc1_deck(ctx);
            var gx1 = bx1 - (sub ? size * 0.5 : 0), tiers = port ? 5 : 4, th2 = (ph - m * 2) / tiers;
            for (i = 0; i < tiers; i++) {
                var y0 = m + i * th2;
                if (i > 0) rule(m, y0, gx1);
                if (i === 1 && !port) { var iw = (gx1 - m) * 0.42; photo(m, y0 + fs, iw, th2 * 2 - fs * 3, 0); tier(m + iw + fs, y0, gx1, y0 + th2, seed + i); continue; }
                if (i === 2 && !port) { tier(m + (gx1 - m) * 0.42 + fs, y0, gx1, y0 + th2, seed + i); continue; }
                tier(m, y0, gx1, y0 + th2, seed + i);
            }
            rule(bodyX1 + m * 0.3, m + mh + ls * 2.6, pw - m);
            tier(mX, m + mh + ls * 2.8, pw - m, ph - m, seed + 9);
            var V = lc1_vcols(ctx, vl, { font: font, size: size, x: hX1 - size * 0.15 - mm.w / 2 - pw / 2, top: m + size * 0.2 - ph / 2, lead: 1.1, track: -0.02, color: rev ? C.fill : tx, name: t0, lyric: true });
            L = V.L;
            if (sub) {
                var sv = jzChars(jzStrip(sub)).slice(0, 14).join(''), ssz = Math.min(size * 0.3, (ph - m * 2) / 15);
                var SV = lc1_vcols(ctx, [sv], { font: font, size: ssz, x: bx1 - size * 0.2 - pw / 2, top: m + size * 0.2 - ph / 2, color: tx, name: 'sub headline' });
                SV.L.setParentWithJump(R); lc1_op(ctx, SV.L, '1-ic(PO)', 1);
            }
        } else {
            var hbw = bodyX1 - m, hbh = ph * (port ? 0.3 : 0.32);
            fb = lc1_fitBlock(ctx, t0, font, hbw * 0.94, hbh * 0.84, { lead: 1.05, track: -0.02 }, port ? 3 : 2); size = fb.size;
            if (rev) rectP('reverse', m, m, hbw, hbh, tx);
            var yb = m + hbh + fs, tiersY = port ? 5 : 3, th3 = (ph - m - yb) / tiersY;
            rule(m, yb, bodyX1);
            for (i = 0; i < tiersY; i++) {
                var yy0 = yb + i * th3, x1 = i === 0 ? bodyX1 : pw - m;
                if (i > 0) rule(m, yy0, pw - m);
                if (i === 1) { var iw2 = (x1 - m) * (port ? 0.5 : 0.3); photo(x1 - iw2, yy0 + fs, iw2, th3 - fs * 3, 1); tier(m, yy0, x1 - iw2 - fs, yy0 + th3, seed + i); continue; }
                tier(m, yy0, x1, yy0 + th3, seed + i);
            }
            tier(mX, m + mh + ls * 2.8, pw - m, yb, seed + 9);
            L = lc1_T(ctx, fb.text, { font: font, size: size, x: m + hbw / 2 - pw / 2, y: m + hbh / 2 - ph / 2, lead: 1.05, track: -0.02, color: rev ? C.fill : tx, name: t0, lyric: true });
        }
        if (rules.length) { var rg = jzGrp(R, 'rules'); for (i = 0; i < rules.length; i++) jzAddPath(rg, rules[i]); jzAddStroke(rg, tx, lw, 70); lc1_front(rg); }
        // masthead type
        var MT = lc1_vcols(ctx, [mast], { font: font, size: Math.min(mw * 0.62, mh * 0.9 / jzChars(mast).length), x: mX + mw / 2 - pw / 2, top: 0, track: 0.05, color: lc1_onCol(sc, hl), name: 'masthead' });
        jzXf(MT.L, 'ADBE Position').setValue([mX + mw / 2 - pw / 2, mY + mh / 2 - ph / 2]); MT.L.setParentWithJump(R); lc1_op(ctx, MT.L, '1-ic(PO)', 1);
        T = lc1_T(ctx, '\u7B2C' + jzP(ctx, 'issue', 12000) + '\u53F7', { font: jzBodyF(ctx), size: ls * 0.7, x: mX + mw / 2 - pw / 2, y: mY + mh + ls * 0.8 - ph / 2, color: tx, name: 'issue' }); T.setParentWithJump(R); lc1_op(ctx, T, '1-ic(PO)', 0.8);
        T = lc1_T(ctx, jzFmtTime(c.start), { font: jzMonoF(ctx), size: ls * 0.7, x: mX + mw / 2 - pw / 2, y: mY + mh + ls * 1.8 - ph / 2, color: tx, name: 'date' }); T.setParentWithJump(R); lc1_op(ctx, T, '1-ic(PO)', 0.8);
        L.moveToBeginning(); L.setParentWithJump(R);
        jzAnimate(ctx, L, { mi: mi, noHold: lc1_plateHold(ctx) });
        return lc1_box(cx - pw / 2, cy - ph / 2, cx + pw / 2, cy + ph / 2);
    }
});

/* ================================================================== 13 vinyl \u2014 \u30EC\u30B3\u30FC\u30C9 */
// text along a circle, centred on midDeg (screen degrees), reading clockwise; the layer's anchor is the circle centre
function lc1_arcText(ctx, txt, font, size, cx, cy, r, midDeg, color, track) {
    var L = jzNoGhost(jzText(ctx, txt, { font: font, size: size, color: color, x: cx, y: cy, track: track || 0, name: 'label text' }));
    var tw = jzRect(L).width, a0 = midDeg - (tw / 2) / r * 180 / Math.PI, sh = lc1_arcShape(cx, cy, r, a0, a0 + 359.9);
    jzTextOnPath(L, sh, null, ctx);
    jzXf(L, 'ADBE Anchor Point').setValue([cx, cy]); jzXf(L, 'ADBE Position').setValue([cx, cy]);
    return L;
}
// the disc: one rotating shape layer (vinyl, grooves, label, label arc), a fixed sheen and the label text on a circle.
// o: head (expression vars), ang (deg expr), dx / dy (px exprs), s (scale expr), a (alpha expr)
function lc1_disc(ctx, cx, cy, R, labC, big, txt, o) {
    var sc = ctx.sc, vin = jzLum(sc.bg) < 0.3 ? jzMixHex(jzDarkest(sc), jzLightest(sc), 0.1) : jzDarkest(sc), lite = jzLightest(sc), LR = R * (big ? 0.5 : 0.36), lc = lc1_onCol(sc, labC), i;
    var HD = lc1_H(ctx) + (o.head || ''), POS = HD + '[value[0]+(' + (o.dx || '0') + '),value[1]+(' + (o.dy || '0') + ')]', SC = HD + 'var s=' + (o.s || '1') + ';[value[0]*s,value[1]*s]';
    var D = lc1_S(ctx, 'record', cx, cy);
    lc1_ellipse(D, 'vinyl', R * 2, R * 2, 0, 0, vin);
    var gr = jzGrp(D, 'grooves');
    for (i = 0; i < 16; i++) { var rr = R * (0.42 + 0.55 * i / 15); jzAddEllipse(gr, rr * 2, rr * 2, 0, 0); }
    jzAddStroke(gr, lite, Math.max(1, R * 0.004), 10); lc1_front(gr);
    lc1_ellipse(D, 'label', LR * 2, LR * 2, 0, 0, labC);
    var ag = jzGrp(D, 'label arc'); lc1_shp(ag, lc1_arcShape(0, 0, LR * 0.7, 20, 160)); jzAddStroke(ag, lc, Math.max(1, R * 0.004), 60); lc1_front(ag);
    lc1_ellipse(D, 'hole', R * 0.044, R * 0.044, 0, 0, sc.bg);
    jzSetExpr(jzXf(D, 'ADBE Rotate Z'), HD + 'value+(' + o.ang + ')');
    jzSetExpr(jzXf(D, 'ADBE Position'), POS); jzSetExpr(jzXf(D, 'ADBE Scale'), SC); lc1_op(ctx, D, o.a, 1, o.head);
    // sheen (stays put while the disc turns): two soft light wedges
    var SH = lc1_S(ctx, 'record sheen', cx, cy), wd = [[-0.6, 0.405], [2.54, 3.545]];
    for (i = 0; i < 2; i++) for (var q = 0; q < 2; q++) {
        var m0 = (wd[i][0] + wd[i][1]) / 2, hw = (wd[i][1] - wd[i][0]) / 2 * (q ? 0.45 : 1), pts = [], j;
        for (j = 0; j <= 8; j++) { var an = m0 - hw + 2 * hw * j / 8; pts.push([Math.cos(an) * R * 0.98, Math.sin(an) * R * 0.98]); }
        for (j = 8; j >= 0; j--) { var an2 = m0 - hw + 2 * hw * j / 8; pts.push([Math.cos(an2) * R * 0.42, Math.sin(an2) * R * 0.42]); }
        lc1_path(SH, 'sheen', pts, null, 0, { closed: true, fill: lite, fop: 5 });
    }
    var sb = jzEffect(SH, 'ADBE Gaussian Blur 2', 'JZ Soft'); jzEP(sb, 1, R * 0.04);
    jzSetExpr(jzXf(SH, 'ADBE Position'), POS); jzSetExpr(jzXf(SH, 'ADBE Scale'), SC); lc1_op(ctx, SH, o.a, 1, o.head);
    var T = lc1_arcText(ctx, txt, jzMonoF(ctx), R * 0.034, cx, cy, LR * 0.84, -90, lc, 0.25);
    jzSetExpr(jzXf(T, 'ADBE Rotate Z'), HD + 'value+(' + o.ang + ')');
    jzSetExpr(jzXf(T, 'ADBE Position'), POS); jzSetExpr(jzXf(T, 'ADBE Scale'), SC); lc1_op(ctx, T, '(' + o.a + ')*0.9', 1, o.head);
    return D;
}
jzReg('layout', 'vinyl', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), variant: cut.n <= 6 ? rng.pick(['sleeve', 'label']) : 'sleeve', side: rng.pick(['A', 'B']), rpm: rng.pick(['33\u2153', '45']),
            sleeve: rng.pick(['ink', 'accent', 'card']), cat: rng.int(100, 9999), arm: rng.chance(0.7) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), k = lc1_k(ctx), port = jzPortrait(ctx);
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), side = jzP(ctx, 'side', 'A'), cat = jzP(ctx, 'cat', 1234), ls = lc1_ls(ctx), mono = jzMonoF(ctx), t0 = jzTrim(c.text);
        var labC = lc1_plateCol(sc, [sc.accent, sc.accent2, sc.fg], jzDarkest(sc), 1.5), arc = 'SIDE ' + side + '  \u00B7  ' + jzP(ctx, 'rpm', '45') + ' RPM  \u00B7  No.' + jzLineNo(ctx) + '  \u00B7  ';
        var L, fb, T;
        if (jzP(ctx, 'variant', 'sleeve') === 'label') {
            var R = Math.min(H * 0.44, W * 0.44), cx = W / 2, cy = H / 2, TT = Math.max(0.5, c.inDur + 0.35);
            var HV = 'var A=oc(time/0.3)*(1-cl((PO-0.6)/0.4)),ANG=540*(1-oc(time/' + jzN(TT) + '))-300*ic(PO),KS=0.9+0.1*oc(time/0.4);';
            var D = lc1_disc(ctx, cx, cy, R, labC, true, arc + arc, { head: HV, ang: 'ANG', s: 'KS', a: 'A' });
            if (jzP(ctx, 'arm', true)) {
                var AR = lc1_S(ctx, 'tone arm', cx + R * 1.02, cy - R * 0.92), AL = R * 1.05;
                lc1_path(AR, 'arm', [[0, 0], [AL, 0]], sc.sub, Math.max(3 * k, R * 0.02), { cap: 2 });
                lc1_rect(AR, 'head', R * 0.07, R * 0.07, AL, 0, sc.fg);
                jzXf(AR, 'ADBE Rotate Z').setValue(32);
                jzSetExpr(jzXf(AR, 'ADBE Rotate Z'), lc1_H(ctx) + 'value+30*' + lc1_e(0.2, 0.7, 'ioc') + '*K');
                lc1_op(ctx, AR, 'A', 1, HV);
                var PV = lc1_S(ctx, 'arm pivot', cx + R * 1.02, cy - R * 0.92); lc1_ellipse(PV, 'pivot', R * 0.12, R * 0.12, 0, 0, sc.sub); lc1_op(ctx, PV, 'A', 1, HV);
            }
            var lab = R * 0.5;
            fb = lc1_fitBlock(ctx, t0, font, lab * 1.3, lab * 0.8, { lead: 1.1 }, 2);
            L = lc1_T(ctx, fb.text, { font: font, size: Math.min(fb.size, lab * 0.55), x: 0, y: 0, lead: 1.1, color: lc1_onCol(sc, labC), name: t0, lyric: true });
            L.setParentWithJump(D);
            jzAnimate(ctx, L, { mi: 0, noHold: true, treat: false });
            return lc1_box(cx - lab, cy - lab, cx + lab, cy + lab);
        }
        // sleeve + disc sliding out
        var S = port ? Math.min(W * 0.74, H * 0.4) : Math.min(H * 0.74, W * 0.42), sx = port ? W / 2 : W / 2 - S * 0.28, sy = port ? H / 2 - S * 0.28 : H / 2, Rd = S * 0.47;
        var HS = 'var EIN=oe(time/0.45),SL=ioc((time-0.25)/0.7)*(1-ioc(PO)),A=cl(EIN*2)*(1-cl((PO-0.5)/0.5)),OFF=(1-EIN)*' + jzN(S * 0.25) + ';';
        var dMove = 'SL*' + jzN(S * 0.56) + '+OFF';
        lc1_disc(ctx, sx, sy, Rd, labC, false, arc + arc, { head: HS, ang: 'time*120', dx: port ? '0' : dMove, dy: port ? dMove : '0', a: 'A' });
        var sl = jzP(ctx, 'sleeve', 'ink'), slC = sl === 'card' ? lc1_card(sc).fill : lc1_plateCol(sc, sl === 'accent' ? [sc.accent, sc.ink] : [sc.ink, sc.fg]), tc = lc1_onCol(sc, slC);
        var SV = lc1_S(ctx, 'sleeve', sx, sy);
        lc1_rect(SV, 'sleeve', S, S, 0, 0, slC, jzContrast(slC, sc.bg) < 1.4 ? { stroke: jzMixHex(sc.fg, slC, 0.4), sw: 1.5 * k } : null);
        lc1_shadow(SV, sc, u * 0.015);
        jzSetExpr(jzXf(SV, 'ADBE Position'), lc1_H(ctx) + HS + (port ? '[value[0],value[1]+OFF]' : '[value[0]+OFF,value[1]]'));
        lc1_op(ctx, SV, 'A', 1, HS);
        var m = S * 0.07, FA = 'A*' + lc1_e(0.15, 0.4, 'oc') + '*K', x0 = -S / 2, y0 = -S / 2;
        T = lc1_T(ctx, 'JZR-' + cat, { font: mono, size: ls * 0.9, track: 0.2, align: 'right', x: x0 + S - m, y: y0 + m + ls * 0.3, color: tc, name: 'catalogue' }); T.setParentWithJump(SV); lc1_op(ctx, T, FA, 1, HS);
        T = lc1_T(ctx, 'SIDE ' + side, { font: mono, size: ls * 0.9, track: 0.2, align: 'left', x: x0 + m, y: y0 + m + ls * 0.3, color: tc, name: 'side' }); T.setParentWithJump(SV); lc1_op(ctx, T, FA, 1, HS);
        var RL = lc1_S(ctx, 'sleeve rule', 0, 0), rg = lc1_path(RL, 'rule', [[x0 + m, y0 + m + ls * 1.3], [x0 + S - m, y0 + m + ls * 1.3]], tc, Math.max(1, u * 0.0015));
        lc1_trim(ctx, rg, lc1_e(0.15, 0.4, 'oc') + '*K'); RL.setParentWithJump(SV); lc1_op(ctx, RL, 'A*0.8', 1, HS);
        fb = lc1_fitBlock(ctx, t0, font, S - m * 2, S * 0.52, { lead: 1.05, track: 0.01 }, 4);
        var size = Math.min(fb.size, S * 0.3), mh = size * (1 + (fb.lines - 1) * 1.05);
        L = lc1_T(ctx, fb.text, { font: font, size: size, x: x0 + m, y: y0 + S - m - ls * 1.6 - mh / 2, align: 'left', lead: 1.05, track: 0.01, color: tc, name: t0, lyric: true });
        var lb = lc1_bb(L);
        L.setParentWithJump(SV);
        jzAnimate(ctx, L, { mi: lc1_mi(ctx, 0.22), noHold: lc1_plateHold(ctx) });
        T = lc1_T(ctx, lc1_cut(jzRomajiOf(ctx) || jzFmtTime(c.start), 26), { font: mono, size: ls * 0.8, track: 0.15, align: 'left', x: x0 + m, y: y0 + S - m, color: tc, name: 'reading' }); T.setParentWithJump(SV); lc1_op(ctx, T, FA + '*0.8', 1, HS);
        return lc1_box(lb.x0 + sx, lb.y0 + sy, lb.x1 + sx, lb.y1 + sy);
    }
});

/* ================================================================== 14 cassette \u2014 \u30AB\u30BB\u30C3\u30C8 */
jzReg('layout', 'cassette', {
    plan: function (rng, cut, st) {
        return { font: rng.chance(0.55) ? 'klee' : rng.pick(jzFontsOf(st, ['body', 'display'])), shell: rng.pick(['ink', 'accent', 'clear']), band: rng.pick(['accent', 'ink', 'stripe']),
            side: rng.pick(['A', 'B']), tilt: rng.range(-4, 4), len: rng.pick(['C-46', 'C-60', 'C-90']) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), k = lc1_k(ctx), port = jzPortrait(ctx), i, j;
        var font = jzP(ctx, 'font', 'klee'), shell = jzP(ctx, 'shell', 'ink'), band = jzP(ctx, 'band', 'accent'), side = jzP(ctx, 'side', 'A'), tilt = jzP(ctx, 'tilt', 0), ls = lc1_ls(ctx);
        var cw = port ? W * 0.92 : Math.min(W * 0.66, H * 0.8 * 1.58), ch = cw / 1.58, clear = shell === 'clear';
        var shellC = clear ? sc.bg : lc1_plateCol(sc, shell === 'accent' ? [sc.accent, sc.ink] : [sc.ink, sc.fg]), lineC = clear ? sc.fg : jzMixHex(shellC, lc1_onCol(sc, shellC), 0.3), C = lc1_card(sc);
        var labF = jzContrast(C.fill, shellC) > 1.3 ? C.fill : lc1_plateCol(sc, [sc.bg, sc.fg], shellC, 1.5), labT = lc1_onCol(sc, labF);
        var bandC = band === 'ink' ? lc1_plateCol(sc, [sc.ink, sc.fg], labF, 2) : lc1_plateCol(sc, [sc.accent, sc.accent2, sc.ink], labF, 1.6);
        var HC = 'var EIN=oc(time/0.5),EO=ic(PO),A=cl(EIN*2)*(1-EO);';
        var R = lc1_S(ctx, 'cassette', W / 2, H / 2);
        jzSetExpr(jzXf(R, 'ADBE Position'), lc1_H(ctx) + HC + '[value[0],value[1]+(1-EIN)*' + jzN(H * 0.6) + '+EO*' + jzN(H * 0.15) + ']');
        jzSetExpr(jzXf(R, 'ADBE Rotate Z'), lc1_H(ctx) + HC + 'value+' + jzN(tilt) + '*(1-EO)+(1-EIN)*12');
        lc1_op(ctx, R, 'A', 1, HC);
        lc1_shadow(R, sc, u * 0.015);
        var x0 = -cw / 2, y0 = -ch / 2;
        lc1_rect(R, 'shell', cw, ch, 0, 0, shellC, { round: ch * 0.05, stroke: clear ? sc.fg : null, sw: Math.max(2 * k, u * 0.003) });
        var scr = [[x0 + ch * 0.07, y0 + ch * 0.07], [x0 + cw - ch * 0.07, y0 + ch * 0.07], [x0 + ch * 0.07, y0 + ch - ch * 0.07], [x0 + cw - ch * 0.07, y0 + ch - ch * 0.07], [0, y0 + ch * 0.9]], sg = jzGrp(R, 'screws');
        for (i = 0; i < scr.length; i++) jzAddEllipse(sg, ch * 0.044, ch * 0.044, scr[i][0], scr[i][1]);
        jzAddStroke(sg, lineC, Math.max(1, u * 0.0015)); lc1_front(sg);
        var lx = x0 + cw * 0.06, ly = y0 + ch * 0.08, lw2 = cw * 0.88, lh2 = ch * 0.62;
        lc1_rect(R, 'label', lw2, lh2, lx + lw2 / 2, ly + lh2 / 2, labF, { round: ch * 0.02 });
        if (band === 'stripe') { lc1_rect(R, 'stripe 1', lw2, lh2 * 0.06, lx + lw2 / 2, ly + lh2 * 0.65, lc1_plateCol(sc, [sc.accent], labF, 1.4)); lc1_rect(R, 'stripe 2', lw2, lh2 * 0.06, lx + lw2 / 2, ly + lh2 * 0.73, lc1_plateCol(sc, [sc.accent2, sc.ink], labF, 1.4)); }
        else lc1_rect(R, 'band', lw2, lh2 * 0.12, lx + lw2 / 2, ly + lh2 * 0.68, bandC);
        var sb = lh2 * 0.3;
        lc1_rect(R, 'side box', sb, sb, lx + lw2 * 0.03 + sb / 2, ly + lh2 * 0.08 + sb / 2, labT);
        var tx0 = lx + lw2 * 0.06 + sb, tw = lw2 * 0.9 - sb, rl = ly + lh2 * 0.5;
        lc1_path(R, 'writing line', [[tx0, rl], [tx0 + tw, rl]], labT, Math.max(1, u * 0.0012), { op: 0.35 });
        var wy0 = y0 + ch * 0.44;
        lc1_rect(R, 'window', cw * 0.6, ch * 0.24, 0, wy0 + ch * 0.12, jzMixHex(shellC, jzDarkest(sc), 0.35), { round: ch * 0.12 });
        var rr = ch * 0.1, rxs = [-cw * 0.19, cw * 0.19], ry = wy0 + ch * 0.12;
        for (i = 0; i < 2; i++) {
            lc1_ellipse(R, 'tape ' + i, rr * (i ? 0.75 : 1.05) * 2, rr * (i ? 0.75 : 1.05) * 2, rxs[i], ry, jzMixHex(jzDarkest(sc), shellC, 0.2));
            lc1_ellipse(R, 'hub ' + i, rr, rr, rxs[i], ry, labF);
            var sp = jzGrp(R, 'spokes ' + i);
            for (j = 0; j < 6; j++) { var an = j * Math.PI / 3; jzAddPath(sp, [[Math.cos(an) * rr * 0.2, Math.sin(an) * rr * 0.2], [Math.cos(an) * rr * 0.46, Math.sin(an) * rr * 0.46]]); }
            jzAddStroke(sp, labT, Math.max(2 * k, rr * 0.08));
            sp = lc1_front(sp);
            jzGX(sp).property('ADBE Vector Position').setValue([rxs[i], ry]);
            lc1_gRot(ctx, sp, 'time*200+ic(PO)*time*600');
        }
        var T = lc1_T(ctx, side, { font: 'gothic_black', size: sb * 0.78, x: lx + lw2 * 0.03 + sb / 2, y: ly + lh2 * 0.08 + sb / 2, color: labF, name: 'side' }); T.setParentWithJump(R); lc1_op(ctx, T, 'A', 1, HC);
        T = lc1_T(ctx, jzP(ctx, 'len', 'C-60') + '  \u00B7  NR  \u00B7  No.' + jzLineNo(ctx), { font: jzMonoF(ctx), size: ls * 0.75, track: 0.2, align: 'right', x: lx + lw2 * 0.97, y: ly + lh2 * 0.9, color: labT, name: 'tape info' });
        T.setParentWithJump(R); lc1_op(ctx, T, 'A*0.8', 1, HC);
        // lyric handwritten on the top line
        var t0 = jzTrim(c.text), fb = lc1_fitBlock(ctx, t0, font, tw, lh2 * 0.46, { lead: 1.05 }, 2), size = Math.min(fb.size, lh2 * 0.32), mh = size * (1 + (fb.lines - 1) * 1.05);
        var L = lc1_T(ctx, fb.text, { font: font, size: size, x: tx0 + size * 0.1, y: rl - mh / 2 - size * 0.08, align: 'left', lead: 1.05, rot: -1.2, color: labT, name: t0, lyric: true });
        L.setParentWithJump(R);
        jzAnimate(ctx, L, { mi: lc1_mi(ctx, 0.28), noHold: lc1_plateHold(ctx) });
        return lc1_box(W / 2 - cw / 2, H / 2 - ch / 2, W / 2 + cw / 2, H / 2 + ch / 2);
    }
});

/* ================================================================== 15 bookSpine \u2014 \u80CC\u8868\u7D19 */
jzReg('layout', 'bookSpine', {
    plan: function (rng, cut, st) {
        var port = cut.H > cut.W * 1.08, nb = port ? rng.int(5, 7) : rng.int(8, 12);
        var font = rng.pick(jzFontsOf(st, ['serif', 'display'])), variant = jzHasLatin(cut.text) ? 'pile' : rng.pick(['shelf', 'shelf', 'pile']), books = [];
        for (var i = 0; i < nb; i++) books.push([rng.range(0.5, 1), rng.range(0.62, 0.92), rng.int(0, 5), rng.int(0, 3)]);
        return { font: font, variant: variant, books: books, hero: rng.pick(['accent', 'ink']), vol: rng.int(1, 24) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), k = lc1_k(ctx), port = jzPortrait(ctx), i;
        var font = jzP(ctx, 'font', jzSerifF(ctx)), books = jzP(ctx, 'books', null), vol = jzP(ctx, 'vol', 3), t0 = jzTrim(c.text);
        if (!books || !books.length) { books = []; for (i = 0; i < (port ? 6 : 10); i++) books.push([0.5 + 0.5 * jzR(c.seed, i, 1), 0.62 + 0.3 * jzR(c.seed, i, 2), Math.floor(jzR(c.seed, i, 3) * 6), Math.floor(jzR(c.seed, i, 4) * 4)]); }
        var heroC = lc1_plateCol(sc, jzP(ctx, 'hero', 'accent') === 'accent' ? [sc.accent, sc.ink] : [sc.ink, sc.accent]), htc = lc1_onCol(sc, heroC);
        var pal = [jzMixHex(sc.bg, sc.fg, 0.18), jzMixHex(sc.bg, sc.sub, 0.45), jzMixHex(sc.bg, sc.accent, 0.35), sc.dim || jzMixHex(sc.bg, sc.fg, 0.25), jzMixHex(sc.bg, sc.fg, 0.32), jzMixHex(sc.bg, sc.accent2 || sc.sub, 0.3)];
        var bb = null, L, T, nb, hi;
        if (jzP(ctx, 'variant', 'shelf') === 'pile' || jzHasLatin(t0)) {
            // books lying flat, spines facing us; the hero sits mid-pile
            var bw = port ? W * 0.84 : Math.min(W * 0.62, H * 1.1), fb = lc1_fitBlock(ctx, t0, font, bw * 0.78, H * 0.16, { track: 0.04 }, 1), size = Math.min(fb.size, u * 0.12), hh = size * 1.7;
            nb = Math.min(6, books.length); hi = Math.floor(nb / 2);
            var hs = [], total = 0;
            for (i = 0; i < nb; i++) { hs.push(i === hi ? hh : H * 0.045 + books[i][0] * H * 0.05); total += hs[i]; }
            var y = H / 2 + total / 2 + H * 0.03, flo = y;
            var FL = lc1_line(ctx, 'floor', [[W * 0.08, flo], [W * 0.92, flo]], sc.sub, Math.max(2 * k, u * 0.003)); lc1_fade(ctx, FL, 0, 0.4, 1, 'oe');
            for (i = 0; i < nb; i++) {
                var b = books[i], h = hs[i], w = i === hi ? bw : bw * (0.72 + 0.26 * b[1]), xo = (jzR(b[2], i, 3) * 2 - 1) * bw * 0.05, t = 0.04 + i * 0.09;
                y -= h;
                var col = i === hi ? heroC : pal[b[2] % pal.length], bc = i === hi ? htc : jzMixHex(col, sc.fg, 0.35);
                var HB = 'var E=oc((time-' + jzN(t) + ')/0.3),EO=ic(PO);';
                var BK = lc1_S(ctx, i === hi ? 'hero book' : 'book ' + (i + 1), W / 2 + xo, y + h / 2);
                lc1_rect(BK, 'cover', w, h - 2, 0, -1, col);
                lc1_rect(BK, 'band L', Math.max(2 * k, w * 0.008), h - 2, -w / 2 + w * 0.06 + Math.max(2 * k, w * 0.008) / 2, -1, bc, { op: 0.7 });
                lc1_rect(BK, 'band R', Math.max(2 * k, w * 0.008), h - 2, w / 2 - w * 0.07 + Math.max(2 * k, w * 0.008) / 2, -1, bc, { op: 0.7 });
                if (i !== hi && h > H * 0.05) lc1_rect(BK, 'title', w * 0.3 * b[1], h * 0.16, -w * 0.22 + w * 0.15 * b[1], 0, jzMixHex(col, sc.fg, 0.3), { op: 0.7 });
                jzSetExpr(jzXf(BK, 'ADBE Position'), lc1_H(ctx) + HB + '[value[0],value[1]-(1-E)*' + jzN(H * 0.6) + '-EO*' + jzN((nb - i) * H * 0.02) + ']');
                lc1_op(ctx, BK, 'cl(E*3)*(1-EO)', 1, HB);
                if (i === hi) {
                    T = lc1_T(ctx, jzPad(vol, 2), { font: jzMonoF(ctx), size: h * 0.24, x: w / 2 - w * 0.035, y: 0, rot: -90, color: htc, name: 'volume' }); T.setParentWithJump(BK); lc1_op(ctx, T, 'cl(E*3)*(1-EO)', 1, HB);
                    L = lc1_T(ctx, fb.text, { font: font, size: size, x: -w * 0.02, y: 0, track: 0.04, color: htc, name: t0, lyric: true });
                    L.setParentWithJump(BK);
                    jzAnimate(ctx, L, { mi: lc1_mi(ctx, 0.1 + hi * 0.09 + 0.2), noHold: lc1_plateHold(ctx) });
                    bb = lc1_box(W / 2 + xo - w / 2, y, W / 2 + xo + w / 2, y + h);
                }
            }
            return bb;
        }
        // shelf of standing books rising from the shelf board
        var n = jzCount(t0), cols = n > (port ? 12 : 10) ? 2 : 1, vt = cols > 1 ? lc1_brk(jzStrip(t0), Math.ceil(n / 2)) : jzStrip(t0), vl = lc1_lines(vt), per = 1;
        for (i = 0; i < vl.length; i++) per = Math.max(per, jzCount(vl[i]));
        var shelfY = H * (port ? 0.84 : 0.88), maxH = H * (port ? 0.7 : 0.78);
        var sz = Math.min((maxH * 0.76) / (Math.max(per, 3) * 1.02 + 1.9), u * (cols > 1 ? 0.09 : port ? 0.14 : 0.115));
        var hw = sz * (cols > 1 ? 2.9 : 1.75), hh2 = Math.min(maxH, (per * sz * 1.02 + sz * 1.9) / 0.76);
        nb = books.length; hi = Math.floor(nb / 2);
        var ws = [], tot = 0;
        for (i = 0; i < nb; i++) { ws.push(i === hi ? hw : sz * (0.9 + 0.8 * books[i][0])); tot += ws[i] + 2; }
        var x = W / 2 - tot / 2, th = Math.max(4 * k, u * 0.008);
        var SHV = lc1_bar(ctx, 'shelf', W * 0.03, shelfY + th / 2, W * 0.94, th, sc.sub, lc1_e(0, 0.4) + '*K');
        var BS = lc1_S(ctx, 'books', 0, 0), hx = 0, hwid = 0, hht = 0, HX = '';
        for (i = 0; i < nb; i++) {
            var bk = books[i], wd = ws[i], hb = i === hi ? hh2 : Math.min(maxH, hh2 * (0.7 + 0.3 * bk[1])), bx = x, top = shelfY - hb;
            x += wd + 2;
            if (bx + wd < 0 || bx > W) continue;
            var cl0 = i === hi ? heroC : pal[bk[2] % pal.length], bcol = i === hi ? htc : jzMixHex(cl0, sc.fg, 0.3), bH = Math.max(2 * k, hb * 0.012), fr = [0.06, 0.08, 0.92, 0.94];
            // parts in drawing order; added in reverse (AE draws the first sub-group on top)
            var parts = [['r', wd, hb, bx + wd / 2, top + hb / 2, cl0, 100]];
            for (var q = 0; q < 4; q++) parts.push(['r', wd, bH, bx + wd / 2, top + hb * fr[q] + bH / 2, bcol, 75]);
            if (i === hi) {
                parts.push(['r', wd * 0.6, sz * 0.8, bx + wd / 2, top + hb * 0.11 + sz * 0.4, htc, 100]);
                parts.push(['o', sz * 0.52, sz * 0.52, bx + wd / 2, top + hb * 0.87 - sz * 0.3, htc, Math.max(1.5 * k, sz * 0.04)]);
                hx = bx; hwid = wd; hht = hb;
            } else {
                var tl = hb * (0.25 + 0.3 * bk[1]);
                if (bk[3] > 0) parts.push(['r', wd * 0.24, tl, bx + wd / 2, top + hb * 0.16 + tl / 2, bcol, 55]);
                if (bk[3] === 2) parts.push(['e', wd * 0.28, wd * 0.28, bx + wd / 2, top + hb * 0.84, bcol, 55]);
            }
            var g = jzGrp(BS, i === hi ? 'hero book' : 'book ' + (i + 1));
            for (q = parts.length - 1; q >= 0; q--) {
                var pp = parts[q], pg = lc1_sub(g, 'part ' + q);
                if (pp[0] === 'r') jzAddRect(pg, pp[1], pp[2], 0, pp[3], pp[4]); else jzAddEllipse(pg, pp[1], pp[2], pp[3], pp[4]);
                if (pp[0] === 'o') jzAddStroke(pg, pp[5], pp[6]); else jzAddFill(pg, pp[5], pp[6]);
            }
            g = lc1_front(g);
            var BE = 'var E=oe((time-' + jzN(0.03 + Math.abs(i - hi) * 0.05) + ')/0.4),EO=ic(PO),LF=' + (i === hi ? 'ioc((time-0.35)/0.4)*' + jzN(sz * 0.6) + '*(1-EO)' : '0') + ';';
            jzSetExpr(jzGX(g).property('ADBE Vector Position'), lc1_H(ctx) + BE + '[value[0],value[1]+' + jzN(hb) + '*(1-E)-LF+EO*' + jzN(hb * 1.05) + ']');
            if (i === hi) HX = BE;
        }
        jzMaskRect(BS, -W, -H, W * 2, shelfY);
        lc1_op(ctx, BS, '1-ic(PO)*0.4', 1);
        SHV.moveBefore(BS);
        // hero texts ride on a null that follows the hero book; track mattes clip them at the shelf board
        var N = lc1_null(ctx, 'hero book', 0, 0), htop = shelfY - hht;
        jzSetExpr(jzXf(N, 'ADBE Position'), lc1_H(ctx) + HX + '[value[0],value[1]+' + jzN(hht) + '*(1-E)-LF+EO*' + jzN(hht * 1.05) + ']');
        function clip(Lx) { var M = lc1_S(ctx, 'shelf clip', 0, 0); lc1_rect(M, 'clip', W * 3, H + shelfY, W / 2, (shelfY - H) / 2, '#FFFFFF'); M.moveBefore(Lx); lc1_matte(Lx, M); }
        T = lc1_T(ctx, lc1_kanjiNum(vol), { font: jzSerifF(ctx), size: sz * 0.46, x: hx + hwid / 2, y: htop + hht * 0.11 + sz * 0.4, color: heroC, name: 'volume' });
        T.setParentWithJump(N); lc1_op(ctx, T, '1-ic(PO)*0.4', 1); clip(T);
        var V = lc1_vcols(ctx, vl, { font: font, size: sz, x: hx + hwid / 2, top: htop + hht * 0.11 + sz * 1.2, lead: 1.24, track: 0.02, color: htc, name: t0, lyric: true });
        V.L.setParentWithJump(N);
        jzAnimate(ctx, V.L, { mi: lc1_mi(ctx, 0.3), noHold: lc1_plateHold(ctx) });
        clip(V.L);
        return lc1_box(hx, htop - sz * 0.6, hx + hwid, shelfY);
    }
});

/* ================================================================== 16 polaroid \u2014 \u30DD\u30E9\u30ED\u30A4\u30C9 */
jzReg('layout', 'polaroid', {
    plan: function (rng, cut, st) {
        var n = cut.n, kk = n <= 5 ? 1 : n <= 10 ? 2 : 3;
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), pen: rng.chance(0.6) ? 'klee' : rng.pick(jzFontsOf(st, ['body'])), chunks: lc1_splitK(cut.text, kk, kk),
            tilts: [rng.range(-7, 7), rng.range(-7, 7), rng.range(-7, 7)], img: rng.pick(['dark', 'accent', 'dusk']), tape: rng.chance(0.5) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), k = lc1_k(ctx), port = jzPortrait(ctx), i;
        var chunks = lc1_chunks(ctx), nk = chunks.length, font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), penF = jzP(ctx, 'pen', 'klee'), tilts = jzP(ctx, 'tilts', [3, -4, 2]), img = jzP(ctx, 'img', 'dark');
        var C = lc1_card(sc), fw;
        if (port) fw = Math.min(W * (nk === 1 ? 0.78 : 0.62), H * 0.78 / (nk === 1 ? 1.2 : 1 + (nk - 1) * 0.72) / 1.2);
        else fw = Math.min(H * 0.8 / 1.2, W * 0.86 / (nk === 1 ? 1 : nk * 0.92));
        var fh = fw * 1.2, iw = fw * 0.88, lite = jzLightest(sc);
        var imgC = img === 'accent' ? lc1_plateCol(sc, [sc.accent, sc.ink], C.fill, 1.8) : img === 'dusk' ? jzMixHex(jzDarkest(sc), sc.accent, 0.35) : jzDarkest(sc), tc = lc1_onCol(sc, imgC), bb = null;
        for (i = 0; i < nk; i++) {
            var x = nk === 1 ? W / 2 : port ? W / 2 + (i % 2 ? 1 : -1) * W * 0.07 : W / 2 + (i - (nk - 1) / 2) * fw * 0.92;
            var y = nk === 1 ? H / 2 : port ? H / 2 + (i - (nk - 1) / 2) * fh * 0.72 : H / 2 + (i % 2 ? 1 : -1) * H * 0.025;
            var t = 0.02 + i * 0.16, rot = tilts[i % 3] * (nk === 1 ? 0.6 : 1);
            var HP = 'var E=oc((time-' + jzN(t) + ')/0.4),EO=ic(PO),A=cl(E*2.5)*(1-EO),DEV=ioc((time-' + jzN(t + 0.1) + ')/0.9);';
            var F = lc1_S(ctx, 'polaroid ' + (i + 1), x, y);
            jzSetExpr(jzXf(F, 'ADBE Position'), lc1_H(ctx) + HP + '[value[0],value[1]-(1-E)*' + jzN(H * 0.15) + '+EO*' + jzN(H * 0.1 * (i + 1)) + ']');
            jzXf(F, 'ADBE Rotate Z').setValue(rot);
            jzSetExpr(jzXf(F, 'ADBE Rotate Z'), lc1_H(ctx) + HP + 'value+(1-E)*14');
            lc1_op(ctx, F, 'A', 1, HP);
            lc1_shadow(F, sc, u * 0.014);
            lc1_rect(F, 'frame', fw, fh, 0, 0, C.fill, C.edge ? { stroke: C.line, sw: 1.2 * k, round: fw * 0.01 } : { round: fw * 0.01 });
            var iy = -fh / 2 + fw * 0.06, pic = lc1_rect(F, 'picture', iw, iw, 0, iy + iw / 2, imgC);
            jzSetExpr(lc1_fillOf(pic).property('ADBE Vector Fill Color'), lc1_H(ctx) + HP + lc1_mixExpr(jzMixHex(C.fill, sc.sub, 0.35), imgC, 'DEV'));
            var l1 = lc1_ellipse(F, 'light leak', iw * 0.6, iw * 0.6, -iw / 2 + iw * 0.72, iy + iw * 0.26, lite); lc1_gOp(ctx, l1, '0.07*DEV', HP);
            var l2 = lc1_ellipse(F, 'light leak core', iw * 0.32, iw * 0.32, -iw / 2 + iw * 0.72, iy + iw * 0.26, lite); lc1_gOp(ctx, l2, '0.08*DEV', HP);
            if (jzP(ctx, 'tape', false)) lc1_rect(F, 'tape', fw * 0.32, fw * 0.1, 0, -fh / 2, lite, { op: 0.55 });
            var fb = lc1_fitBlock(ctx, chunks[i], font, iw * 0.84, iw * 0.7, { lead: 1.1 }, 3);
            var L = lc1_T(ctx, fb.text, { font: font, size: Math.min(fb.size, iw * 0.34), x: 0, y: iy + iw / 2, lead: 1.1, color: tc, name: chunks[i], lyric: true });
            L.setParentWithJump(F);
            jzAnimator(L, 'JZ Develop', [['ADBE Text Opacity', 0]], lc1_H(ctx) + HP + '(1-(0.25+0.75*DEV))*100');
            jzAnimate(ctx, L, { mi: lc1_mi(ctx, t + 0.15), noHold: lc1_plateHold(ctx) });
            var cap = i === nk - 1 ? (jzRomajiOf(ctx) || jzFmtTime(c.start)) : ('No.' + jzLineNo(ctx) + '-' + (i + 1));
            cap = lc1_cut(cap, 20);
            var CT = lc1_T(ctx, cap, { font: penF, size: Math.min(fw * 0.07, (fw * 0.84) / Math.max(6, cap.length * 0.62)), x: 0, y: iy + iw + (fh / 2 - iy - iw) * 0.5, rot: -2, color: C.text, name: 'caption ' + (i + 1) });
            CT.setParentWithJump(F); lc1_op(ctx, CT, 'cl((time-' + jzN(t + 0.5) + ')/0.4)*A*0.85', 1, HP);
            bb = jzUnion(bb, lc1_box(x - fw / 2, y - fh / 2, x + fw / 2, y + fh / 2));
        }
        return bb;
    }
});

// ================================================================ pack layoutsC part 2 (AE port of src/11p_layoutsC.js, entries 17-34)
// stampSheet, postcard, letterPaper, calendar, chochin, routeMap, stationSign, noren, tanzaku, omikuji, kakejiku, shoji,
// clapper, warningLabel, priceTag, nameTag, stickyNotes, karuta
//
// Techniques:
// - paper objects are shape layers ("rigs") whose transform expressions carry the object's motion (fly in, swing, tear\u2026);
//   their text layers are parented to the rig, so the lyric keeps the cut's enter / hold / exit on its own layer.
// - glyph positions are estimated like the browser's layoutText (full-width glyphs = 1 em), used for fitting and placing.
// - vertical columns (right \u2192 left) = one text layer with one glyph per line + a per-glyph Position animator (lc2_V).
// - many small labels (zip digits, dates, station codes \u2026) = one text layer, one label per line, moved per glyph (lc2_multi).
// - clip windows that open (folded paper, unrolling scroll) = a thin static rectangle mask + animated Mask Expansion.
// - dotted lines / perforations = stroke with dash 0 and round caps.

var LC2_CY = 0.38;        // glyph centre above the baseline (em)
var LC2_FX = 'function ioc(x){x=cl(x);return x<0.5?4*x*x*x:1-Math.pow(-2*x+2,3)/2;}function lp(a,b,t){return a+(b-a)*t;}\n';
var LC2_WD_J = '\u65E5\u6708\u706B\u6C34\u6728\u91D1\u571F', LC2_WD_E = ['SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT'];
var LC2_MON_E = ['JANUARY', 'FEBRUARY', 'MARCH', 'APRIL', 'MAY', 'JUNE', 'JULY', 'AUGUST', 'SEPTEMBER', 'OCTOBER', 'NOVEMBER', 'DECEMBER'];
var LC2_ROKUYO = ['\u5927\u5B89', '\u8D64\u53E3', '\u5148\u52DD', '\u53CB\u5F15', '\u5148\u8CA0', '\u4ECF\u6EC5'];
var LC2_KUJI = ['\u5927\u5409', '\u5409', '\u4E2D\u5409', '\u5C0F\u5409', '\u672B\u5409', '\u5927\u5409'];
var LC2_KUJI_CAT = ['\u9858\u671B', '\u5F85\u4EBA', '\u5931\u7269', '\u65C5\u884C', '\u5546\u58F2', '\u5B66\u554F', '\u604B\u611B', '\u5065\u5EB7'];
var LC2_WARN = [['WARNING', '\u8B66\u544A'], ['CAUTION', '\u6CE8\u610F'], ['DANGER', '\u5371\u967A'], ['NOTICE', '\u304A\u77E5\u3089\u305B']];
var LC2_KNUM = '\u3007\u4E00\u4E8C\u4E09\u56DB\u4E94\u516D\u4E03\u516B\u4E5D';

/* ---------------------------------------------------------------- text splitting (browser splitK / charUnits / brk) */
function lc2_isSmall(c) { return '\u3041\u3043\u3045\u3047\u3049\u3063\u3083\u3085\u3087\u308E\u30A1\u30A3\u30A5\u30A7\u30A9\u30C3\u30E3\u30E5\u30E7\u30EE\u30F5\u30F6'.indexOf(c) >= 0 && c.length > 0; }
function lc2_isPunct(c) { return /[\u3001\u3002\uFF0C\uFF0E,.!?\uFF01\uFF1F\u2026\u2025\u30FB\u300C\u300D\u300E\u300F\uFF08\uFF09()\u3010\u3011\u3008\u3009\u300A\u300B\u3014\u3015\uFF3B\uFF3D\[\]'"\u201C\u201D\u2018\u2019\u30FC\u301C\uFF5E:\uFF1A;\uFF1B\-\u2014\u2015]/.test(c); }
function lc2_isBad(c) { return lc2_isSmall(c) || lc2_isPunct(c) || c === '\u30FC' || c === ' '; }
var lc2_VROT = '\u30FC\u301C\uFF5E\u2026\u2025\u2015\u2014-()\uFF08\uFF09\u300C\u300D\u300E\u300F\u3010\u3011\u3008\u3009\u300A\u300B\u3014\u3015[]\uFF3B\uFF3D\u2192\u2190:\uFF1A;\uFF1B=\uFF1D';
// chunk boundaries (glyph offsets) of a word \u2014 stands in for the browser's Intl word segments
function lc2_bounds(word) {
    var out = {}, ch = jzChunk(word), acc = 0;
    for (var i = 0; i < ch.length; i++) { acc += jzChars(ch[i]).length; out[acc] = 1; }
    return out;
}
function lc2_split2(word, force) {
    var ch = jzChars(word), n = ch.length, sb = lc2_bounds(word), best = Math.max(1, Math.floor(n / 2)), bs = -1e9;
    for (var c = 1; c < n; c++) {
        var a = ch[c - 1], b = ch[c];
        if (!force && ((c === 1 && !jzIsKanji(a)) || (c === n - 1 && !jzIsKanji(b)))) continue;
        var s = -Math.abs(c - n / 2) * 0.9;
        if (a === ' ' || a === '\u3000' || (lc2_isPunct(a) && a !== '\u30FC')) s += 5;
        if (jzIsHira(a) && !jzIsHira(b) && !lc2_isBad(b)) s += 3;
        if (sb[c]) s += 2;
        if (lc2_isBad(b)) s -= 6;
        if (jzIsKanji(a) && jzIsKanji(b)) s -= 2;
        if (jzIsKanji(a) && jzIsHira(b)) s -= sb[c] ? 0.5 : 2.5;
        if (jzIsHira(a) && jzIsHira(b) && !sb[c]) s -= 2.5;
        if (s > bs) { bs = s; best = c; }
    }
    if (!force && bs < -1.5 && !jzHasLatin(word)) return [word];
    var out = [], p1 = jzTrim(ch.slice(0, best).join('')), p2 = jzTrim(ch.slice(best).join(''));
    if (p1) out.push(p1);
    if (p2) out.push(p2);
    return out;
}
// split text into k balanced chunks (browser splitK): word chunks first, long chunks cut at the most natural boundary
function lc2_splitK(text, k, force) {
    var t = jzTrim(String(text || '')), i;
    if (!t) return [''];
    // words (latin: by spaces; Japanese: chunks of each space-separated phrase); sp[i] = a space follows word i
    var latin = jzHasLatin(t), pcs = t.split(/\s+/), words = [], sp = [];
    for (var pi = 0; pi < pcs.length; pi++) {
        var raw = latin ? [pcs[pi]] : jzChunk(pcs[pi]);
        for (i = 0; i < raw.length; i++) { var w0 = jzTrim(raw[i]); if (w0) { words.push(w0); sp.push(false); } }
        if (!latin && sp.length) sp[sp.length - 1] = pi < pcs.length - 1;
    }
    if (!words.length) { words = [t]; sp = [false]; }
    k = Math.max(1, Math.min(k, jzCount(t)));
    var hard = {}, parts;
    for (var guard = 0; words.length < k && guard < 16; guard++) {
        var bi = -1, bl = 1;
        for (i = 0; i < words.length; i++) { var l = jzCount(words[i]); if (l > bl && !hard[words[i]]) { bl = l; bi = i; } }
        if (bi < 0) break;
        parts = latin ? [words[bi]] : lc2_split2(words[bi]);
        if (parts.length < 2 && force && words.length < force) parts = lc2_split2(words[bi], true);
        if (parts.length < 2) { hard[words[bi]] = 1; continue; }
        sp.splice(bi, 1, false, sp[bi]); words.splice(bi, 1, parts[0], parts[1]);
    }
    var sep = latin ? ' ' : '';
    if (k <= 1) return [words.join(sep)];
    if (words.length <= k) return words;
    function part(ws) {
        var pre = [0], n = ws.length, j;
        for (j = 0; j < n; j++) pre.push(pre[j] + jzCount(ws[j]) + 0.5);
        var tgt = pre[n] / k, best = null, bestS = 1e18, cnt = 0;
        function rec(start, g, cuts) {
            if (++cnt > 5000) return;
            if (g === k - 1) {
                var s = 0, prev = 0, all = cuts.concat([n]);
                for (var q = 0; q < all.length; q++) { var d = pre[all[q]] - pre[prev] - tgt; s += d * d; prev = all[q]; if (q < all.length - 1 && sp[all[q] - 1]) s -= tgt * tgt * 0.25; }
                if (s < bestS) { bestS = s; best = all; }
                return;
            }
            for (var c = start + 1; c <= n - (k - 1 - g); c++) rec(c, g + 1, cuts.concat([c]));
        }
        rec(0, 0, []);
        var out = [], prv = 0;
        if (!best) { for (j = 0; j < n; j++) out.push([ws[j]]); return out; }
        for (j = 0; j < best.length; j++) { out.push(ws.slice(prv, best[j])); prv = best[j]; }
        return out;
    }
    var groups = part(words);
    for (var it = 0; it < 3; it++) {
        var gl = [], mx = -1, mn = 1e9, gi = 0;
        for (i = 0; i < groups.length; i++) { var sum = 0; for (var q2 = 0; q2 < groups[i].length; q2++) sum += jzCount(groups[i][q2]); gl.push(sum); if (sum > mx) { mx = sum; gi = i; } if (sum < mn) mn = sum; }
        if (mx <= mn * 1.7 + 1) break;
        var g0 = groups[gi], bw = null;
        for (i = 0; i < g0.length; i++) { if (!hard[g0[i]] && jzCount(g0[i]) >= 3 && (!bw || jzCount(g0[i]) > jzCount(bw))) bw = g0[i]; }
        if (!bw) break;
        parts = latin ? [bw] : lc2_split2(bw);
        if (parts.length < 2) { hard[bw] = 1; continue; }
        var wi = jzIndexOf(words, bw);
        sp.splice(wi, 1, false, sp[wi]); words.splice(wi, 1, parts[0], parts[1]);
        groups = part(words);
    }
    var res = [];
    for (i = 0; i < groups.length; i++) res.push(groups[i].join(sep));
    return res;
}
// single glyphs, small kana / punctuation / \u30FC kept on the preceding glyph
function lc2_charUnits(text) {
    var a = jzChars(jzStrip(text)), out = [];
    for (var i = 0; i < a.length; i++) { if (out.length && lc2_isBad(a[i])) out[out.length - 1] += a[i]; else out.push(a[i]); }
    return out;
}
// balanced display line breaks at chunk boundaries (AE line break = \r)
function lc2_brk(text, maxPer) {
    var t = jzTrim(String(text || '')), n = jzCount(t);
    if (n <= maxPer) return t;
    return lc2_splitK(t, Math.ceil(n / maxPer)).join('\n');
}

/* ---------------------------------------------------------------- small helpers */
function lc2_H(ctx) { return jzTH(ctx) + LC2_FX; }
function lc2_k(ctx) { return jzU(ctx) / 1080; }               // browser design px -> comp px
function lc2_vt(t) { return jzHasLatin(t) ? jzFlat(t) : jzStrip(t); }
function lc2_rom(t) { if (!t || jzHasLatin(t)) return null; var r = jzRomaji(jzStrip(t)); return r ? r.toUpperCase() : null; }
function lc2_deck(ctx) { var c = ctx.cut; if (c.lineText && jzStrip(c.lineText) !== jzStrip(c.text)) return jzFlat(c.lineText); return c.note || jzRomajiOf(ctx) || null; }
function lc2_hold(ctx) { return jzIndexOf(['still', 'jitter', 'breathe', 'glitchtick'], ctx.cut.hold || 'still') < 0; }
function lc2_mi(ctx, t) { return Math.max(0, t) / Math.max(0.005, ctx.cut.stagger || 0.04); }
function lc2_box4(x0, y0, x1, y1) { return { x0: x0, y0: y0, x1: x1, y1: y1, cx: (x0 + x1) / 2, cy: (y0 + y1) / 2 }; }
function lc2_port(cut) { return cut.H > cut.W * 1.08; }
function lc2_kanjiNum(n) {
    n = Math.max(0, Math.floor(n));
    var K = LC2_KNUM;
    if (n < 10) return K.charAt(n);
    if (n < 20) return '\u5341' + (n % 10 ? K.charAt(n % 10) : '');
    if (n < 100) return K.charAt(Math.floor(n / 10)) + '\u5341' + (n % 10 ? K.charAt(n % 10) : '');
    return String(n);
}
function lc2_charUnitsOf(text) { return lc2_charUnits(text); }
// the lyric: cut motion + treatment, entrance starting at local time t (browser miAt)
// ghosts: everything this pack draws besides the lyric is ghost-off in the browser (plates, papers, labels \u2026), so every
// helper layer is created with jzNoGhost; the lyric (and only the lyric) passes through lc2_main, which clears the mark
function lc2_ghostOn(L) { try { L.comment = String(L.comment || '').replace(/\s*JZ_NOGHOST/g, ''); } catch (e) {} return L; }
function lc2_main(ctx, L, t, o) {
    o = o || {};
    lc2_ghostOn(L);
    return jzAnimate(ctx, L, { mi: lc2_mi(ctx, t || 0), noHold: o.noHold != null ? o.noHold : lc2_hold(ctx), treat: o.treat });
}

/* ---------------------------------------------------------------- colour (scheme colours only) */
function lc2_onCol(sc, fill) {
    var c = [sc.bg, sc.fg, sc.ink, sc.accent, sc.sub], best = null, bv = 0;
    for (var i = 0; i < c.length; i++) { if (!c[i] || String(c[i]).toUpperCase() === String(fill).toUpperCase()) continue; var k = jzContrast(c[i], fill); if (k > bv) { bv = k; best = c[i]; } }
    return bv >= 2.4 ? best : (jzLum(fill) > 0.5 ? '#111111' : '#FFFFFF');
}
function lc2_plate(sc, pref, against, min) {
    var b = against || sc.bg; if (min == null) min = 1.6;
    for (var i = 0; i < pref.length; i++) if (pref[i] && jzContrast(pref[i], b) >= min) return pref[i];
    return jzContrast(sc.fg, b) >= jzContrast(sc.bg, b) ? sc.fg : sc.bg;
}
function lc2_card(sc) {
    var fill = jzLightest(sc), text = lc2_onCol(sc, fill);
    return { fill: fill, edge: jzContrast(fill, sc.bg) < 1.4, text: text, acc: lc2_plate(sc, [sc.accent, sc.accent2, sc.ink], fill, 2), faint: jzMixHex(fill, text, 0.22), line: jzMixHex(fill, text, 0.35) };
}
function lc2_shCol(sc) { return jzDarkest(sc); }
function lc2_shOp(sc) { return jzLum(sc.bg) > 0.5 ? 22 : 50; }

/* ---------------------------------------------------------------- text metrics (estimates like the browser's layoutText) */
function lc2_adv(ch, mono) {
    var c = ch.charCodeAt(0);
    if (ch === ' ') return mono ? 0.6 : 0.3;
    if (c < 0x80) {
        if (mono) return 0.6;
        if (/[A-Z]/.test(ch)) return /[MW]/.test(ch) ? 0.9 : (ch === 'I' ? 0.32 : 0.7);
        if (/[a-z]/.test(ch)) return /[mw]/.test(ch) ? 0.88 : /[ijl]/.test(ch) ? 0.28 : /[frt]/.test(ch) ? 0.42 : 0.61;
        if (/[0-9]/.test(ch)) return 0.62;
        return /[.,:;!'|]/.test(ch) ? 0.28 : 0.42;
    }
    if (c < 0x250) return 0.56;
    if (c >= 0xFF61 && c <= 0xFF9F) return 0.5;
    return 1;
}
function lc2_vadv(ch, size) { return /[A-Za-z0-9]/.test(ch) ? lc2_adv(ch) * size : size; }
function lc2_lines(str) { return String(str).split(/\r\n|\r|\n/); }
// { w, h } of a block (o: vertical, lead (em), track (em), mono)
function lc2_meas(str, size, o) {
    o = o || {};
    var tr = (o.track || 0) * size, lead = o.lead || 1.3, ls = lc2_lines(str), nL = ls.length, mx = 1, i, j, a, w;
    for (i = 0; i < nL; i++) {
        a = jzChars(ls[i]); w = 0;
        for (j = 0; j < a.length; j++) w += (o.vertical ? lc2_vadv(a[j], size) : lc2_adv(a[j], o.mono) * size) + (j < a.length - 1 ? tr : 0);
        mx = Math.max(mx, w);
    }
    var cross = nL * lead * size - (lead - 1) * size;
    return o.vertical ? { w: cross, h: mx } : { w: mx, h: cross };
}
function lc2_fitSize(str, aw, ah, o) { var m = lc2_meas(str, 100, o); return 100 * Math.min(aw / Math.max(1, m.w), ah / Math.max(1, m.h)); }
// best line break + size for a block that must fit aw x ah (browser fitBlock); text uses '\n'
function lc2_fitBlock(text, aw, ah, o, maxLines) {
    var t = jzTrim(String(text || '')), n = Math.max(1, jzCount(t)), best = null, seen = {}, L, i;
    maxLines = maxLines || 4;
    var cands = [];
    for (L = 1; L <= Math.min(maxLines, n); L++) {
        var s0 = L === 1 ? t : lc2_brk(t, Math.ceil(n / L));
        cands.push(s0);
        // the browser's word segmenter splits short kana words that the chunker keeps whole: add a forced split too
        if (L > 1 && lc2_lines(s0).length < L && !jzHasLatin(t)) cands.push(lc2_splitK(t, L, L).join('\n'));
    }
    for (var ci = 0; ci < cands.length; ci++) {
        var s = cands[ci];
        if (seen['k' + s]) continue; seen['k' + s] = 1;
        var ls = lc2_lines(s), lines = ls.length;
        if (lines > maxLines) continue;
        var size = lc2_fitSize(s, aw, ah, o), lone = false;
        if (lines > 1 && n > 2) for (i = 0; i < ls.length; i++) if (jzCount(ls[i]) < 2) lone = true;
        var score = size * (1 - 0.06 * (lines - 1)) * (lone ? 0.8 : 1);
        if (!best || score > best.score) best = { text: s, size: size, lines: lines, score: score };
    }
    return best || { text: t, size: lc2_fitSize(t, aw, ah, o), lines: 1, score: 0 };
}

/* ---------------------------------------------------------------- text layers */
// horizontal block: x = centre / left / right edge (o.align), y = vertical centre of the block (browser item semantics)
function lc2_T(ctx, str, o) {
    var size = o.size, lead = o.lead || 1.3, txt = String(str).replace(/\r\n|\n/g, '\r'), nL = txt.split('\r').length, LD = size * lead;
    var L = jzNoGhost(jzText(ctx, txt, { font: o.font, size: size, color: o.color || ctx.sc.fg, x: o.x, y: o.y, align: o.align, track: o.track || 0, leading: nL > 1 ? LD : null,
        name: o.name, fill: o.fill, stroke: o.stroke, strokeColor: o.strokeColor }));
    jzXf(L, 'ADBE Anchor Point').setValue([0, -LC2_CY * size + (nL - 1) / 2 * LD]);
    jzXf(L, 'ADBE Position').setValue([o.x, o.y]);
    if (o.rot) jzXf(L, 'ADBE Rotate Z').setValue(o.rot);
    if (o.op != null) jzXf(L, 'ADBE Opacity').setValue(o.op * 100);
    return L;
}
// vertical block (columns right -> left, one glyph per line + per-glyph offsets); x = block centre, y = top (o.mid: each column centred on y)
// returns { L, bb }
function lc2_V(ctx, str, o) {
    var size = o.size, tr = (o.track || 0) * size, lead = o.lead || 1.3, cols = lc2_lines(str), nc = cols.length, gl = [], tg = [], rot = [], anyR = false, i, j, mH = size;
    var colH = [];
    for (i = 0; i < nc; i++) { var a0 = jzChars(cols[i]), h0 = 0; for (j = 0; j < a0.length; j++) h0 += lc2_vadv(a0[j], size) + (j < a0.length - 1 ? tr : 0); colH.push(h0); mH = Math.max(mH, h0); }
    var top = o.mid ? o.y - mH / 2 : o.y;
    for (i = 0; i < nc; i++) {
        var a = jzChars(cols[i]), xc = o.x - (i - (nc - 1) / 2) * lead * size, y = o.mid ? o.y - colH[i] / 2 : top;
        for (j = 0; j < a.length; j++) {
            var ch = a[j], ad = lc2_vadv(ch, size), vx = 0, vy = 0, r90 = lc2_VROT.indexOf(ch) >= 0 || /[A-Za-z0-9]/.test(ch);
            if (lc2_isSmall(ch)) { vx = 0.11 * size; vy = -0.11 * size; }
            if ('\u3001\u3002\uFF0C\uFF0E'.indexOf(ch) >= 0) { vx = 0.3 * size; vy = -0.3 * size; }
            gl.push(ch === ' ' ? '\u3000' : ch); tg.push([xc + vx, y + ad / 2 + vy]); rot.push(r90 ? 90 : 0); if (r90) anyR = true;
            y += ad + tr;
        }
    }
    if (!gl.length) { gl.push(' '); tg.push([o.x, top + size / 2]); rot.push(0); }
    var LD = size + tr, bw = nc * lead * size - (lead - 1) * size;
    var L = jzNoGhost(jzText(ctx, gl.join('\r'), { font: o.font, size: size, color: o.color || ctx.sc.fg, x: o.x, y: top + mH / 2, track: 0, leading: LD, name: o.name || jzStrip(str) }));
    var P = [o.x, top + mH / 2], x0 = tg[0][0], y0 = tg[0][1];
    jzXf(L, 'ADBE Anchor Point').setValue([P[0] - x0, P[1] - y0 - LC2_CY * size]);
    jzXf(L, 'ADBE Position').setValue(P);
    var offs = [], any = false;
    for (i = 0; i < gl.length; i++) { var d = [tg[i][0] - x0, tg[i][1] - (y0 + i * LD)]; if (Math.abs(d[0]) + Math.abs(d[1]) > 0.05) any = true; offs.push(d); }
    if (any) jzCharOffsets(L, offs, 'JZ Columns');
    if (anyR) jzCharRotations(L, rot, 'JZ Upright');
    return { L: L, bb: lc2_box4(o.x - bw / 2, top, o.x + bw / 2, top + mH), w: bw, h: mH };
}
// many small labels in ONE text layer (one per line): items [{ t, x, y, c }], y = label centre; o: font size align track color name
function lc2_multi(ctx, items, o) {
    var lines = [], map = [], offs = [], size = o.size, LD = size * 1.25, i, j, cols = [], lineOf = [];
    for (i = 0; i < items.length; i++) {
        var t = String(items[i].t == null ? '' : items[i].t).replace(/[\r\n]/g, ' ');
        if (!t) { lineOf.push(-1); continue; }
        var l = lines.length; lines.push(t); lineOf.push(l);
        var ch = jzChars(t);
        for (j = 0; j < ch.length; j++) { map.push(l); offs.push([items[i].x, items[i].y + LC2_CY * size - l * LD]); }
        cols.push(items[i].c || null);
    }
    if (!lines.length) return null;
    var L = jzNoGhost(jzText(ctx, lines.join('\r'), { font: o.font, size: size, color: o.color, x: 0, y: 0, align: o.align, track: o.track || 0, leading: LD, name: o.name || 'labels' }));
    jzXf(L, 'ADBE Anchor Point').setValue([0, 0]); jzXf(L, 'ADBE Position').setValue([0, 0]);
    jzCharOffsets(L, offs, 'JZ Labels');
    var done = {};
    for (i = 0; i < cols.length; i++) {
        var cc = cols[i];
        if (!cc || String(cc).toUpperCase() === String(o.color).toUpperCase() || done[cc]) continue;
        done[cc] = 1;
        var fl = []; for (j = 0; j < map.length; j++) fl.push(cols[map[j]] === cc);
        jzCharColors(L, cc, fl, 'JZ Label Colour');
    }
    if (o.op != null) jzXf(L, 'ADBE Opacity').setValue(o.op * 100);
    return { L: L, map: map, lineOf: lineOf };
}
// per-line alpha for lc2_multi layers: body sets a from l (line index)
function lc2_lineAlpha(M, head, body) {
    return jzAnimator(M.L, 'JZ Line Alpha', [['ADBE Text Opacity', 0]], head + 'var LM=' + jzArrExpr(M.map) + ';var l=LM[textIndex-1]||0;var a=1;' + body + ';(1-cl(a))*100');
}
// layer opacity = value * factor (alpha = static base 0..1)
function lc2_op(ctx, L, factor, alpha, pre) {
    if (alpha != null) jzXf(L, 'ADBE Opacity').setValue(alpha * 100);
    jzSetExpr(jzXf(L, 'ADBE Opacity'), lc2_H(ctx) + (pre || '') + 'value*(' + factor + ')');
}
// per-glyph alpha animator on a text layer (keeps the layer's own opacity expression free for the motion system)
function lc2_alphaAnim(L, head, aExpr) { return jzAnimator(L, 'JZ Alpha', [['ADBE Text Opacity', 0]], head + 'var a=' + aExpr + ';(1-cl(a))*100'); }

/* ---------------------------------------------------------------- rigs + shapes */
function lc2_S(ctx, name, x, y) { return jzNoGhost(jzShapeLayer(ctx, name, x || 0, y || 0)); }
// parent L to rig R (R: anchor 0, static scale 100 / rotation 0 \u2014 its motion lives in expressions); L keeps its comp place
function lc2_kid(L, R) {
    var p = jzXf(L, 'ADBE Position').value, q = jzXf(R, 'ADBE Position').value;
    L.setParentWithJump(R);
    jzXf(L, 'ADBE Position').setValue([p[0] - q[0], p[1] - q[1]]);
    return L;
}
function lc2_G(S, name) { return jzGrp(S, name); }
function lc2_sub(g, name) { var n = jzVecs(g).addProperty('ADBE Vector Group'); if (name) n.name = name; return n; }
function lc2_rr(g, x, y, w, h, r) { return jzAddRect(g, Math.max(0.5, w), Math.max(0.5, h), Math.max(0, Math.min(r || 0, w / 2, h / 2)), x + w / 2, y + h / 2); }
function lc2_stroke(g, col, w, op, cap) {
    var s = jzAddStroke(g, col, Math.max(0.5, w), op);
    if (cap) { try { s.property('ADBE Vector Stroke Line Cap').setValue(cap); s.property('ADBE Vector Stroke Line Join').setValue(cap === 2 ? 2 : 1); } catch (e) {} }
    return s;
}
// o: { r, stroke, sw, op (fill 0..100), sop }
function lc2_box(g, name, x, y, w, h, fill, o) {
    o = o || {};
    var s = lc2_sub(g, name); lc2_rr(s, x, y, w, h, o.r || 0);
    if (o.stroke) lc2_stroke(s, o.stroke, o.sw || 2, o.sop);
    if (fill) jzAddFill(s, fill, o.op);
    return s;
}
function lc2_circ(g, name, cx, cy, r, fill, o) {
    o = o || {};
    var s = lc2_sub(g, name); jzAddEllipse(s, r * 2, r * 2, cx, cy);
    if (o.stroke) lc2_stroke(s, o.stroke, o.sw || 2, o.sop);
    if (fill) jzAddFill(s, fill, o.op);
    return s;
}
function lc2_poly(g, name, pts, fill, o) {
    o = o || {};
    var s = lc2_sub(g, name); jzAddPath(s, pts, true);
    if (o.stroke) lc2_stroke(s, o.stroke, o.sw || 2, o.sop, o.cap);
    if (fill) jzAddFill(s, fill, o.op);
    return s;
}
function lc2_line(g, name, pts, col, w, o) {
    o = o || {};
    var s = lc2_sub(g, name); jzAddPath(s, pts, !!o.closed);
    lc2_stroke(s, col, w, o.op, o.cap);
    return s;
}
// several open polylines with one stroke
function lc2_lines2(g, name, list, col, w, o) {
    o = o || {};
    var s = lc2_sub(g, name);
    for (var i = 0; i < list.length; i++) jzAddPath(s, list[i], false);
    lc2_stroke(s, col, w, o.op, o.cap);
    return s;
}
// (each dash entry is added AND set before the next one is added: in AE adding to the Dashes group invalidates the
//  references already held to its other entries)
function lc2_dashP(D, mn, v) {
    var p = null;
    try { p = D.addProperty(mn); } catch (e) { p = null; }
    if (!p) { try { p = D.property(mn); } catch (e2) { p = null; } }
    if (p) p.setValue(v);
}
function lc2_dash(st, d, gp, off) {
    var D = st.property('ADBE Vector Stroke Dashes');
    lc2_dashP(D, 'ADBE Vector Stroke Dash 1', d);
    lc2_dashP(D, 'ADBE Vector Stroke Gap 1', gp);
    if (off) lc2_dashP(D, 'ADBE Vector Stroke Offset', off);
}
// dotted polylines (dots of radius r every `step` px): zero-length dashes with round caps
function lc2_dots(g, name, list, col, r, step, op) {
    var s = lc2_sub(g, name);
    for (var i = 0; i < list.length; i++) jzAddPath(s, list[i], false);
    var st = lc2_stroke(s, col, r * 2, op, 2);
    lc2_dash(st, 0, Math.max(1, step));
    return s;
}
// soft offset shadow under a paper object
function lc2_shadow(g, sc, x, y, w, h, r, d) { return lc2_box(g, 'shadow', x + d * 0.6, y + d, w, h, lc2_shCol(sc), { r: r, op: lc2_shOp(sc) }); }
function lc2_gx(g, key, ex) {
    var mn = { pos: 'ADBE Vector Position', sc: 'ADBE Vector Scale', rot: 'ADBE Vector Rotation', op: 'ADBE Vector Group Opacity', anc: 'ADBE Vector Anchor' }[key];
    jzSetExpr(jzGX(g).property(mn), ex);
}
function lc2_gset(g, key, v) {
    var mn = { pos: 'ADBE Vector Position', sc: 'ADBE Vector Scale', rot: 'ADBE Vector Rotation', op: 'ADBE Vector Group Opacity', anc: 'ADBE Vector Anchor' }[key];
    jzGX(g).property(mn).setValue(v);
}
// clip windows follow the UNLAGGED time (browser ltU / pOutU), so the lyric's ghost passes are clipped like the main pass:
// inside the ghost copy of the content comp (named '\u2026 ghost' by jzBuild, running LC2_LAG s late) the clock is advanced again
var LC2_LAG = 1.2 / 24;   // mean of the build's two ghost lags (0.8 / 1.6 frames): both ghosts end within 0.4 frame of the main clip
function lc2_unlag(ex) { return 'var tU=time+(thisComp.name.indexOf(" ghost")>=0?' + jzN(LC2_LAG) + ':0);\n' + String(ex).replace(/\btime\b/g, 'tU'); }
// rectangle mask in comp px on an unparented, unrotated layer, grown by an animated Mask Expansion (px expression)
function lc2_clip(L, x0, y0, x1, y1, expEx) {
    var p = jzXf(L, 'ADBE Position').value, a = jzXf(L, 'ADBE Anchor Point').value, dx = a[0] - p[0], dy = a[1] - p[1];
    var m = jzMaskRect(L, x0 + dx, y0 + dy, x1 + dx, y1 + dy, 0);
    if (expEx) jzSetExpr(m.property('ADBE Mask Offset'), lc2_unlag(expEx));
    return m;
}
// diagonal stripes (browser stripes()) clipped to x..x+w: list of polygons
function lc2_clipX(poly, xa, xb) {
    function cut(pts, xl, keepGreater) {
        var out = [], n = pts.length;
        for (var i = 0; i < n; i++) {
            var A = pts[i], B = pts[(i + 1) % n], ina = keepGreater ? A[0] >= xl : A[0] <= xl, inb = keepGreater ? B[0] >= xl : B[0] <= xl;
            if (ina) out.push(A);
            if (ina !== inb) { var t = (xl - A[0]) / (B[0] - A[0]); out.push([xl, A[1] + (B[1] - A[1]) * t]); }
        }
        return out;
    }
    return cut(cut(poly, xa, true), xb, false);
}
function lc2_stripes(x, y, w, h, sw, ang, clip) {
    var t = Math.tan(ang * Math.PI / 180), n = Math.min(160, Math.ceil((w + h * Math.abs(t)) / (sw * 2)) + 2), out = [];
    for (var i = -1; i < n + 1; i++) {
        var xa = x - h * Math.abs(t) + i * sw * 2;
        var q = [[xa, y + h], [xa + sw, y + h], [xa + sw + h * t, y], [xa + h * t, y]];
        if (clip) { q = lc2_clipX(q, x, x + w); if (q.length < 3) continue; }
        out.push(q);
    }
    return out;
}
function lc2_polys(g, name, list, fill, op) {
    var s = lc2_sub(g, name);
    for (var i = 0; i < list.length; i++) jzAddPath(s, list[i], true);
    jzAddFill(s, fill, op);
    return s;
}
// smooth closed path through points (Catmull-Rom tangents)
function lc2_smooth(g, name, pts, closed) {
    var n = pts.length, I = [], O = [], i;
    for (i = 0; i < n; i++) {
        var a = pts[(i - 1 + n) % n], b = pts[(i + 1) % n];
        if (!closed && (i === 0 || i === n - 1)) { I.push([0, 0]); O.push([0, 0]); continue; }
        var tx = (b[0] - a[0]) / 6, ty = (b[1] - a[1]) / 6;
        I.push([-tx, -ty]); O.push([tx, ty]);
    }
    var sh = new Shape(); sh.vertices = pts; sh.inTangents = I; sh.outTangents = O; sh.closed = !!closed;
    var p = jzVecs(g).addProperty('ADBE Vector Shape - Group'); p.property('ADBE Vector Shape').setValue(sh);
    return p;
}
// paint order: groups are written back-to-front (like canvas code); AE draws the FIRST group on top, so every container that
// holds only groups is reversed at the end (call after all expressions of the layer are set \u2014 moving invalidates references)
function lc2_flipV(v) {
    var n = v.numProperties, i, allG = true;
    for (i = 1; i <= n; i++) if (v.property(i).matchName !== 'ADBE Vector Group') allG = false;
    for (i = 1; i <= n; i++) if (v.property(i).matchName === 'ADBE Vector Group') lc2_flipV(v.property(i).property('ADBE Vectors Group'));
    if (allG && n > 1) for (i = 1; i < n; i++) v.property(n).moveTo(i);
}
function lc2_paint(S) { lc2_flipV(S.property('ADBE Root Vectors Group')); return S; }

/* ================================================================== 17 stampSheet \u2014 \u5207\u624B\u30B7\u30FC\u30C8 */
jzReg('layout', 'stampSheet', {
    plan: function (rng, cut, st) {
        var port = lc2_port(cut), cols = port ? 3 : 5, rows = port ? 5 : 3;
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), cols: cols, rows: rows, hc: port ? rng.int(0, 1) : 1, hr: port ? 1 : rng.int(0, 1),
            val: rng.pick([63, 84, 94, 110, 120, 140]), motif: rng.pick(['circle', 'wave', 'char']), tear: rng.chance(0.6) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), k = lc2_k(ctx), ls = jzSmallSize(ctx), port = jzPortrait(ctx), HD = lc2_H(ctx), r, q, i;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), cols = jzP(ctx, 'cols', port ? 3 : 5), rows = jzP(ctx, 'rows', port ? 5 : 3);
        var val = jzP(ctx, 'val', 84), motif = jzP(ctx, 'motif', 'circle'), tear = !!jzP(ctx, 'tear', true), C = lc2_card(sc);
        var cell = Math.min(W * 0.86 / (cols + 0.4), H * 0.84 / (rows + 0.6)), sw = cols * cell, sh = rows * cell, sx = W / 2 - sw / 2, sy = H / 2 - sh / 2 + cell * 0.1, m = cell * 0.2;
        var SA = HD + 'var sa=oc(time/0.35)*(1-cl((PO-0.5)/0.5));';
        var perfC = String(sc.bg).toUpperCase() === String(C.fill).toUpperCase() ? jzMixHex(C.fill, C.text, 0.2) : sc.bg;
        // sheet (selvage) + header line
        var S = lc2_S(ctx, 'stamp sheet', 0, 0), g = lc2_G(S, 'sheet');
        lc2_shadow(g, sc, sx - m, sy - m * 1.8, sw + m * 2, sh + m * 2.8, 0, u * 0.012);
        lc2_box(g, 'selvage', sx - m, sy - m * 1.8, sw + m * 2, sh + m * 2.8, C.fill, C.edge ? { stroke: C.line, sw: 1.2 * k } : null);
        jzSetExpr(jzXf(S, 'ADBE Opacity'), SA + 'value*sa');
        lc2_paint(S);
        var hdr = lc2_T(ctx, 'JIZURA POST  \u00B7  ' + val + ' \u00D7 ' + (cols * rows - 3) + '  \u00B7  No.' + jzLineNo(ctx), { font: jzMonoF(ctx), size: Math.min(ls * 0.8, m * 0.7), track: 0.2, align: 'left', x: sx, y: sy - m * 0.9, color: C.text });
        jzSetExpr(jzXf(hdr, 'ADBE Opacity'), SA + 'value*sa*0.7');
        // the small stamps (pop in diagonally)
        var spc = rows > cols ? 2 : 3, spr = rows > cols ? 3 : 2;
        var hc = Math.min(jzP(ctx, 'hc', 1), cols - spc), hr = Math.min(jzP(ctx, 'hr', 0), rows - spr);
        var tones = [lc2_plate(sc, [sc.accent], C.fill, 1.3), jzMixHex(C.fill, C.text, 0.18), lc2_plate(sc, [sc.accent2, sc.sub], C.fill, 1.3), jzMixHex(C.fill, sc.accent, 0.45)];
        var t0 = jzTrim(c.text), first = jzChars(jzStrip(t0))[0] || '', hole = cell * 0.035, pad = cell * 0.1, qq = cell - pad * 2;
        var S2 = lc2_S(ctx, 'stamps', 0, 0), G2 = lc2_G(S2, 'stamps'), labs = [], chs = [], D = [], DC = [];
        for (r = 0; r < rows; r++) for (q = 0; q < cols; q++) {
            if (q >= hc && q < hc + spc && r >= hr && r < hr + spr) continue;
            var d0 = 0.05 + (q + r) * 0.03, x = sx + q * cell, y = sy + r * cell, tone = tones[(q + r * 2) % 4], tt = lc2_onCol(sc, tone);
            var gs = lc2_sub(G2, 'stamp ' + (r + 1) + '-' + (q + 1));
            lc2_box(gs, 'face', -qq / 2, -qq / 2, qq, qq, tone);
            var lw0 = Math.max(1.5 * k, qq * 0.03);
            if (motif === 'circle') lc2_gx(lc2_circ(gs, 'motif', 0, qq * 0.08, qq * 0.24, null, { stroke: tt, sw: lw0, sop: 60 }), 'op', HD + 'var e=ob(cl((time-' + jzN(d0) + ')/0.3),1.5)*K;e>0.6?100:0');
            else if (motif === 'wave') {
                var wp = []; for (i = 0; i <= 16; i++) wp.push([-qq * 0.35 + qq * 0.7 * i / 16, qq * 0.1 + Math.sin(i / 16 * Math.PI * 3) * qq * 0.08]);
                lc2_gx(lc2_line(gs, 'motif', wp, tt, lw0, { op: 60, cap: 2 }), 'op', HD + 'var e=ob(cl((time-' + jzN(d0) + ')/0.3),1.5)*K;e>0.6?100:0');
            } else if (first) { chs.push({ t: first, x: x + cell / 2, y: y + cell / 2 + qq * 0.08, c: tt }); DC.push(d0); }
            lc2_gset(gs, 'pos', [x + cell / 2, y + cell / 2]);
            lc2_gx(gs, 'sc', HD + 'var e=ob(cl((time-' + jzN(d0) + ')/0.3),1.5)*K;[100*e,100*e]');
            labs.push({ t: String(val), x: x + pad + qq * 0.08, y: y + pad + qq * 0.13, c: tt }); D.push(d0);
        }
        lc2_paint(S2);
        if (labs.length) {
            var ML = lc2_multi(ctx, labs, { font: jzMonoF(ctx), size: qq * 0.16, align: 'left', color: labs[0].c, name: 'stamp values' });
            lc2_lineAlpha(ML, HD + 'var D=' + jzArrExpr(D) + ';', 'var e=ob(cl((time-D[l])/0.3),1.5)*K;a=e>0.6?0.9:0');
        }
        if (chs.length) {
            var MC = lc2_multi(ctx, chs, { font: font, size: qq * 0.45, color: chs[0].c, name: 'stamp motifs' });
            lc2_lineAlpha(MC, HD + 'var D=' + jzArrExpr(DC) + ';', 'var e=ob(cl((time-D[l])/0.3),1.5)*K;a=e>0.6?0.5:0');
        }
        // perforations between all stamps
        var P = lc2_S(ctx, 'perforations', 0, 0), gp = lc2_G(P, 'perf'), nx = Math.max(2, Math.round(cell / (hole * 3.2))), rowsL = [];
        for (r = 0; r <= rows; r++) rowsL.push([[sx, sy + r * cell], [sx + sw, sy + r * cell]]);
        for (q = 0; q <= cols; q++) rowsL.push([[sx + q * cell, sy], [sx + q * cell, sy + sh]]);
        lc2_dots(gp, 'holes', rowsL, perfC, hole, cell / nx);
        jzSetExpr(jzXf(P, 'ADBE Opacity'), SA + 'value*sa*0.9');
        // the hero stamp (3x2 / 2x3): lifts and tears away on exit
        var hx = sx + hc * cell, hy = sy + hr * cell, hw = cell * spc, hh = cell * spr, hs = Math.min(hw, hh), ip = hs * 0.07;
        var HE = HD + 'var he=ob(cl((time-0.12)/0.35),1.3),tr=' + (tear ? 'ic(PO)' : '0') + ',ha=cl(he*2)*' + (tear ? '(1-cl((PO-0.6)/0.4))' : 'K') + ';';
        var R = lc2_S(ctx, 'hero stamp', hx + hw / 2, hy + hh / 2), gh = lc2_G(R, 'hero'), dS = u * 0.02;
        var gsd = lc2_box(gh, 'lift shadow', -hw / 2, -hh / 2, hw, hh, lc2_shCol(sc), { op: lc2_shOp(sc) });
        lc2_gx(gsd, 'pos', HE + '[' + jzN(dS * 0.6) + '*(1+tr*2),' + jzN(dS) + '*(1+tr*2)]'); lc2_gx(gsd, 'op', HE + 'tr>0.001?100:0');
        lc2_box(gh, 'paper', -hw / 2, -hh / 2, hw, hh, C.fill);
        var hl = hole * 1.2, nxh = Math.max(2, Math.round(hw / (hl * 3.2))), nyh = Math.max(2, Math.round(hh / (hl * 3.2)));
        lc2_dots(gh, 'perf h', [[[-hw / 2, -hh / 2], [hw / 2, -hh / 2]], [[-hw / 2, hh / 2], [hw / 2, hh / 2]]], perfC, hl, hw / nxh);
        lc2_dots(gh, 'perf v', [[[-hw / 2, -hh / 2], [-hw / 2, hh / 2]], [[hw / 2, -hh / 2], [hw / 2, hh / 2]]], perfC, hl, hh / nyh);
        var heroC = lc2_plate(sc, [sc.accent, sc.ink], C.fill, 1.8), htc = lc2_onCol(sc, heroC);
        lc2_box(gh, 'plate', -hw / 2 + ip, -hh / 2 + ip, hw - ip * 2, hh - ip * 2, heroC);
        jzSetExpr(jzXf(R, 'ADBE Position'), HE + '[value[0]+tr*' + jzN(cell * 0.4) + ',value[1]-tr*' + jzN(cell * 0.8) + ']');
        jzSetExpr(jzXf(R, 'ADBE Rotate Z'), HE + 'value+tr*14+(1-he)*-6');
        jzSetExpr(jzXf(R, 'ADBE Scale'), HE + 'var s=0.9+0.1*he+tr*0.06;[value[0]*s,value[1]*s]');
        jzSetExpr(jzXf(R, 'ADBE Opacity'), HE + 'value*ha');
        lc2_paint(R);
        var ox = hx + hw / 2, oy = hy + hh / 2;
        var V1 = lc2_T(ctx, String(val), { font: jzMonoF(ctx), size: hs * 0.1, align: 'left', x: ox - hw / 2 + ip * 1.8, y: oy - hh / 2 + ip * 2.4, color: htc });
        var V2 = lc2_T(ctx, 'LYRIC  ' + jzLineNo(ctx), { font: jzMonoF(ctx), size: hs * 0.045, track: 0.3, align: 'right', x: ox + hw / 2 - ip * 1.8, y: oy + hh / 2 - ip * 1.9, color: htc });
        lc2_kid(V1, R); lc2_kid(V2, R);
        jzSetExpr(jzXf(V1, 'ADBE Opacity'), HE + 'value*ha'); jzSetExpr(jzXf(V2, 'ADBE Opacity'), HE + 'value*ha*0.8');
        var fb = lc2_fitBlock(t0, hw * 0.76, hh * 0.56, { lead: 1.08 }, 3);
        var T = lc2_T(ctx, fb.text, { font: font, size: Math.min(fb.size, hs * 0.34), x: ox, y: oy + hs * 0.03, lead: 1.08, color: htc, name: t0 });
        lc2_kid(T, R);
        lc2_main(ctx, T, 0.15);
        return lc2_box4(hx, hy, hx + hw, hy + hh);
    }
});

/* ================================================================== 18 postcard \u2014 \u306F\u304C\u304D */
jzReg('layout', 'postcard', {
    plan: function (rng, cut, st) {
        var font = rng.chance(0.4) ? 'klee' : rng.pick(jzFontsOf(st, ['serif', 'display'])), tilt = rng.range(-5, 5), val = rng.pick([63, 85, 110]), z = '';
        for (var i = 0; i < 7; i++) z += rng.int(0, 9);
        return { font: font, tilt: tilt, val: val, zip: z, mark: rng.range(-18, 18), stamp: rng.pick(['circle', 'mount', 'wave']) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), k = lc2_k(ctx), ls = jzSmallSize(ctx), port = jzPortrait(ctx), HD = lc2_H(ctx), i;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'serif')), tilt = jzP(ctx, 'tilt', 0), val = jzP(ctx, 'val', 63), zip = String(jzP(ctx, 'zip', '1500001'));
        var mark = jzP(ctx, 'mark', 0), stamp = jzP(ctx, 'stamp', 'circle'), C = lc2_card(sc), vertCard = port;
        var cw = vertCard ? Math.min(W * 0.8, H * 0.8 / 1.48) : Math.min(W * 0.66, H * 0.84 * 1.48), ch = vertCard ? cw * 1.48 : cw / 1.48;
        var CX = W / 2, CY = H / 2, EX = HD + 'var ein=oc(time/0.55),eo=ic(PO),a=cl(ein*2)*(1-eo);';
        var R = lc2_S(ctx, 'postcard', CX, CY), g = lc2_G(R, 'card');
        var x0 = -cw / 2, y0 = -ch / 2;
        lc2_shadow(g, sc, x0, y0, cw, ch, cw * 0.012, u * 0.016);
        lc2_box(g, 'card', x0, y0, cw, ch, C.fill, { r: cw * 0.012, stroke: C.edge ? C.line : null, sw: 1.2 * k });
        var red = lc2_plate(sc, [sc.accent, sc.accent2, C.text], C.fill, 1.8), m = Math.min(cw, ch) * 0.07;
        // stamp (top-left) with a perforated edge
        var stw = Math.min(cw, ch) * 0.2, sth = stw * 1.2, stx = x0 + m, sty = y0 + m, stC = lc2_plate(sc, [sc.accent2, sc.accent, sc.ink], C.fill, 1.5), stt = lc2_onCol(sc, stC);
        var gS = lc2_sub(g, 'stamp'), hole = stw * 0.035, np = Math.max(2, Math.round(stw / (hole * 3.2))), npv = Math.max(2, Math.round(sth / (hole * 3.2)));
        lc2_box(gS, 'paper', stx, sty, stw, sth, stC);
        lc2_dots(gS, 'perf h', [[[stx, sty], [stx + stw, sty]], [[stx, sty + sth], [stx + stw, sty + sth]]], C.fill, hole, stw / np);
        lc2_dots(gS, 'perf v', [[[stx, sty], [stx, sty + sth]], [[stx + stw, sty], [stx + stw, sty + sth]]], C.fill, hole, sth / npv);
        if (stamp === 'circle') lc2_circ(gS, 'sun', stx + stw / 2, sty + sth * 0.56, stw * 0.26, stt, { op: 80 });
        else if (stamp === 'mount') lc2_poly(gS, 'mount', [[stx + stw * 0.12, sty + sth * 0.78], [stx + stw * 0.5, sty + sth * 0.3], [stx + stw * 0.88, sty + sth * 0.78]], stt, { op: 80 });
        else { var wp = []; for (i = 0; i <= 12; i++) wp.push([stx + stw * (0.12 + 0.76 * i / 12), sty + sth * 0.6 + Math.sin(i / 12 * Math.PI * 2) * sth * 0.08]); lc2_line(gS, 'wave', wp, stt, Math.max(2 * k, stw * 0.05), { cap: 2 }); }
        // postal code boxes
        var bs = Math.min(cw, ch) * 0.062, gap = bs * 0.22, zx1 = x0 + cw - m, zy = y0 + m * 0.9, gz = lc2_sub(g, 'zip boxes'), zi = [];
        for (i = 0; i < 7; i++) {
            var bx = zx1 - (7 - i) * (bs + gap) - (i < 3 ? gap * 1.5 : 0);
            lc2_box(gz, 'box', bx, zy, bs, bs * 1.25, null, { r: bs * 0.08, stroke: red, sw: Math.max(1.2 * k, bs * 0.06) });
            if (i === 3) lc2_line(gz, 'dash', [[bx - gap * 2.2, zy + bs * 0.62], [bx - gap * 0.6, zy + bs * 0.62]], red, Math.max(1.2 * k, bs * 0.06));
            zi.push({ t: zip.charAt(i), x: CX + bx + bs / 2, y: CY + zy + bs * 0.66 });
        }
        var ZA = 'oc(cl((time-0.25)/0.4))*K';
        lc2_gx(gz, 'op', HD + '100*' + ZA);
        // address lines + lyric
        var t0 = jzTrim(c.text), bb, LA = 'oc(cl((time-0.2)/0.5))*K', texts = [], gl = lc2_sub(g, 'address lines');
        var lyr, lastSama = null;
        if (vertCard && !jzHasLatin(t0)) {
            var top = y0 + m + sth + ls * 2.4, bot = y0 + ch - m * 1.4, vo = { vertical: true, lead: 1.3, track: 0.06 };
            var fb = lc2_fitBlock(jzStrip(t0), cw * 0.5, (bot - top) * 0.84, vo, 2), size = Math.min(fb.size, cw * 0.2), mm = lc2_meas(fb.text, size, vo);
            var lx = x0 + cw * 0.62, cl0 = [];
            for (i = 0; i < 4; i++) { var xx = x0 + cw * (0.84 - i * 0.2); cl0.push([[xx, top], [xx, bot]]); }
            lc2_dots(gl, 'leaders', cl0, C.text, Math.max(1 * k, ls * 0.05), ls * 0.45, 30);
            var ty = top + Math.max(0, (bot - top) - mm.h - size * 1.3) * 0.3;
            var VV = lc2_V(ctx, fb.text, { font: font, size: size, color: C.text, x: CX + lx, y: CY + ty, lead: 1.3, track: 0.06, name: t0 });
            lyr = VV.L;
            var lls = lc2_lines(fb.text), lastLen = jzCount(lls[lls.length - 1]);
            lastSama = lc2_T(ctx, '\u69D8', { font: font, size: size * 0.7, x: CX + lx - (fb.lines - 1) * size * 0.65, y: CY + ty + lastLen * size * 1.06 + size * 0.75, color: C.text });
        } else {
            var top2 = y0 + m + sth + ls * 1.8, bot2 = y0 + ch - m, lh = (bot2 - top2) / 4, rl = [];
            for (i = 1; i <= 4; i++) rl.push([[x0 + m, top2 + lh * i], [x0 + cw - m, top2 + lh * i]]);
            lc2_dots(gl, 'leaders', rl, C.text, Math.max(1 * k, ls * 0.05), ls * 0.45, 30);
            var fb2 = lc2_fitBlock(t0, cw - m * 2.4, lh * 2.4, { lead: 1.2, track: 0.02 }, 2), size2 = Math.min(fb2.size, lh * 1.05), nL = fb2.lines;
            lyr = lc2_T(ctx, fb2.text, { font: font, size: size2, x: CX + x0 + m * 1.2, y: CY + top2 + lh * (nL > 1 ? 2.5 : 2.0) - size2 * 0.3, align: 'left', lead: lh / size2, track: 0.02, color: C.text, name: t0 });
        }
        lc2_gx(gl, 'op', HD + '100*' + LA);
        // postmark thumps onto the stamp
        var pmx = stx + stw * 0.95, pmy = sty + sth * 0.62, RR = stw * 0.62, PT = 'var pt=(time-' + jzN((c.inDur || 0) + 0.2) + ')/0.18,q=lp(1.4,1,ob(cl(pt),1.3)),pa=pt>0?cl(pt*3)*K*0.85:0;';
        var gm = lc2_sub(g, 'postmark');
        lc2_circ(gm, 'ring', 0, 0, RR, null, { stroke: C.text, sw: Math.max(1.5 * k, RR * 0.05) });
        lc2_circ(gm, 'ring 2', 0, 0, RR * 0.8, null, { stroke: C.text, sw: Math.max(1 * k, RR * 0.025) });
        lc2_lines2(gm, 'bars', [[[-RR * 0.8, -RR * 0.36], [RR * 0.8, -RR * 0.36]], [[-RR * 0.8, RR * 0.36], [RR * 0.8, RR * 0.36]]], C.text, Math.max(1 * k, RR * 0.025));
        var wl = [];
        for (var k2 = 0; k2 < 3; k2++) { var pts = []; for (i = 0; i <= 20; i++) pts.push([RR * 1.15 + i * RR * 0.1, (k2 - 1) * RR * 0.32 + Math.sin(i * 0.9) * RR * 0.08]); wl.push(pts); }
        lc2_lines2(gm, 'cancel waves', wl, C.text, Math.max(1.5 * k, RR * 0.04), { cap: 2 });
        lc2_gset(gm, 'pos', [pmx, pmy]); lc2_gset(gm, 'rot', mark);
        lc2_gx(gm, 'sc', HD + PT + '[100*q,100*q]'); lc2_gx(gm, 'op', HD + PT + '100*pa');
        jzSetExpr(jzXf(R, 'ADBE Position'), EX + '[value[0]-(1-ein)*' + jzN(W * 0.4) + '+eo*' + jzN(W * 0.3) + ',value[1]+(1-ein)*' + jzN(H * 0.05) + ']');
        jzSetExpr(jzXf(R, 'ADBE Rotate Z'), EX + 'value+' + jzN(tilt) + '+(1-ein)*-18+eo*10');
        jzSetExpr(jzXf(R, 'ADBE Opacity'), EX + 'value*a');
        lc2_paint(R);
        // text layers riding on the card
        var tv = lc2_T(ctx, String(val), { font: jzMonoF(ctx), size: stw * 0.2, align: 'left', x: CX + stx + stw * 0.1, y: CY + sty + sth * 0.14, color: stt });
        lc2_kid(tv, R); jzSetExpr(jzXf(tv, 'ADBE Opacity'), EX + 'value*a');
        var ZM = lc2_multi(ctx, zi, { font: font, size: bs * 0.8, color: C.text, name: 'zip code' });
        lc2_kid(ZM.L, R); lc2_op(ctx, ZM.L, ZA, 0.9);
        var lab = lc2_T(ctx, vertCard ? '\u90F5\u4FBF\u306F\u304C\u304D' : 'POST CARD', { font: vertCard ? jzSerifF(ctx) : jzMonoF(ctx), size: ls * (vertCard ? 1.2 : 0.9), track: 0.5, x: CX + (vertCard ? 0 : x0 + cw * 0.46), y: CY + (vertCard ? y0 + m + sth + ls * 0.9 : y0 + m * 0.75), color: C.text });
        lc2_kid(lab, R); jzSetExpr(jzXf(lab, 'ADBE Opacity'), EX + 'value*a*0.75');
        if (lastSama) { lc2_kid(lastSama, R); lc2_op(ctx, lastSama, LA); }
        var tm = lc2_T(ctx, jzFmtTime(c.start), { font: jzMonoF(ctx), size: RR * 0.26, x: CX + pmx, y: CY + pmy, color: C.text, rot: mark });
        lc2_kid(tm, R);
        jzSetExpr(jzXf(tm, 'ADBE Scale'), HD + PT + '[value[0]*q,value[1]*q]'); jzSetExpr(jzXf(tm, 'ADBE Opacity'), HD + PT + 'value*pa');
        lc2_kid(lyr, R);
        lc2_main(ctx, lyr, 0.3);
        return lc2_box4(CX - cw / 2, CY - ch / 2, CX + cw / 2, CY + ch / 2);
    }
});

/* ================================================================== 19 letterPaper \u2014 \u4FBF\u7B8B */
jzReg('layout', 'letterPaper', {
    plan: function (rng, cut, st) {
        return { font: rng.chance(0.5) ? 'klee' : rng.pick(jzFontsOf(st, ['serif'])), variant: jzHasLatin(cut.text) ? 'yoko' : rng.pick(['tate', 'tate', 'yoko']),
            rule: rng.pick(['accent', 'accent', 'accent', 'sub']), sign: rng.chance(0.7) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), k = lc2_k(ctx), ls = jzSmallSize(ctx), port = jzPortrait(ctx), HD = lc2_H(ctx), i;
        var font = jzP(ctx, 'font', jzSerifF(ctx)), t0 = jzTrim(c.text), tate = jzP(ctx, 'variant', 'tate') === 'tate' && !jzHasLatin(t0), sign = !!jzP(ctx, 'sign', true);
        var C = lc2_card(sc), pw, ph;
        if (tate) { pw = port ? W * 0.88 : Math.min(W * 0.8, H * 0.86 * 1.45); ph = port ? Math.min(H * 0.74, pw * 1.5) : pw / 1.45; }
        else { ph = port ? Math.min(H * 0.8, W * 0.9 * 1.35) : H * 0.86; pw = port ? ph / 1.35 : Math.min(W * 0.8, ph * 1.1); }
        var x0 = W / 2 - pw / 2, y0 = H / 2 - ph / 2;
        var ruleC = jzP(ctx, 'rule', 'accent') === 'accent' ? lc2_plate(sc, [sc.accent, sc.accent2], C.fill, 1.6) : jzMixHex(C.fill, C.text, 0.4);
        var lw = Math.max(1.5 * k, u * 0.002), third = tate ? pw / 3 : ph / 3;
        // kk = how far the outer thirds are unfolded (portrait: the sheet slides up instead)
        var KX = HD + 'var uf=' + (port ? '1' : 'ioc(cl((time-0.05)/0.45))') + ',fo=' + (port ? '0' : 'ioc(cl(PO/0.7))') + ',kk=uf*(1-fo),a0=oc(cl(time/0.2))*(1-cl((PO-0.55)/0.45)),T3=' + jzN(third) + ';';
        // visible window (comp px): vx0/vy0/vw/vh
        var VIS = tate ? 'var vx0=' + jzN(x0) + '+T3*(1-kk),vy0=' + jzN(y0) + ',vw=T3*(1+2*kk),vh=' + jzN(ph) + ';'
            : 'var vx0=' + jzN(x0) + ',vy0=' + jzN(y0) + '+T3*(1-kk),vw=' + jzN(pw) + ',vh=T3*(1+2*kk);';
        var R = lc2_S(ctx, 'letter rig', W / 2, H / 2);
        if (port) jzSetExpr(jzXf(R, 'ADBE Position'), HD + '[value[0],value[1]+(1-oc(cl(time/0.45)))*' + jzN(H * 0.25) + '+ic(PO)*' + jzN(H * 0.05) + ']');
        // shadow + edge follow the visible window
        var SH = lc2_S(ctx, 'letter shadow', 0, 0), gsh = lc2_G(SH, 'shadow'), d = u * 0.014;
        // (rect expressions are set before the fill / stroke is added: adding a sibling invalidates `rp` / `rpf` in AE)
        var rs = lc2_sub(gsh, 'drop'), rp = jzAddRect(rs, 10, 10, 0);
        jzSetExpr(rp.property('ADBE Vector Rect Size'), KX + VIS + '[vw,vh]'); jzSetExpr(rp.property('ADBE Vector Rect Position'), KX + VIS + '[vx0+vw/2+' + jzN(d * 0.6) + ',vy0+vh/2+' + jzN(d) + ']');
        jzAddFill(rs, lc2_shCol(sc), lc2_shOp(sc));
        var rf = lc2_sub(gsh, 'sheet'), rpf = jzAddRect(rf, 10, 10, 0);
        jzSetExpr(rpf.property('ADBE Vector Rect Size'), KX + VIS + '[vw,vh]'); jzSetExpr(rpf.property('ADBE Vector Rect Position'), KX + VIS + '[vx0+vw/2,vy0+vh/2]');
        if (C.edge) lc2_stroke(rf, C.line, 1.2 * k); jzAddFill(rf, C.fill);
        jzSetExpr(jzXf(SH, 'ADBE Opacity'), KX + 'value*a0');
        lc2_paint(SH);
        // rules, creases and the shading of the swinging thirds (clipped to the window)
        var S = lc2_S(ctx, 'letter', 0, 0), g = lc2_G(S, 'rules'), mT = ph * 0.1, mS = pw * 0.06, texts = [], bb;
        var ctxT = lc2_deck(ctx), A2 = 'cl((kk-0.6)/0.4)*K';
        if (tate) {
            var top = y0 + mT, bot = y0 + ph - mT;
            lc2_box(g, 'border top', x0 + mS, top - lw * 4, pw - mS * 2, lw * 2.5, ruleC);
            lc2_box(g, 'border bottom', x0 + mS, bot + lw * 1.5, pw - mS * 2, lw * 2.5, ruleC);
            var vo = { vertical: true, lead: 1.55, track: 0.08 }, fb = lc2_fitBlock(jzStrip(t0), port ? pw * 0.6 : third * 0.9, (bot - top) * 0.92, vo, 3);
            var size = Math.min(fb.size, u * (port ? 0.17 : 0.13)), pitch = size * 1.55, nl = fb.lines, base = W / 2 - nl / 2 * pitch;
            var iMin = Math.ceil((x0 + mS - base) / pitch), iMax = Math.floor((x0 + pw - mS - base) / pitch), vl = [];
            for (i = iMin; i <= iMax; i++) vl.push([[base + i * pitch, top], [base + i * pitch, bot]]);
            lc2_lines2(g, 'column rules', vl, ruleC, lw, { op: 75 });
            var VV = lc2_V(ctx, fb.text, { font: font, size: size, color: C.text, x: W / 2, y: top + size * 0.35, lead: 1.55, track: 0.08, name: t0 });
            bb = VV.bb; texts.push([VV.L, null]);
            var cs = size * 0.62, cxp = base + (nl + 1.5) * pitch;
            if (ctxT && cxp < x0 + pw - mS) {
                var cc = jzChars(jzStrip(ctxT)).slice(0, Math.max(1, Math.floor((bot - top) / (cs * 1.1)))).join('');
                texts.push([lc2_V(ctx, cc, { font: font, size: cs, color: C.text, x: cxp, y: top + cs * 0.4, track: 0.08 }).L, A2 + '*0.5']);
            }
            if (sign && base - 1.5 * pitch > x0 + mS) texts.push([lc2_V(ctx, 'No.' + jzLineNo(ctx), { font: font, size: ls, color: C.text, x: base - 1.5 * pitch, y: bot - ls * 5 }).L, A2 + '*0.7']);
            // (each shade is finished before the next group is added to `g`)
            var gsl = lc2_box(g, 'shade left', 0, 0, 10, 10, jzDarkest(sc));
            lc2_gx(gsl, 'sc', KX + '[T3*kk*10,' + jzN(ph * 10) + ']'); lc2_gx(gsl, 'pos', KX + '[' + jzN(x0) + '+T3*(1-kk),' + jzN(y0) + ']'); lc2_gx(gsl, 'op', KX + '(1-kk)*35');
            var gsr = lc2_box(g, 'shade right', 0, 0, 10, 10, jzDarkest(sc));
            lc2_gx(gsr, 'sc', KX + '[T3*kk*10,' + jzN(ph * 10) + ']'); lc2_gx(gsr, 'pos', KX + '[' + jzN(x0 + third * 2) + ',' + jzN(y0) + ']'); lc2_gx(gsr, 'op', KX + '(1-kk)*35');
            var cr = jzMixHex(C.fill, C.text, 0.12);
            lc2_line(g, 'crease 1', [[x0 + third, y0], [x0 + third, y0 + ph]], cr, lw, { op: 80 });
            lc2_gx(lc2_line(g, 'crease 2', [[x0 + third * 2, y0], [x0 + third * 2, y0 + ph]], cr, lw, { op: 80 }), 'op', KX + '100*kk');
        } else {
            var left = x0 + pw * 0.1, right = x0 + pw * 0.9, top2 = y0 + ph * 0.1, bot2 = y0 + ph * 0.92, ho = { lead: 1.7, track: 0.04 };
            var fb2 = lc2_fitBlock(t0, right - left, (bot2 - top2) * 0.46, ho, port ? 3 : 2), size2 = Math.min(fb2.size, u * 0.15), pitch2 = size2 * 1.7;
            var nRow = Math.max(3, Math.floor((bot2 - top2) / pitch2)), hl = [];
            for (i = 0; i <= nRow; i++) hl.push([[left - pw * 0.03, top2 + i * pitch2], [right + pw * 0.03, top2 + i * pitch2]]);
            lc2_lines2(g, 'rules', hl, ruleC, lw, { op: 75 });
            lc2_line(g, 'top rule', [[left - pw * 0.03, top2 - lw * 5], [right + pw * 0.03, top2 - lw * 5]], ruleC, lw * 2.5);
            var nl2 = fb2.lines, r0 = Math.max(0, Math.floor(nRow / 2 - nl2 / 2)), ly = top2 + (r0 + nl2 / 2) * pitch2 - size2 * 0.12;
            var TL = lc2_T(ctx, fb2.text, { font: font, size: size2, x: left, y: ly, align: 'left', lead: 1.7, track: 0.04, color: C.text, name: t0 });
            var mm2 = lc2_meas(fb2.text, size2, ho); bb = lc2_box4(left, ly - mm2.h / 2, left + mm2.w, ly + mm2.h / 2); texts.push([TL, null]);
            if (ctxT && r0 > 0) texts.push([lc2_T(ctx, ctxT, { font: font, size: size2 * 0.6, x: left, y: top2 + (r0 - 0.5) * pitch2 - size2 * 0.1, align: 'left', color: C.text }), A2 + '*0.45']);
            if (sign) texts.push([lc2_T(ctx, '\u2014 No.' + jzLineNo(ctx), { font: font, size: ls, x: right, y: top2 + (nRow - 0.5) * pitch2 - ls * 0.2, align: 'right', color: C.text }), A2 + '*0.7']);
            var gst = lc2_box(g, 'shade top', 0, 0, 10, 10, jzDarkest(sc));
            lc2_gx(gst, 'sc', KX + '[' + jzN(pw * 10) + ',T3*kk*10]'); lc2_gx(gst, 'pos', KX + '[' + jzN(x0) + ',' + jzN(y0) + '+T3*(1-kk)]'); lc2_gx(gst, 'op', KX + '(1-kk)*35');
            var gsb = lc2_box(g, 'shade bottom', 0, 0, 10, 10, jzDarkest(sc));
            lc2_gx(gsb, 'sc', KX + '[' + jzN(pw * 10) + ',T3*kk*10]'); lc2_gx(gsb, 'pos', KX + '[' + jzN(x0) + ',' + jzN(y0 + third * 2) + ']'); lc2_gx(gsb, 'op', KX + '(1-kk)*35');
            var cr2 = jzMixHex(C.fill, C.text, 0.12);
            lc2_gx(lc2_lines2(g, 'creases', [[[x0, y0 + third], [x0 + pw, y0 + third]], [[x0, y0 + third * 2], [x0 + pw, y0 + third * 2]]], cr2, lw, { op: 80 }), 'op', KX + '100*kk');
        }
        jzSetExpr(jzXf(S, 'ADBE Opacity'), KX + 'value*a0');
        lc2_paint(S);
        // clip window: the middle third, grown by the unfold
        var BIG = Math.max(W, H);
        function clip(L) {
            if (port) return;
            if (tate) lc2_clip(L, x0 + third, y0 - BIG, x0 + third * 2, y0 + ph + BIG, KX + 'T3*kk');
            else lc2_clip(L, x0 - BIG, y0 + third, x0 + pw + BIG, y0 + third * 2, KX + 'T3*kk');
        }
        clip(S);
        lc2_kid(SH, R); lc2_kid(S, R);
        for (i = 0; i < texts.length; i++) {
            var L = texts[i][0];
            clip(L); lc2_kid(L, R);
            if (texts[i][1]) lc2_op(ctx, L, texts[i][1], null, 'var uf=' + (port ? '1' : 'ioc(cl((time-0.05)/0.45))') + ',fo=' + (port ? '0' : 'ioc(cl(PO/0.7))') + ',kk=uf*(1-fo);');
            else { lc2_main(ctx, L, 0); lc2_alphaAnim(L, HD, 'oc(cl(time/0.2))*(1-cl((PO-0.55)/0.45))>0.01?1:0'); }   // the browser skips the lyric while the sheet is invisible
        }
        return bb;
    }
});

/* ================================================================== 20 calendar \u2014 \u30AB\u30EC\u30F3\u30C0\u30FC */
jzReg('layout', 'calendar', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), variant: rng.pick(['month', 'himekuri']), month: rng.int(0, 11), off: rng.int(0, 6), day: rng.int(3, 27), days: rng.pick([30, 31]) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), k = lc2_k(ctx), ls = jzSmallSize(ctx), port = jzPortrait(ctx), HD = lc2_H(ctx), i;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), variant = jzP(ctx, 'variant', 'month'), month = jzP(ctx, 'month', 3), off = jzP(ctx, 'off', 2), pday = jzP(ctx, 'day', 10), days = jzP(ctx, 'days', 30);
        var C = lc2_card(sc), t0 = jzTrim(c.text), day = ((pday + (c.line || 0)) % days) + 1, wd = (off + day - 1) % 7;
        var red = lc2_plate(sc, [sc.accent, sc.accent2], C.fill, 2.2), blue = lc2_plate(sc, [sc.accent2, sc.accent], C.fill, 2.2);
        function dayCol(w) { return w === 0 ? red : w === 6 ? blue : C.text; }
        var mon = LC2_MON_E[month % 12];
        if (variant === 'himekuri') {
            // tear-off day pad: the lyric takes the place of the big date
            var pw = port ? W * 0.84 : Math.min(W * 0.6, H * 0.86 * 0.95), ph = port ? Math.min(H * 0.7, pw * 1.3) : H * 0.84;
            var x0 = W / 2 - pw / 2, y0 = H / 2 - ph / 2 + ph * 0.03, bindH = ph * 0.08, A = 'oc(cl(time/0.3))';
            var bc = [jzDarkest(sc), sc.ink, sc.accent, sc.sub], bindC = sc.sub;
            for (i = 0; i < bc.length; i++) if (bc[i] && jzContrast(bc[i], sc.bg) >= 1.5 && jzContrast(bc[i], C.fill) >= 1.8) { bindC = bc[i]; break; }
            var S = lc2_S(ctx, 'day pad', 0, 0), g = lc2_G(S, 'pad');
            for (i = 3; i >= 1; i--) lc2_box(g, 'page ' + i, x0 + i * 2 * k, y0 + bindH, pw, ph - bindH + i * u * 0.006, jzMixHex(C.fill, C.text, 0.08 * i));
            lc2_shadow(g, sc, x0, y0, pw, ph, 0, u * 0.012);
            lc2_box(g, 'next page', x0, y0 + bindH, pw, ph - bindH, C.fill);
            jzSetExpr(jzXf(S, 'ADBE Opacity'), HD + 'value*' + A);
            lc2_paint(S);
            var nx = lc2_T(ctx, String(day % days + 1), { font: font, size: ph * 0.3, x: W / 2, y: y0 + ph * 0.55, color: dayCol((wd + 1) % 7) });
            lc2_op(ctx, nx, A, 0.18);
            // this page: tears away from its hinge on exit
            var hx = x0 + pw * (pday % 2 ? 0.1 : 0.9), hy = y0 + bindH, sg = pday % 2 ? 1 : -1;
            var TE = HD + 'var tr=ic(PO),pa=' + A + '*(1-cl((PO-0.7)/0.3)),fa=oc(cl((time-0.1)/0.4))*pa;';
            var R = lc2_S(ctx, 'this page', hx, hy), gp = lc2_G(R, 'page'), lx0 = x0 - hx, ly0 = y0 - hy, m = pw * 0.07;
            lc2_box(gp, 'page', lx0, 0, pw, ph - bindH, C.fill, C.edge ? { stroke: C.line, sw: 1.2 * k } : null);
            var gq = lc2_sub(gp, 'print');
            lc2_box(gq, 'rokuyo box', lx0 + pw - m - ls * 3.2, ph * 0.06, ls * 3.2, ls * 1.6, null, { r: ls * 0.2, stroke: red, sw: Math.max(1.5 * k, ls * 0.08) });
            lc2_line(gq, 'rule', [[lx0 + m, ly0 + ph * 0.8], [lx0 + pw - m, ly0 + ph * 0.8]], C.text, Math.max(1 * k, u * 0.0015), { op: 50 });
            lc2_gx(gq, 'op', TE + '100*oc(cl((time-0.1)/0.4))');
            jzSetExpr(jzXf(R, 'ADBE Rotate Z'), TE + 'value+' + sg + '*tr*28');
            jzSetExpr(jzXf(R, 'ADBE Position'), TE + '[value[0],value[1]+tr*tr*' + jzN(ph * 0.6) + ']');
            jzSetExpr(jzXf(R, 'ADBE Opacity'), TE + 'value*pa');
            lc2_paint(R);
            var dcol0 = dayCol(wd), dcol = jzContrast(dcol0, C.fill) >= 3 ? dcol0 : C.text, kids = [];
            kids.push(lc2_T(ctx, String(month + 1), { font: font, size: ph * 0.1, align: 'left', x: x0 + m, y: y0 + bindH + ph * 0.09, color: C.text }));
            kids.push(lc2_T(ctx, mon, { font: jzMonoF(ctx), size: ls * 0.9, track: 0.3, align: 'left', x: x0 + m + ph * 0.1 * 0.9 * (month >= 9 ? 1.25 : 1), y: y0 + bindH + ph * 0.1, color: C.text, op: 0.8 }));
            kids.push(lc2_T(ctx, LC2_ROKUYO[(day + month) % 6], { font: jzSerifF(ctx), size: ls * 1.05, x: x0 + pw - m - ls * 1.6, y: y0 + bindH + ph * 0.06 + ls * 0.8, color: red }));
            kids.push(lc2_T(ctx, LC2_WD_J.charAt(wd) + '\u66DC\u65E5', { font: jzSerifF(ctx), size: ls * 1.5, align: 'left', x: x0 + m, y: y0 + ph * 0.88, color: dcol }));
            kids.push(lc2_T(ctx, day + '  ' + LC2_WD_E[wd], { font: jzMonoF(ctx), size: ls * 1.1, track: 0.2, align: 'right', x: x0 + pw - m, y: y0 + ph * 0.88, color: dcol }));
            for (i = 0; i < kids.length; i++) { lc2_kid(kids[i], R); jzSetExpr(jzXf(kids[i], 'ADBE Opacity'), TE + 'value*fa'); }
            var fb = lc2_fitBlock(t0, pw - m * 2, ph * 0.46, { lead: 1.08, track: 0.02 }, 3), size = Math.min(fb.size, u * 0.24);
            var T = lc2_T(ctx, fb.text, { font: font, size: size, x: W / 2, y: y0 + bindH + ph * 0.47, lead: 1.08, track: 0.02, color: dcol, name: t0 });
            lc2_kid(T, R); lc2_main(ctx, T, 0);
            // binding with rings (on top)
            var B = lc2_S(ctx, 'binding', 0, 0), gb = lc2_G(B, 'binding');
            lc2_box(gb, 'bar', x0 - pw * 0.02, y0, pw * 1.04, bindH, bindC);
            lc2_circ(gb, 'ring', x0 + pw * 0.3, y0 + bindH * 0.5, bindH * 0.22, sc.bg);
            lc2_circ(gb, 'ring', x0 + pw * 0.7, y0 + bindH * 0.5, bindH * 0.22, sc.bg);
            jzSetExpr(jzXf(B, 'ADBE Opacity'), HD + 'value*' + A);
            lc2_paint(B);
            var mb = lc2_meas(fb.text, size, { lead: 1.08, track: 0.02 }), cy = y0 + bindH + ph * 0.47;
            return lc2_box4(W / 2 - mb.w / 2, cy - mb.h / 2, W / 2 + mb.w / 2, cy + mb.h / 2);
        }
        // month grid; today's cell zooms into the lyric panel
        var cols = 7, rows = 5, gw = port ? W * 0.9 : Math.min(W * 0.84, H * 1.5), cell = gw / cols, gh = cell * rows * (port ? 1 : 0.72), rh = gh / rows, hdrH = ls * 2.2;
        var gx = W / 2 - gw / 2, gy = H / 2 - (gh + hdrH + ls * 3) / 2 + ls * 3 + hdrH, GA = 'oc(cl(time/0.3))*K', ZM = 'var z=ioc(cl((time-0.12)/0.35)),dim=1-z*0.55;';
        var h1 = lc2_T(ctx, String(month + 1), { font: font, size: ls * 2.6, align: 'left', x: gx, y: gy - hdrH - ls * 1.7, color: sc.fg });
        var h2 = lc2_T(ctx, mon, { font: jzMonoF(ctx), size: ls, track: 0.4, align: 'left', x: gx + ls * 2.6 * (month >= 9 ? 1.25 : 1), y: gy - hdrH - ls * 1.3, color: sc.sub });
        lc2_op(ctx, h1, GA); lc2_op(ctx, h2, GA);
        var wdi = [], a2c = jzContrast(sc.accent2, sc.bg) > 1.6 ? sc.accent2 : sc.sub;
        for (i = 0; i < 7; i++) wdi.push({ t: LC2_WD_E[i], x: gx + (i + 0.5) * cell, y: gy - hdrH * 0.45, c: i === 0 ? sc.accent : i === 6 ? a2c : sc.sub });
        var WM = lc2_multi(ctx, wdi, { font: jzMonoF(ctx), size: ls * 0.8, track: 0.2, color: sc.sub, name: 'weekdays' });
        lc2_op(ctx, WM.L, GA);
        var GL = lc2_S(ctx, 'grid', 0, 0), gg = lc2_G(GL, 'rows');
        for (i = 0; i <= rows; i++) {
            var gl1 = lc2_line(gg, 'row ' + i, [[gx, gy + i * rh], [gx + gw, gy + i * rh]], sc.sub, 1 * k);
            jzAddTrimPaths(gl1, HD + '100*ioc(cl((time-' + jzN(i * 0.03) + ')/0.4))*K');
        }
        jzSetExpr(jzXf(GL, 'ADBE Opacity'), HD + ZM + '45*dim');
        lc2_paint(GL);
        var its = [], D = [], tcx = gx, tcy = gy, d2;
        for (d2 = 1; d2 <= days; d2++) {
            var idx = off + d2 - 1, cc = idx % 7, rr = Math.floor(idx / 7) % rows, x = gx + cc * cell, y = gy + rr * rh;
            if (d2 === day) { tcx = x; tcy = y; continue; }
            its.push({ t: String(d2), x: x + cell * 0.08, y: y + rh * 0.22, c: cc === 0 ? sc.accent : sc.fg }); D.push(0.02 + idx * 0.006);
        }
        var DM = lc2_multi(ctx, its, { font: jzMonoF(ctx), size: Math.min(rh * 0.28, cell * 0.24), align: 'left', color: sc.fg, name: 'dates' });
        lc2_lineAlpha(DM, HD + ZM + 'var D=' + jzArrExpr(D) + ';', 'a=cl((time-D[l])/0.15)*K*dim*0.8');
        // today's panel (a rig scaled from the cell up to the panel; the lyric rides on it)
        var PW = port ? W * 0.84 : Math.min(W * 0.7, gw * 0.9), PH = port ? H * 0.34 : H * 0.5, X1 = W / 2 - PW / 2, Y1 = gy + gh / 2 - PH / 2;
        var plate = lc2_plate(sc, [sc.accent, sc.ink]), tc = lc2_onCol(sc, plate);
        var PZ = HD + ZM + 'var bx=lp(' + jzN(tcx) + ',' + jzN(X1) + ',z),by=lp(' + jzN(tcy) + ',' + jzN(Y1) + ',z),bw=lp(' + jzN(cell) + ',' + jzN(PW) + ',z),bh=lp(' + jzN(rh) + ',' + jzN(PH) + ',z),pa=cl(time/0.12)*K;';
        var PR = lc2_S(ctx, 'today', X1, Y1), gpn = lc2_G(PR, 'panel');
        var gsd = lc2_shadow(gpn, sc, 0, 0, PW, PH, 0, u * 0.015); lc2_gx(gsd, 'op', PZ + lc2_shOp(sc) + '*z');
        lc2_box(gpn, 'plate', 0, 0, PW, PH, plate);
        jzSetExpr(jzXf(PR, 'ADBE Position'), PZ + '[bx,by]');
        jzSetExpr(jzXf(PR, 'ADBE Scale'), PZ + '[100*bw/' + jzN(PW) + ',100*bh/' + jzN(PH) + ']');
        jzSetExpr(jzXf(PR, 'ADBE Opacity'), PZ + 'value*pa');
        lc2_paint(PR);
        var s0 = PH * 0.12, DN = lc2_T(ctx, String(day), { font: jzMonoF(ctx), size: s0, align: 'left', x: X1, y: Y1, color: tc });
        jzSetExpr(jzXf(DN, 'ADBE Position'), PZ + '[bx+bw*0.04,by+bh*0.13]');
        jzSetExpr(jzXf(DN, 'ADBE Scale'), PZ + 'var s=Math.max(' + jzN(rh * 0.28) + ',' + jzN(s0) + '*bw/' + jzN(PW) + ')/' + jzN(s0) + ';[value[0]*s,value[1]*s]');
        jzSetExpr(jzXf(DN, 'ADBE Opacity'), PZ + 'value*pa');
        var s1 = Math.max(6 * k, PH * 0.06), WDN = lc2_T(ctx, LC2_WD_E[wd], { font: jzMonoF(ctx), size: s1, track: 0.3, align: 'right', x: X1, y: Y1, color: tc });
        jzSetExpr(jzXf(WDN, 'ADBE Position'), PZ + '[bx+bw*0.96,by+bh*0.12]');
        jzSetExpr(jzXf(WDN, 'ADBE Scale'), PZ + 'var s=Math.max(' + jzN(6 * k) + ',' + jzN(s1) + '*bw/' + jzN(PW) + ')/' + jzN(s1) + ';[value[0]*s,value[1]*s]');
        jzSetExpr(jzXf(WDN, 'ADBE Opacity'), PZ + 'value*pa*z');
        var fb2 = lc2_fitBlock(t0, PW * 0.88, PH * 0.62, { lead: 1.08, track: 0.02 }, 3);
        var T2 = lc2_T(ctx, fb2.text, { font: font, size: Math.min(fb2.size, u * 0.2), x: X1 + PW / 2, y: Y1 + PH * 0.57, lead: 1.08, track: 0.02, color: tc, name: t0 });
        lc2_kid(T2, PR); lc2_main(ctx, T2, 0.2);
        return lc2_box4(X1, Y1, X1 + PW, Y1 + PH);
    }
});

/* ================================================================== 21 chochin \u2014 \u63D0\u706F */
// a paper lantern drawn around its top-centre (0, 0): body polygon, bamboo ribs, top / bottom caps; returns { y0, bh }
function lc2_lantern(g, sc, lw, lh, bodyC, k) {
    var capH = lh * 0.07, capC = lc2_plate(sc, [jzDarkest(sc), sc.ink], bodyC, 1.6), y0 = capH, y1 = lh - capH, bh = y1 - y0, pts = [], M = 24, i;
    function hw(t) { return lw / 2 * (0.62 + 0.38 * Math.sin(Math.PI * t)); }
    for (i = 0; i <= M; i++) pts.push([hw(i / M), y0 + bh * i / M]);
    for (i = M; i >= 0; i--) pts.push([-hw(i / M), y0 + bh * i / M]);
    lc2_poly(g, 'body', pts, bodyC);
    var ribs = [], R = 13;
    for (i = 1; i < R; i++) { var t = i / R, y = y0 + bh * t, w2 = hw(t); ribs.push([[-w2, y], [-w2 * 0.5, y + bh * 0.026], [0, y + bh * 0.035], [w2 * 0.5, y + bh * 0.026], [w2, y]]); }
    lc2_lines2(g, 'ribs', ribs, lc2_onCol(sc, bodyC), Math.max(1 * k, lw * 0.006), { op: 22 });
    lc2_box(g, 'cap top', -lw * 0.33, 0, lw * 0.66, capH * 1.05, capC, { r: capH * 0.2 });
    lc2_box(g, 'cap bottom', -lw * 0.33, y1 - capH * 0.05, lw * 0.66, capH * 1.05, capC, { r: capH * 0.2 });
    return { y0: y0, bh: bh };
}
jzReg('layout', 'chochin', {
    plan: function (rng, cut, st) {
        var n = cut.n, lat = jzHasLatin(cut.text), variant = n <= 6 && !lat ? rng.pick(['single', 'single', 'row']) : 'row', m = Math.min(6, Math.ceil(n / 2));
        var units = variant === 'row' ? (lat ? lc2_splitK(cut.text, 6) : n <= 6 ? lc2_charUnits(cut.text) : lc2_splitK(cut.text, m, m)) : [lat ? jzFlat(cut.text) : jzStrip(cut.text)];
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), variant: variant, units: units, body: rng.pick(['accent', 'accent', 'paper']), ph: rng.range(0, 6) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), k = lc2_k(ctx), port = jzPortrait(ctx), HD = lc2_H(ctx), i, j;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), variant = jzP(ctx, 'variant', 'row'), ph = jzP(ctx, 'ph', 0), C = lc2_card(sc);
        var paperB = jzP(ctx, 'body', 'accent') === 'paper' && jzContrast(C.fill, sc.bg) >= 1.5;
        var bodyC = paperB ? C.fill : lc2_plate(sc, [sc.accent, sc.ink], sc.bg, 1.3), tc = paperB ? lc2_plate(sc, [sc.accent, jzDarkest(sc)], C.fill, 2.2) : lc2_onCol(sc, bodyC);
        var u0 = jzP(ctx, 'units', null), units = [];
        if (u0 && u0.length) { for (i = 0; i < u0.length; i++) if (u0[i]) units.push(String(u0[i])); }
        if (!units.length) units = [jzStrip(c.text)];
        var FL = 'var fl=0.8+0.2*(0.5+0.5*Math.sin(time*5.3+SD)*Math.cos(time*2.1+SD*0.7));';
        var AL = 'var ein=oc(cl(time/0.5)),a=cl(ein*2)*(1-cl((PO-0.4)/0.6));';
        function glowLayer(name) { var GLw = lc2_S(ctx, name, 0, 0); jzXf(GLw, 'ADBE Opacity').setValue(100); return GLw; }
        function blur(L, r) { var e = jzEffect(L, 'ADBE Gaussian Blur 2', 'JZ Lantern Glow'); jzEP(e, 1, r); jzEP(e, 3, 1); }
        if (variant === 'single' && !jzHasLatin(units[0])) {
            var t = units[0], n = jzCount(t), lw = port ? W * 0.56 : Math.min(W * 0.34, H * 0.56), lh = Math.min(lw * 1.45, H * 0.8);
            var top = H / 2 - lh / 2 + H * 0.03, TOPX = HD + AL + 'var dy=-(1-ein)*' + jzN(H * 0.3) + ';';
            var ST = lc2_S(ctx, 'cord', 0, 0);
            var sp = lc2_sub(lc2_G(ST, 'cord'), 'line'), rp = jzAddRect(sp, 10, 10, 0);
            jzSetExpr(rp.property('ADBE Vector Rect Size'), TOPX + '[' + jzN(Math.max(2 * k, u * 0.003)) + ',Math.max(1,' + jzN(top + 5 * k) + '+dy)]');
            jzSetExpr(rp.property('ADBE Vector Rect Position'), TOPX + '[' + jzN(W / 2) + ',(' + jzN(top - 5 * k) + '+dy)/2]');
            jzAddFill(sp, sc.sub);                                  // (after the rect is set up: adding invalidates `rp` in AE)
            jzSetExpr(jzXf(ST, 'ADBE Opacity'), TOPX + 'value*a');
            var G = glowLayer('lantern glow'); lc2_circ(lc2_G(G, 'glow'), 'glow', W / 2, top + lh / 2, lw * 1.05, bodyC, { op: jzLum(sc.bg) > 0.5 ? 12 : 22 }); blur(G, lw * 0.55);
            jzSetExpr(jzXf(G, 'ADBE Position'), TOPX + '[value[0],value[1]+dy]');
            jzSetExpr(jzXf(G, 'ADBE Opacity'), TOPX + FL + 'value*a*fl');
            var R = lc2_S(ctx, 'lantern', W / 2, top), LL = lc2_lantern(lc2_G(R, 'lantern'), sc, lw, lh, bodyC, k);
            jzSetExpr(jzXf(R, 'ADBE Position'), TOPX + '[value[0],value[1]+dy]');
            jzSetExpr(jzXf(R, 'ADBE Rotate Z'), TOPX + 'value+Math.sin(time*1.3+' + jzN(ph) + ')*2.2+(1-ein)*6');
            jzSetExpr(jzXf(R, 'ADBE Opacity'), TOPX + 'value*a');
            lc2_paint(R);
            var cols = n > 5 ? 2 : 1, vt = cols > 1 ? lc2_brk(t, Math.ceil(n / 2)) : t, vl = lc2_lines(vt), per = 1;
            for (i = 0; i < vl.length; i++) per = Math.max(per, jzCount(vl[i]));
            var size = Math.min(LL.bh * 0.8 / per, lw * (cols > 1 ? 0.3 : 0.46));
            var VV = lc2_V(ctx, vt, { font: font, size: size, color: tc, x: W / 2, y: top + LL.y0 + LL.bh / 2 - per * size * 0.5, lead: 1.15, name: t });
            lc2_kid(VV.L, R); lc2_main(ctx, VV.L, 0.2);
            return lc2_box4(W / 2 - lw / 2, top, W / 2 + lw / 2, top + lh);
        }
        // a string of festival lanterns
        var kq = units.length, rowsN = port && kq > 3 ? 2 : 1, perR = Math.ceil(kq / rowsN), spx = W * 0.88 / perR;
        var lw2 = Math.min(spx * 0.78, H * (rowsN > 1 ? 0.2 : 0.3)), lh2 = lw2 * 1.4, bb = null, r;
        var WR = lc2_S(ctx, 'wires', 0, 0), gw = lc2_G(WR, 'wires');
        var GLo = glowLayer('lantern glow'), gG = lc2_G(GLo, 'glows'), rigs = [];
        for (r = 0; r < rowsN; r++) {
            var cnt = Math.min(perR, kq - r * perR), wireY = rowsN > 1 ? H * (r ? 0.55 : 0.14) : H * 0.22, x0 = W / 2 - (cnt - 1) / 2 * spx, sag = H * 0.05;
            var wy = function (x) { return wireY + sag * (1 - Math.pow((x - W / 2) / (W * 0.5), 2)); };
            var wp = []; for (i = 0; i <= 30; i++) { var xw = jzLerp(-10 * k, W + 10 * k, i / 30); wp.push([xw, wy(xw)]); }
            var wl = lc2_line(gw, 'wire ' + r, wp, sc.sub, Math.max(1.5 * k, u * 0.0025), { op: 80 });
            jzAddTrimPaths(wl, HD + '100*ioc(cl(time/0.5))*K');
            for (j = 0; j < cnt; j++) {
                var ii = r * perR + j, tt = units[ii], x = x0 + j * spx, ty = wy(x) + lw2 * 0.1, t1 = 0.08 + ii * 0.07;
                var EQ = HD + AL + 'var e=ob(cl((time-' + jzN(t1) + ')/0.35),1.4);';
                var R2 = lc2_S(ctx, 'lantern ' + (ii + 1), x, ty), g2 = lc2_G(R2, 'lantern');
                lc2_line(g2, 'hook', [[0, -lw2 * 0.1], [0, 0]], sc.sub, Math.max(1.5 * k, u * 0.002));
                var L2 = lc2_lantern(g2, sc, lw2, lh2, bodyC, k);
                jzSetExpr(jzXf(R2, 'ADBE Rotate Z'), EQ + 'value+Math.sin(time*1.6+' + jzN(ii * 1.1 + ph) + ')*3+(1-e)*10*' + (ii % 2 ? 1 : -1));
                jzSetExpr(jzXf(R2, 'ADBE Scale'), EQ + 'var s=Math.max(0,e);[value[0]*s,value[1]*s]');
                jzSetExpr(jzXf(R2, 'ADBE Opacity'), EQ + 'value*a*(time<' + jzN(t1) + '?0:1)');
                lc2_paint(R2);
                var gl = lc2_circ(gG, 'glow ' + ii, x, ty + lh2 / 2, lw2 * 1.0, bodyC, { op: jzLum(sc.bg) > 0.5 ? 12 : 22 });
                lc2_gx(gl, 'sc', EQ + 'var s=100*Math.max(0,e);[s,s]'); lc2_gx(gl, 'anc', '[' + jzN(x) + ',' + jzN(ty + lh2 / 2) + ']'); lc2_gset(gl, 'pos', [x, ty + lh2 / 2]);
                var lat = jzHasLatin(tt), n2 = jzCount(tt), TL;
                if (lat) {
                    var sz = Math.min(lc2_fitSize(tt, lw2 * 0.8, L2.bh * 0.5, {}), lw2 * 0.4);
                    TL = lc2_T(ctx, tt, { font: font, size: sz, x: x, y: ty + L2.y0 + L2.bh / 2, color: tc });
                } else {
                    var sz2 = Math.min(L2.bh * 0.78 / Math.max(1, n2), lw2 * 0.5);
                    TL = lc2_V(ctx, jzStrip(tt), { font: font, size: sz2, color: tc, x: x, y: ty + L2.y0 + L2.bh / 2 - n2 * sz2 * 0.5, lead: 1.3 }).L;
                }
                lc2_kid(TL, R2); lc2_main(ctx, TL, t1 + 0.12);
                bb = jzUnion(bb, lc2_box4(x - lw2 / 2, ty, x + lw2 / 2, ty + lh2));
            }
        }
        lc2_paint(WR);
        jzSetExpr(jzXf(GLo, 'ADBE Opacity'), HD + AL + FL + 'value*a*fl');
        blur(GLo, lw2 * 0.5);
        lc2_paint(GLo);
        GLo.moveAfter(WR);
        return bb;
    }
});

/* ================================================================== 22 routeMap \u2014 \u8DEF\u7DDA\u56F3 */
jzReg('layout', 'routeMap', {
    plan: function (rng, cut, st) {
        var n = cut.n, kk = n <= 6 ? 2 : n <= 10 ? 3 : 4;
        return { font: rng.pick(jzFontsOf(st, ['display', 'body'])), chunks: n <= 3 ? lc2_charUnits(cut.text) : lc2_splitK(cut.text, kk),
            shape: rng.pick(['straight', 'bend', 'straight']), letter: rng.pick(['Z', 'J', 'M', 'K', 'S']), num0: rng.int(1, 14) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), kx = lc2_k(ctx), ls = jzSmallSize(ctx), port = jzPortrait(ctx), HD = lc2_H(ctx), i;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), letter = jzP(ctx, 'letter', 'Z'), num0 = jzP(ctx, 'num0', 1), c0 = jzP(ctx, 'chunks', null), chunks = [];
        if (c0 && c0.length) { for (i = 0; i < c0.length; i++) if (c0[i]) chunks.push(jzTrim(String(c0[i]))); }
        if (!chunks.length) chunks = [jzTrim(c.text)];
        var k = chunks.length, lineC = lc2_plate(sc, [sc.accent, sc.accent2, sc.fg]), lw = Math.max(8 * kx, u * 0.024);
        var bend = !port && jzP(ctx, 'shape', 'straight') === 'bend' && k >= 2, pts;
        if (port) pts = [[W * 0.22, H * 0.07], [W * 0.22, H * 0.93]];
        else if (bend) pts = [[W * 0.04, H * 0.36], [W * 0.42, H * 0.36], [W * 0.58, H * 0.64], [W * 0.96, H * 0.64]];
        else pts = [[W * 0.04, H * 0.54], [W * 0.96, H * 0.54]];
        var segL = [], tot = 0, PX = [], PY = [];
        for (i = 1; i < pts.length; i++) { var dd = Math.sqrt(Math.pow(pts[i][0] - pts[i - 1][0], 2) + Math.pow(pts[i][1] - pts[i - 1][1], 2)); segL.push(dd); tot += dd; }
        for (i = 0; i < pts.length; i++) { PX.push(pts[i][0]); PY.push(pts[i][1]); }
        function spread(a0, a1, m, j) { return m > 1 ? jzLerp(a0, a1, j / (m - 1)) : (a0 + a1) / 2; }
        var st = [];
        if (port) for (i = 0; i < k; i++) { var yy = spread(H * 0.15, H * 0.85, k, i); st.push({ x: pts[0][0], y: yy, f: (yy - pts[0][1]) / tot, side: 1, maxW: W * 0.66 }); }
        else if (bend) {
            var cu = Math.ceil(k / 2), cl0 = k - cu;
            for (i = 0; i < k; i++) {
                var up = i < cu, j = up ? i : i - cu, mm = up ? cu : cl0, xx = up ? spread(W * 0.09, W * 0.38, mm, j) : spread(W * 0.63, W * 0.92, mm, j);
                st.push({ x: xx, y: up ? pts[0][1] : pts[2][1], f: up ? (xx - pts[0][0]) / tot : (segL[0] + segL[1] + xx - pts[2][0]) / tot, side: up ? -1 : 1, maxW: mm > 1 ? W * 0.29 * 0.84 : W * 0.34 });
            }
        } else {
            var alt = k >= 3, gap = W * 0.76 / Math.max(1, k - 1);
            for (i = 0; i < k; i++) { var x2 = spread(W * 0.12, W * 0.88, k, i); st.push({ x: x2, y: pts[0][1], f: (x2 - pts[0][0]) / tot, side: alt && i % 2 ? 1 : -1, maxW: Math.min(W * 0.42, (alt ? gap * 2 : gap) * 0.88) }); }
        }
        var maxH = port ? Math.min(H * 0.7 / k * 0.45, H * 0.1) : H * 0.13, size = u * (port ? 0.15 : 0.13);
        for (i = 0; i < k; i++) size = Math.min(size, lc2_fitSize(chunks[i], st[i].maxW, maxH, { track: 0.02 }));
        var F = []; for (i = 0; i < k; i++) F.push(st[i].f);
        var LE = HD + 'var le=ioc(cl(time/0.6))*(1-ic(PO)),prog=cl((time-0.3)/' + jzN(Math.max(0.4, c.dur * 0.7)) + '),NK=' + k + ',cur=Math.min(NK-1,Math.floor(prog*NK)),F=' + jzArrExpr(F) + ';';
        // the line + the train running on it
        var S = lc2_S(ctx, 'route line', 0, 0), g = lc2_G(S, 'route');
        var ln = lc2_line(g, 'line', pts, lineC, lw, { cap: 2 });
        jzAddTrimPaths(ln, LE + '100*le');
        var tw = lw * (port ? 1.3 : 2.6), th = lw * (port ? 2.6 : 1.3), gt = lc2_box(g, 'train', -tw / 2, -th / 2, tw, th, sc.fg, { r: Math.min(tw, th) * 0.45, stroke: sc.bg, sw: Math.max(2 * kx, lw * 0.18) });
        lc2_gx(gt, 'pos', LE + 'var f1=F[cur],f0=cur>0?F[cur-1]:Math.max(0,f1-0.08),f=lp(f0,f1,ioc(cl(prog*NK-cur))),PX=' + jzArrExpr(PX) + ',PY=' + jzArrExpr(PY) + ',SL=' + jzArrExpr(segL) +
            ';var d=f*' + jzN(tot) + ',x=PX[PX.length-1],y=PY[PY.length-1];for(var i=0;i<SL.length;i++){if(d<=SL[i]||i==SL.length-1){var q=cl(d/SL[i]);x=lp(PX[i],PX[i+1],q);y=lp(PY[i],PY[i+1],q);break;}d-=SL[i];}[x,y]');
        lc2_gx(gt, 'op', LE + 'le>0.9?100*K:0');
        lc2_paint(S);
        // stations, code badges, line badge
        var S2 = lc2_S(ctx, 'stations', 0, 0), g2 = lc2_G(S2, 'stations'), bs = ls * 0.9, codes = [], roms = [], bb = null;
        for (i = 0; i < k; i++) {
            var P = st[i], r = lw * 0.8, rC = lw * 1.05, TA = LE + 'var ta=cl((le*1.05-' + jzN(P.f) + ')*6);';
            var gs = lc2_sub(g2, 'station ' + (i + 1));
            var gc = lc2_circ(gs, 'stop', 0, 0, r, sc.bg, { stroke: sc.fg, sw: Math.max(3 * kx, lw * 0.32) });
            lc2_gx(gc, 'sc', TA + '[100*ta,100*ta]'); lc2_gx(gc, 'op', TA + '(cur==' + i + ')?0:100');
            var gk = port ? lc2_box(gs, 'here', -rC * 1.05, -rC * 1.5, rC * 2.1, rC * 3, sc.bg, { r: rC, stroke: sc.fg, sw: Math.max(3 * kx, lw * 0.35) })
                : lc2_box(gs, 'here', -rC * 1.5, -rC * 1.05, rC * 3, rC * 2.1, sc.bg, { r: rC, stroke: sc.fg, sw: Math.max(3 * kx, lw * 0.35) });
            lc2_gx(gk, 'op', TA + '(cur==' + i + '&&ta>0)?100:0');
            lc2_gset(gs, 'pos', [P.x, P.y]);
            var code = letter + jzPad(num0 + i, 2), rom = lc2_rom(chunks[i]), bx, by, T;
            if (port) {
                bx = -lw * 1.4 - bs * 2.8; by = -bs * 0.9;
                codes.push({ t: code, x: P.x - lw * 1.4 - bs * 1.4, y: P.y });
                var lx = P.x + lw * 1.8;
                T = lc2_T(ctx, chunks[i], { font: font, size: size, x: lx, y: P.y - (rom ? ls * 0.4 : 0), align: 'left', track: 0.02, color: sc.fg, name: chunks[i] });
                var mw = lc2_meas(chunks[i], size, { track: 0.02 });
                bb = jzUnion(bb, lc2_box4(lx, P.y - size / 2, lx + mw.w, P.y + size / 2));
                if (rom) roms.push({ t: rom, x: lx, y: P.y + size * 0.55, f: P.f });
            } else {
                var dS = P.side, w = lc2_meas(chunks[i], size, { track: 0.02 }).w, lx2 = jzClamp(P.x, W * 0.04 + w / 2, W * 0.96 - w / 2);
                bx = -bs * 1.4; by = dS * (lw * 1.25) + (dS < 0 ? -bs * 1.8 : 0);
                codes.push({ t: code, x: P.x, y: P.y + by + bs * 0.9 });
                var ny = P.y + dS * (lw * 1.25 + bs * 2.2 + size * 0.5 + (dS < 0 && rom ? ls * 1.2 : 0));
                T = lc2_T(ctx, chunks[i], { font: font, size: size, x: lx2, y: ny, track: 0.02, color: sc.fg, name: chunks[i] });
                bb = jzUnion(bb, lc2_box4(lx2 - w / 2, ny - size / 2, lx2 + w / 2, ny + size / 2));
                if (rom) roms.push({ t: rom, x: lx2, y: dS < 0 ? ny + size * 0.5 + ls * 0.7 : ny + size * 0.5 + ls * 0.9, f: P.f });
            }
            var gb = lc2_box(gs, 'code badge', bx, by, bs * 2.8, bs * 1.8, null, { r: bs * 0.3, stroke: lineC, sw: Math.max(2 * kx, bs * 0.12) });
            lc2_gx(gb, 'op', TA + '100*ta*K');
            lc2_main(ctx, T, 0.1 + i * 0.12);
        }
        var R = lw * 1.7, cx2 = port ? pts[0][0] : pts[0][0] + R * 0.2, cy2 = port ? pts[0][1] + R * 0.1 : pts[0][1] + (st[0].side < 0 ? 1 : -1) * R * 2;
        var BA = HD + 'var ba=ob(cl((time-0.05)/0.4),1.70158)*K;';
        var gl = lc2_circ(g2, 'line badge', 0, 0, R, sc.bg, { stroke: lineC, sw: Math.max(3 * kx, lw * 0.5) });
        lc2_gset(gl, 'pos', [cx2, cy2]); lc2_gx(gl, 'sc', BA + 'var s=100*Math.max(0,ba);[s,s]');
        lc2_paint(S2);
        S2.moveBefore(S);
        var CM = lc2_multi(ctx, codes, { font: jzMonoF(ctx), size: bs * 0.8, color: sc.fg, name: 'station codes' });
        lc2_lineAlpha(CM, LE + '', 'a=cl((le*1.05-F[l])*6)*K');
        if (roms.length) {
            var FR = []; for (i = 0; i < roms.length; i++) FR.push(roms[i].f);
            var RM = lc2_multi(ctx, roms, { font: jzMonoF(ctx), size: ls * (port ? 0.8 : 0.75), track: 0.15, align: port ? 'left' : null, color: sc.sub, name: 'readings' });
            lc2_lineAlpha(RM, LE + 'var FR=' + jzArrExpr(FR) + ';', 'a=cl((le*1.05-FR[l])*6)*K');
        }
        var BL = lc2_T(ctx, letter, { font: 'gothic_black', size: R * 1.1, x: cx2, y: cy2, color: sc.fg });
        jzSetExpr(jzXf(BL, 'ADBE Scale'), BA + 'var s=Math.max(0,ba);[value[0]*s,value[1]*s]');
        return bb;
    }
});

/* ================================================================== 23 stationSign \u2014 \u99C5\u540D\u6A19 */
jzReg('layout', 'stationSign', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'body'])), letter: rng.pick(['JZ', 'LY', 'KT', 'SN']), num: rng.int(1, 36), band: rng.pick(['accent', 'accent2', 'ink']), posts: rng.chance(0.7) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), k = lc2_k(ctx), ls = jzSmallSize(ctx), port = jzPortrait(ctx), HD = lc2_H(ctx), i;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), letter = jzP(ctx, 'letter', 'JZ'), num = jzP(ctx, 'num', 1), band = jzP(ctx, 'band', 'accent'), posts = !!jzP(ctx, 'posts', true);
        var C = lc2_card(sc), t0 = jzTrim(c.text), bw = port ? W * 0.92 : Math.min(W * 0.86, H * 2.2), bh = port ? bw * 0.62 : bw * 0.36;
        var CX = W / 2, CY = H / 2, x0 = -bw / 2, y0 = -bh / 2 - (port ? H * 0.04 : H * 0.03);
        var E = HD + 'var e=oc(cl(time/0.45)),eo=ic(PO),a=cl(e*2)*(1-eo),be=ioc(cl((time-0.15)/0.5))*K,fa=oc(cl((time-0.25)/0.4))*K;';
        var boardC = jzContrast(C.fill, sc.bg) >= 1.4 ? C.fill : lc2_plate(sc, [sc.ink, sc.fg]), txC = lc2_onCol(sc, boardC);
        var bandC = lc2_plate(sc, band === 'accent2' ? [sc.accent2, sc.accent] : band === 'ink' ? [sc.ink, sc.accent] : [sc.accent, sc.accent2], boardC, 1.4);
        var R = lc2_S(ctx, 'station sign', CX, CY), g = lc2_G(R, 'sign');
        if (posts) { var pw = bw * 0.018; lc2_box(g, 'post', x0 + bw * 0.12 - pw / 2, y0 + bh, pw, H * 1.3, sc.sub); lc2_box(g, 'post', x0 + bw * 0.88 - pw / 2, y0 + bh, pw, H * 1.3, sc.sub); }
        lc2_shadow(g, sc, x0, y0, bw, bh, bh * 0.04, u * 0.012);
        lc2_box(g, 'board', x0, y0, bw, bh, boardC, { r: bh * 0.04 });
        var bandY = y0 + bh * (port ? 0.66 : 0.64), bandH = bh * (port ? 0.075 : 0.1), bxW = bw * 0.28;
        var gB = lc2_box(g, 'band', x0, bandY, bw, bandH, bandC);
        lc2_gset(gB, 'anc', [x0, bandY]); lc2_gset(gB, 'pos', [x0, bandY]); lc2_gx(gB, 'sc', E + '[100*be,100]');
        lc2_gx(lc2_box(g, 'name box', -bxW / 2, bandY - bandH * 0.25, bxW, bandH * 1.5, bandC), 'op', E + 'be>0.5?100:0');
        lc2_gx(lc2_poly(g, 'arrow', [[x0 + bw * 0.97, bandY - bandH * 0.4], [x0 + bw, bandY + bandH / 2], [x0 + bw * 0.97, bandY + bandH * 1.4]], bandC), 'op', E + '100*cl((be-0.5)*2)');
        var bs = bh * (port ? 0.16 : 0.2), ny = y0 + bh * 0.3, bx2 = x0 + bw * 0.06, by2 = ny - bs / 2;
        var gN = lc2_sub(g, 'number badge');
        lc2_box(gN, 'badge', bx2, by2, bs, bs * 1.1, boardC, { r: bs * 0.12, stroke: bandC, sw: Math.max(2 * k, bs * 0.08) });
        lc2_box(gN, 'badge top', bx2, by2, bs, bs * 0.34, bandC);
        lc2_gx(gN, 'op', E + '100*fa');
        jzSetExpr(jzXf(R, 'ADBE Position'), E + '[value[0],value[1]-(1-e)*' + jzN(H * 0.2) + '+eo*' + jzN(H * 0.05) + ']');
        jzSetExpr(jzXf(R, 'ADBE Opacity'), E + 'value*a');
        lc2_paint(R);
        // prev / next from the rest of the line
        var Lt = jzFlat(c.lineText || ''), Tt = jzFlat(t0), idx = Lt.indexOf(Tt);
        function cutS(s2, n2, fromEnd) { var arr = jzChars(jzTrim(s2)); return (fromEnd ? arr.slice(Math.max(0, arr.length - n2)) : arr.slice(0, n2)).join(''); }
        var prev = idx > 0 ? cutS(Lt.slice(0, idx), 6, true) : '', next = idx >= 0 && idx + Tt.length < Lt.length ? cutS(Lt.slice(idx + Tt.length), 6, false) : '';
        var rom = jzRomajiOf(ctx), sub2 = rom ? rom.charAt(0) + rom.slice(1).toLowerCase() : ('No.' + jzLineNo(ctx));
        var py = bandY + bandH + (bh - (bandY - y0) - bandH) * 0.5, kids = [];
        kids.push([lc2_T(ctx, sub2, { font: jzBodyF(ctx), size: ls * (port ? 1.1 : 1.2), track: 0.1, x: CX, y: CY + bandY - bandH * 0.25 - ls * 1.3, color: txC }), 1]);
        kids.push([lc2_T(ctx, letter, { font: jzMonoF(ctx), size: bs * 0.24, x: CX + bx2 + bs / 2, y: CY + by2 + bs * 0.17, color: lc2_onCol(sc, bandC) }), 1]);
        kids.push([lc2_T(ctx, jzPad(num, 2), { font: 'gothic_black', size: bs * 0.5, x: CX + bx2 + bs / 2, y: CY + by2 + bs * 0.72, color: txC }), 1]);
        kids.push([lc2_T(ctx, prev || ('\u2190 ' + letter + jzPad(Math.max(0, num - 1), 2)), { font: prev ? font : jzMonoF(ctx), size: ls * 1.25, align: 'left', x: CX + x0 + bw * 0.04, y: CY + py, color: txC }), prev ? 1 : 0.7]);
        kids.push([lc2_T(ctx, next || (letter + jzPad(num + 1, 2) + ' \u2192'), { font: next ? font : jzMonoF(ctx), size: ls * 1.25, align: 'right', x: CX + x0 + bw * 0.96, y: CY + py, color: txC }), next ? 1 : 0.7]);
        for (i = 0; i < kids.length; i++) { lc2_kid(kids[i][0], R); jzSetExpr(jzXf(kids[i][0], 'ADBE Opacity'), E + 'value*fa*' + jzN(kids[i][1])); }
        var fb = lc2_fitBlock(t0, bw - (bw * 0.06 + bs * 1.4) * 2, bh * (port ? 0.42 : 0.4), { track: 0.12, lead: 1.05 }, port ? 2 : 1), size = Math.min(fb.size, bh * 0.36);
        var T = lc2_T(ctx, fb.text, { font: font, size: size, x: CX, y: CY + ny, track: 0.12, lead: 1.05, color: txC, name: t0 });
        lc2_kid(T, R); lc2_main(ctx, T, 0.15);
        return lc2_box4(CX + x0, CY + y0, CX + x0 + bw, CY + y0 + bh);
    }
});

/* ================================================================== 24 noren \u2014 \u6696\u7C3E */
jzReg('layout', 'noren', {
    plan: function (rng, cut, st) {
        var n = cut.n, m = Math.min(4, Math.ceil(n / 3));
        var units = n <= 4 ? lc2_charUnits(cut.text) : lc2_splitK(cut.text, m, m);
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), units: units, cloth: rng.pick(['ink', 'accent', 'ink']), mon: rng.chance(0.35), ph: rng.range(0, 6), wind: rng.pick([1, -1]) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), kx = lc2_k(ctx), port = jzPortrait(ctx), HD = lc2_H(ctx), i;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'serif')), mon = !!jzP(ctx, 'mon', false), ph = jzP(ctx, 'ph', 0), wind = jzP(ctx, 'wind', 1);
        var u0 = jzP(ctx, 'units', null), units = [];
        if (u0 && u0.length) { for (i = 0; i < u0.length; i++) if (u0[i]) units.push(lc2_vt(String(u0[i]))); }
        if (!units.length) units = [lc2_vt(c.text)];
        if (units.length === 1) units = ['', units[0], ''];
        var k = units.length, clothC = lc2_plate(sc, jzP(ctx, 'cloth', 'ink') === 'accent' ? [sc.accent, sc.ink] : [sc.ink, sc.accent]), tc = lc2_onCol(sc, clothC);
        var nw = port ? W * 0.88 : Math.min(W * 0.74, H * 1.3), nh = port ? H * 0.56 : H * 0.7, rodY = H * (port ? 0.18 : 0.12);
        var x0 = W / 2 - nw / 2, gap = nw * 0.012, pw = (nw - gap * (k - 1)) / k, lw = Math.max(4 * kx, u * 0.012), dark = jzDarkest(sc);
        var FO = HD + 'var fo=1-cl((PO-0.55)/0.45),part=ioc(PO);';
        var maxG = 1; for (i = 0; i < k; i++) maxG = Math.max(maxG, jzCount(units[i]));
        var hem = nh * 0.08, size = Math.min((nh - hem * 1.6) * 0.84 / Math.max(maxG, 1.6), pw * 0.66), bb = null;
        for (i = 0; i < k; i++) {
            var t1 = 0.04 + i * 0.06, px = x0 + i * (pw + gap), side = (i + 0.5) / k - 0.5, sg = side === 0 ? 1 : (side > 0 ? 1 : -1);
            var DR = FO + 'var drop=oc(cl((time-' + jzN(t1) + ')/0.45));';
            var R = lc2_S(ctx, 'noren ' + (i + 1), px + pw / 2, rodY), g = lc2_G(R, 'cloth');
            lc2_box(g, 'cloth', -pw / 2, -lw * 0.2, pw, nh, clothC);
            lc2_box(g, 'hem', -pw / 2, -lw * 0.2, pw, hem, jzMixHex(clothC, dark, 0.25));
            lc2_box(g, 'fold left', -pw / 2, hem, pw * 0.22, nh - hem - lw * 0.2, dark, { op: 13 });
            lc2_box(g, 'fold right', pw / 2 - pw * 0.18, hem, pw * 0.18, nh - hem - lw * 0.2, dark, { op: 9 });
            if (mon && k > 1 && i === Math.floor(k / 2) - (k % 2 ? 0 : 1)) {
                var mx0 = k % 2 ? 0 : pw / 2 + gap / 2, my0 = hem + pw * 0.2, mr = pw * 0.1, gm = lc2_sub(g, 'crest');
                lc2_circ(gm, 'disc', mx0, my0, mr, tc); lc2_circ(gm, 'ring', mx0, my0, mr * 0.62, clothC); lc2_circ(gm, 'core', mx0, my0, mr * 0.3, tc);
            }
            jzSetExpr(jzXf(R, 'ADBE Rotate Z'), DR + 'value+Math.sin(time*1.2+' + jzN(i * 0.8 + ph) + ')*1.1*' + wind + '+part*' + sg + '*26*' + jzN(0.4 + Math.abs(side) * 1.4));
            jzSetExpr(jzXf(R, 'ADBE Opacity'), DR + 'value*fo*(drop>0?1:0)');
            lc2_paint(R);
            // the cloth drops from the rod: a thin mask under the rod grown by the drop
            var EXP = DR + jzN(nh) + '*drop+' + jzN(lw - 2 * kx);
            lc2_clip(R, px - 2 * kx, rodY - lw, px + pw + 2 * kx, rodY - lw + 2 * kx, EXP);
            var t = units[i];
            if (t) {
                var g2 = jzCount(t), VV = lc2_V(ctx, t, { font: font, size: size, color: tc, x: px + pw / 2, y: rodY + hem + (nh - hem) * 0.5 - g2 * size * 0.52 + (mon ? pw * 0.12 : 0), track: 0.04, lead: 1.3, name: t });
                lc2_clip(VV.L, px - 2 * kx, rodY - lw, px + pw + 2 * kx, rodY - lw + 2 * kx, EXP);
                lc2_kid(VV.L, R); lc2_main(ctx, VV.L, t1 + 0.15);
                bb = jzUnion(bb, lc2_box4(px, rodY, px + pw, rodY + nh));
            }
        }
        // the rod on top
        var RD = lc2_S(ctx, 'rod', 0, 0), gr = lc2_box(lc2_G(RD, 'rod'), 'rod', x0 - nw * 0.06, rodY - lw / 2, nw * 1.12, lw, sc.sub, { r: lw / 2 });
        lc2_gset(gr, 'anc', [x0 - nw * 0.06, rodY]); lc2_gset(gr, 'pos', [x0 - nw * 0.06, rodY]); lc2_gx(gr, 'sc', HD + '[100*oc(cl(time/0.4)),100]');
        jzSetExpr(jzXf(RD, 'ADBE Opacity'), FO + 'value*fo');
        lc2_paint(RD);
        return bb;
    }
});

/* ================================================================== 25 tanzaku \u2014 \u77ED\u518A */
jzReg('layout', 'tanzaku', {
    plan: function (rng, cut, st) {
        var n = cut.n, k = n <= 6 ? 1 : n <= 11 ? 2 : 3;
        var font = rng.chance(0.4) ? 'brush' : rng.pick(jzFontsOf(st, ['serif', 'display']));
        var chunks = jzHasLatin(cut.text) ? lc2_splitK(cut.text, k) : lc2_splitK(jzStrip(cut.text), k, k), extra = rng.int(2, 3), cols = [];
        for (var i = 0; i < 6; i++) cols.push(rng.int(0, 4));
        return { font: font, chunks: chunks, extra: extra, cols: cols, ph: rng.range(0, 6), dir: rng.pick([1, -1]) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), kx = lc2_k(ctx), port = jzPortrait(ctx), HD = lc2_H(ctx), i;
        var font = jzP(ctx, 'font', jzSerifF(ctx)), extra = jzP(ctx, 'extra', 2), pc = jzP(ctx, 'cols', [0, 1, 2, 3, 4, 0]), ph = jzP(ctx, 'ph', 0), dir = jzP(ctx, 'dir', 1);
        var c0 = jzP(ctx, 'chunks', null), chunks = [];
        if (c0 && c0.length) { for (i = 0; i < c0.length; i++) if (c0[i]) chunks.push(lc2_vt(String(c0[i]))); }
        if (!chunks.length) chunks = [lc2_vt(c.text)];
        var k = chunks.length, C = lc2_card(sc), dark = jzDarkest(sc);
        var pal = [lc2_plate(sc, [sc.accent, sc.ink]), C.fill, lc2_plate(sc, [sc.accent2, sc.sub, sc.accent]), jzMixHex(sc.bg, sc.accent, 0.55), lc2_plate(sc, [sc.ink, sc.fg])];
        function by(x) { return H * 0.1 + (x / W) * H * 0.06 * dir + Math.sin(x / W * 3) * H * 0.012; }
        var BE = HD + 'var be=ioc(cl(time/0.5))*K;', stemC = jzMixHex(lc2_plate(sc, [sc.accent2, sc.sub]), sc.sub, 0.3);
        // bamboo branch, nodes, leaves
        var B = lc2_S(ctx, 'bamboo', 0, 0), gb = lc2_G(B, 'bamboo'), bp = [];
        for (i = 0; i <= 24; i++) { var xb = jzLerp(-20 * kx, W + 20 * kx, i / 24); bp.push([xb, by(xb)]); }
        jzAddTrimPaths(lc2_line(gb, 'stem', bp, stemC, Math.max(5 * kx, u * 0.01), { cap: 2 }), BE + '100*be');
        for (i = 1; i < 8; i++) {
            var nx = W * i / 8;
            lc2_gx(lc2_line(gb, 'node', [[nx, by(nx) - u * 0.009], [nx, by(nx) + u * 0.009]], jzMixHex(stemC, dark, 0.4), Math.max(3 * kx, u * 0.005)), 'op', BE + '(be>' + jzN(nx / W) + ')?100*K:0');
        }
        var Lf = u * 0.07, wd = u * 0.018;
        for (i = 0; i < 9; i++) {
            var lx = W * (0.05 + i * 0.115), gl = lc2_sub(gb, 'leaf');
            lc2_smooth(gl, 'leaf', [[0, 0], [Lf * 0.5, wd], [Lf, 0], [Lf * 0.5, -wd]], true); jzAddFill(gl, stemC);
            lc2_gset(gl, 'pos', [lx, by(lx)]);
            lc2_gx(gl, 'rot', HD + (i % 2 ? '35' : '-35') + '+Math.sin(time*1.4+' + i + ')*6');
            lc2_gx(gl, 'op', BE + '85*cl(be*9-' + i + ')*K');
        }
        lc2_paint(B);
        // strips: lyric strips in the middle, blank ones around
        var nS = k + (port ? 1 : extra), mid = Math.floor((nS - k) / 2), maxG = 1;
        for (i = 0; i < k; i++) maxG = Math.max(maxG, jzCount(chunks[i]));
        var sw = Math.min(W * (port ? 0.9 : 0.62) / (nS * 1.3), u * (port ? 0.2 : 0.16)), shMax = H * (port ? 0.7 : 0.72);
        var size = Math.min(sw * 0.64, (shMax - sw * 0.4) / (Math.max(maxG, 2.5) * 1.08 + 1.6)), bb = null;
        for (i = 0; i < nS; i++) {
            var isL = i >= mid && i < mid + k, t = isL ? chunks[i - mid] : '', x = W / 2 + (i - (nS - 1) / 2) * sw * 1.3 * (port ? 1 : 1.1), top = by(x) + u * 0.012;
            var sh = isL ? Math.max(sw * 2.6, jzCount(t) * size * 1.08 + size * 1.6) : sw * (2.4 + (i % 3) * 0.5), string = u * (0.03 + (i % 3) * 0.022);
            var t1 = 0.05 + Math.abs(i - (nS - 1) / 2) * 0.07, pci = pc[i % 6] || 0;
            var col = isL ? pal[pci % 3 === 1 ? 1 : pci % 3 === 2 ? 2 : 0] : pal[(pci + 3) % 5];
            var EQ = HD + 'var e=cl((time-' + jzN(t1) + ')/0.5),a=cl(e*3)*(1-cl((PO-0.4)/0.6));';
            var R = lc2_S(ctx, 'tanzaku ' + (i + 1), x, top), g = lc2_G(R, 'strip');
            lc2_line(g, 'string', [[0, 0], [0, string]], sc.sub, Math.max(1 * kx, u * 0.0016));
            lc2_shadow(g, sc, -sw / 2, string, sw, sh, 0, u * 0.008);
            lc2_box(g, 'paper', -sw / 2, string, sw, sh, col, jzContrast(col, sc.bg) < 1.3 ? { stroke: jzMixHex(sc.fg, col, 0.5), sw: 1.2 * kx } : null);
            lc2_circ(g, 'hole', 0, string + sw * 0.18, sw * 0.05, sc.bg);
            jzSetExpr(jzXf(R, 'ADBE Position'), EQ + '[value[0],value[1]-(1-ob(e,1.6))*' + jzN(H * 0.25) + ']');
            jzSetExpr(jzXf(R, 'ADBE Rotate Z'), EQ + 'value+Math.sin(time*1.3+' + jzN(i * 0.9 + ph) + ')*2.5+ic(PO)*' + (i % 2 ? 20 : -20));
            jzSetExpr(jzXf(R, 'ADBE Opacity'), EQ + 'value*a');
            lc2_paint(R);
            if (isL) {
                var VV = lc2_V(ctx, t, { font: font, size: size, color: lc2_onCol(sc, col), x: x, y: top + string + size * 0.9, track: 0.06, name: t });
                lc2_kid(VV.L, R); lc2_main(ctx, VV.L, t1 + 0.25);
                bb = jzUnion(bb, lc2_box4(x - sw / 2, top + string, x + sw / 2, top + string + sh));
            }
        }
        return bb;
    }
});

/* ================================================================== 26 omikuji \u2014 \u304A\u307F\u304F\u3058 */
// greeked copy: dashed rows / columns that read as small glyphs
function lc2_greek(g, name, x, y, w, h, lh, col, op, seed, vertical) {
    var gg = lh / 1.45, n = Math.min(160, Math.floor((vertical ? w : h) / lh)), span = vertical ? h : w, list = [], r;
    for (r = 0; r < n; r++) {
        var endP = jzR(seed, r, 3) < 0.12, ind = (r === 0 || jzR(seed, r - 1, 3) < 0.12) ? gg * 1.02 : 0;
        var len = (endP ? span * (0.2 + 0.55 * jzR(seed, r, 4)) : span) - ind;
        if (len <= gg) continue;
        if (vertical) { var xx = x + w - (r + 0.5) * lh; list.push([[xx, y + ind], [xx, y + ind + len]]); }
        else { var yy = y + (r + 0.5) * lh; list.push([[x + ind, yy], [x + ind + len, yy]]); }
    }
    if (!list.length) return null;
    var s = lc2_lines2(g, name, list, col, Math.max(1, gg * 0.72), { op: op });
    lc2_dash(s.property('ADBE Vectors Group').property(list.length + 1), gg * 0.78, gg * 0.24);
    return s;
}
// vertical labels at free positions (one glyph per line of ONE text layer): labels [{ t, x (column centre), y (top) }]
function lc2_vlabels(ctx, labels, o) {
    var it = [], i, j;
    for (i = 0; i < labels.length; i++) { var ch = jzChars(jzStrip(labels[i].t)); for (j = 0; j < ch.length; j++) it.push({ t: ch[j], x: labels[i].x, y: labels[i].y + (j + 0.5) * o.size * (1 + (o.track || 0)) }); }
    return lc2_multi(ctx, it, { font: o.font, size: o.size, color: o.color, name: o.name });
}
jzReg('layout', 'omikuji', {
    plan: function (rng, cut, st) {
        var font = rng.chance(0.4) ? 'brush' : rng.pick(jzFontsOf(st, ['serif'])), rank = rng.pick(LC2_KUJI), a = LC2_KUJI_CAT.slice(0), i;
        for (i = a.length - 1; i > 0; i--) { var j = Math.floor(rng.next() * (i + 1)), t = a[i]; a[i] = a[j]; a[j] = t; }
        return { font: font, rank: rank, cats: a.slice(0, 4) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), kx = lc2_k(ctx), ls = jzSmallSize(ctx), port = jzPortrait(ctx), HD = lc2_H(ctx), i;
        var font = jzP(ctx, 'font', jzSerifF(ctx)), rank = String(jzP(ctx, 'rank', '\u5927\u5409')), cats = jzP(ctx, 'cats', ['\u9858\u671B', '\u5F85\u4EBA', '\u5931\u7269', '\u65C5\u884C']);
        var C = lc2_card(sc), red = lc2_plate(sc, [sc.accent, sc.accent2, C.text], C.fill, 1.7), t0 = lc2_vt(c.text);
        var sw = port ? W * 0.84 : Math.min(W * 0.88, H * 1.7), sh = port ? Math.min(H * 0.82, sw * 1.7) : Math.min(H * 0.66, sw * 0.46), x0 = W / 2 - sw / 2, y0 = H / 2 - sh / 2;
        var OP = HD + 'var op=ioc(cl((time-0.03)/0.55))*(1-ioc(cl((PO-0.1)/0.8))),vis=Math.max(0.12,op),a=oc(cl(time/0.2))*(1-cl((PO-0.75)/0.25));';
        var VIS = port ? 'var vx0=' + jzN(x0) + ',vy0=' + jzN(y0) + ',vw=' + jzN(sw) + ',vh=' + jzN(sh) + '*vis;' : 'var vx0=' + jzN(x0 + sw) + '-' + jzN(sw) + '*vis,vy0=' + jzN(y0) + ',vw=' + jzN(sw) + '*vis,vh=' + jzN(sh) + ';';
        // slip (shadow + paper follow the unfolded part)
        var SH = lc2_S(ctx, 'omikuji paper', 0, 0), gs = lc2_G(SH, 'paper'), d = u * 0.012;
        // (rect expressions are set before the fill / stroke is added: adding a sibling invalidates `r1` / `r2` in AE)
        var s1 = lc2_sub(gs, 'shadow'), r1 = jzAddRect(s1, 10, 10, 0);
        jzSetExpr(r1.property('ADBE Vector Rect Size'), OP + VIS + '[vw,vh]'); jzSetExpr(r1.property('ADBE Vector Rect Position'), OP + VIS + '[vx0+vw/2+' + jzN(d * 0.6) + ',vy0+vh/2+' + jzN(d) + ']');
        jzAddFill(s1, lc2_shCol(sc), lc2_shOp(sc));
        var s2 = lc2_sub(gs, 'slip'), r2 = jzAddRect(s2, 10, 10, 0);
        jzSetExpr(r2.property('ADBE Vector Rect Size'), OP + VIS + '[vw,vh]'); jzSetExpr(r2.property('ADBE Vector Rect Position'), OP + VIS + '[vx0+vw/2,vy0+vh/2]');
        if (C.edge) lc2_stroke(s2, C.line, 1.2 * kx); jzAddFill(s2, C.fill);
        jzSetExpr(jzXf(SH, 'ADBE Opacity'), OP + 'value*a');
        lc2_paint(SH);
        var S = lc2_S(ctx, 'omikuji print', 0, 0), g = lc2_G(S, 'print'), m = Math.min(sw, sh) * 0.05, lw = Math.max(1.5 * kx, u * 0.002);
        lc2_box(g, 'frame', x0 + m, y0 + m, sw - m * 2, sh - m * 2, null, { stroke: red, sw: lw * 1.8 });
        lc2_box(g, 'frame 2', x0 + m * 1.35, y0 + m * 1.35, sw - m * 2.7, sh - m * 2.7, null, { stroke: red, sw: lw * 0.7 });
        var ix0 = x0 + m * 2, iy0 = y0 + m * 2, iw = sw - m * 4, ih = sh - m * 4, no = '\u7B2C' + lc2_kanjiNum((c.line || 0) + 1) + '\u756A', texts = [], labs = [], lyr, bb, vo = { vertical: true, lead: 1.35, track: 0.06 };
        if (!port) {
            var colW = ih * 0.14, hx = ix0 + iw - colW * 0.6;
            texts.push(lc2_V(ctx, no, { font: jzSerifF(ctx), size: Math.min(colW * 0.6, ih * 0.8 / jzCount(no)), color: C.text, x: hx, y: iy0 + ih * 0.06 }).L);
            var rw = colW * 1.2, rx = hx - colW * 0.5 - rw - m * 0.4;
            lc2_box(g, 'rank plate', rx, iy0 + ih * 0.04, rw, ih * 0.92, red);
            texts.push(lc2_V(ctx, rank, { font: jzSerifF(ctx), size: Math.min(rw * 0.7, ih * 0.8 / jzCount(rank)), color: lc2_onCol(sc, red), x: rx + rw / 2, y: iy0 + ih / 2, mid: true, track: 0.2 }).L);
            var px1 = rx - m, catW = iw * 0.3, pw = px1 - (ix0 + catW) - m;
            var fb = lc2_fitBlock(t0, pw, ih * 0.9, vo, 3), size = Math.min(fb.size, ih * 0.3), mm = lc2_meas(fb.text, size, vo);
            var VV = lc2_V(ctx, fb.text, { font: font, size: size, color: C.text, x: px1 - pw / 2, y: iy0 + (ih - mm.h) / 2, lead: 1.35, track: 0.06, name: t0 });
            lyr = VV.L; bb = VV.bb;
            lc2_line(g, 'divider', [[ix0 + catW + m * 0.5, iy0], [ix0 + catW + m * 0.5, iy0 + ih]], red, lw);
            for (i = 0; i < 4; i++) {
                var cx = ix0 + catW - (i % 2 + 0.5) * catW / 2, cy = iy0 + Math.floor(i / 2) * ih / 2;
                labs.push({ t: cats[i] || '', x: cx + catW * 0.12, y: cy + ls * 0.8 });
                lc2_greek(g, 'greek ' + i, cx - catW * 0.24, cy + ls * 0.8, catW * 0.28, ih / 2 - ls * 2, ls * 0.9, C.text, 35, i + 7, true);
                if (i === 0) lc2_line(g, 'cat rule', [[ix0, cy + ih / 2], [ix0 + catW, cy + ih / 2]], red, lw * 0.6);
            }
            var cr = []; for (i = 1; i < 4; i++) cr.push([[x0 + sw * i / 4, y0], [x0 + sw * i / 4, y0 + sh]]);
            lc2_lines2(g, 'creases', cr, jzMixHex(C.fill, C.text, 0.12), 1 * kx, { op: 80 });
            var LB = lc2_vlabels(ctx, labs, { font: jzSerifF(ctx), size: ls * 1.05, color: red, name: 'fortunes' });
        } else {
            var hh = ih * 0.1, rw2 = iw * 0.4, rh = hh * 1.2;
            texts.push(lc2_T(ctx, no, { font: jzSerifF(ctx), size: hh * 0.5, track: 0.3, x: W / 2, y: iy0 + hh * 0.45, color: C.text }));
            lc2_box(g, 'rank plate', W / 2 - rw2 / 2, iy0 + hh, rw2, rh, red);
            texts.push(lc2_T(ctx, rank, { font: jzSerifF(ctx), size: rh * 0.62, track: 0.3, x: W / 2, y: iy0 + hh + rh / 2, color: lc2_onCol(sc, red) }));
            var catH = ih * 0.2, pt = iy0 + hh + rh + m, pb = iy0 + ih - catH - m;
            var fb2 = lc2_fitBlock(t0, iw * 0.84, pb - pt, vo, 3), size2 = Math.min(fb2.size, iw * 0.3), mm2 = lc2_meas(fb2.text, size2, vo);
            var V2 = lc2_V(ctx, fb2.text, { font: font, size: size2, color: C.text, x: W / 2, y: pt + (pb - pt - mm2.h) / 2, lead: 1.35, track: 0.06, name: t0 });
            lyr = V2.L; bb = V2.bb;
            lc2_line(g, 'divider', [[ix0, pb + m * 0.5], [ix0 + iw, pb + m * 0.5]], red, lw);
            for (i = 0; i < 4; i++) {
                var cw2 = iw / 4, cx2 = ix0 + iw - (i + 0.5) * cw2;
                labs.push({ t: cats[i] || '', x: cx2 + cw2 * 0.22, y: pb + m });
                lc2_greek(g, 'greek ' + i, cx2 - cw2 * 0.4, pb + m, cw2 * 0.5, catH - m, ls * 0.8, C.text, 35, i + 7, true);
            }
            var cr2 = []; for (i = 1; i < 4; i++) cr2.push([[x0, y0 + sh * i / 4], [x0 + sw, y0 + sh * i / 4]]);
            lc2_lines2(g, 'creases', cr2, jzMixHex(C.fill, C.text, 0.12), 1 * kx, { op: 80 });
            var LB = lc2_vlabels(ctx, labs, { font: jzSerifF(ctx), size: ls, color: red, name: 'fortunes' });
        }
        jzSetExpr(jzXf(S, 'ADBE Opacity'), OP + 'value*a');
        lc2_paint(S);
        if (LB) texts.push(LB.L);
        var BIG = Math.max(W, H);
        function clip(L) {
            if (port) lc2_clip(L, x0 - BIG, y0 - 1, x0 + sw + BIG, y0 + 1, OP + jzN(sh) + '*vis-1');
            else lc2_clip(L, x0 + sw - 1, y0 - BIG, x0 + sw + 1, y0 + sh + BIG, OP + jzN(sw) + '*vis-1');
        }
        clip(S);
        for (i = 0; i < texts.length; i++) { clip(texts[i]); jzSetExpr(jzXf(texts[i], 'ADBE Opacity'), OP + 'value*a'); }
        clip(lyr); lc2_main(ctx, lyr, port ? 0.25 : 0.22);
        return bb;
    }
});

/* ================================================================== 27 kakejiku \u2014 \u639B\u3051\u8EF8 */
jzReg('layout', 'kakejiku', {
    plan: function (rng, cut, st) {
        return { font: rng.chance(0.5) ? 'brush' : rng.pick(jzFontsOf(st, ['serif'])), variant: lc2_port(cut) ? 'kake' : rng.pick(['kake', 'kake', 'yoko']),
            mount: rng.pick(['accent', 'ink', 'sub']), seal: rng.chance(0.7) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), kx = lc2_k(ctx), port = jzPortrait(ctx), HD = lc2_H(ctx), i;
        var font = jzP(ctx, 'font', jzSerifF(ctx)), mount = jzP(ctx, 'mount', 'accent'), hasSeal = !!jzP(ctx, 'seal', true), C = lc2_card(sc), t0 = lc2_vt(c.text), dark = jzDarkest(sc);
        var mountC = jzMixHex(lc2_plate(sc, mount === 'accent' ? [sc.accent, sc.ink] : mount === 'ink' ? [sc.ink, sc.accent] : [sc.sub, sc.ink]), dark, 0.3);
        var goldC = jzMixHex(lc2_plate(sc, [sc.accent, sc.accent2, sc.sub], mountC, 1.5), mountC, 0.25), rodC = jzMixHex(dark, sc.sub, 0.35), seal = lc2_plate(sc, [sc.accent, sc.accent2], C.fill, 1.6);
        var OP = HD + 'var op=ioc(cl((time-0.08)/0.7))*(1-ioc(cl(PO/0.85))),a=oc(cl(time/0.2))*(1-cl((PO-0.8)/0.2));', BIG = Math.max(W, H), vo, fb, size, mm, VV, S, g, bb;
        if (jzP(ctx, 'variant', 'kake') === 'yoko' && !port) {
            // horizontal scroll unrolling right -> left
            var sw = W * 0.86, sh = Math.min(H * 0.56, sw * 0.4), x1 = W / 2 + sw / 2, y0 = H / 2 - sh / 2, rodW = sh * 0.07;
            S = lc2_S(ctx, 'scroll', 0, 0); g = lc2_G(S, 'scroll');
            lc2_box(g, 'mount', x1 - sw, y0, sw, sh, mountC);
            var px0 = x1 - sw + sw * 0.08, pw = sw * 0.84, py0 = y0 + sh * 0.12, ph2 = sh * 0.76;
            lc2_box(g, 'gold', px0 - sh * 0.02, py0 - sh * 0.02, pw + sh * 0.04, ph2 + sh * 0.04, goldC);
            lc2_box(g, 'paper', px0, py0, pw, ph2, C.fill);
            vo = { vertical: true, lead: 1.4, track: 0.06 };
            fb = lc2_fitBlock(t0, pw * 0.8, ph2 * 0.86, vo, 4); size = Math.min(fb.size, ph2 * 0.4); mm = lc2_meas(fb.text, size, vo);
            if (hasSeal) lc2_gx(lc2_box(g, 'seal', px0 + pw / 2 - mm.w * 0.5 - size * 0.7, py0 + (ph2 + mm.h) / 2 - size * 0.55, size * 0.45, size * 0.45, seal), 'op', HD + '100*cl((time-0.6)*4)');
            jzSetExpr(jzXf(S, 'ADBE Opacity'), OP + 'value*a');
            lc2_paint(S);
            VV = lc2_V(ctx, fb.text, { font: font, size: size, color: C.text, x: px0 + pw / 2 + mm.w * 0.1, y: py0 + (ph2 - mm.h) / 2, lead: 1.4, track: 0.06, name: t0 });
            var EX = OP + jzN(sw) + '*op+' + jzN(rodW - 1);
            lc2_clip(S, x1 + rodW - 1, y0 - BIG, x1 + rodW + 1, y0 + sh + BIG, EX);
            lc2_clip(VV.L, x1 + rodW - 1, y0 - BIG, x1 + rodW + 1, y0 + sh + BIG, EX);
            lc2_main(ctx, VV.L, 0.25);
            var RL = lc2_S(ctx, 'rollers', 0, 0), gr = lc2_G(RL, 'rollers');
            lc2_box(gr, 'roller right', x1, y0 - rodW * 0.8, rodW, sh + rodW * 1.6, rodC, { r: rodW * 0.4 });
            var gm = lc2_box(gr, 'roller left', x1 - rodW, y0 - rodW * 1.4, rodW * 1.3, sh + rodW * 2.8, rodC, { r: rodW * 0.5 });
            lc2_gx(gm, 'pos', OP + '[-' + jzN(sw) + '*op,0]');
            jzSetExpr(jzXf(RL, 'ADBE Opacity'), OP + 'value*a');
            lc2_paint(RL);
            return VV.bb;
        }
        // hanging scroll unrolling downwards
        var sw2 = port ? W * 0.6 : Math.min(W * 0.32, H * 0.42), sh2 = H * (port ? 0.8 : 0.86), cx = W / 2, y02 = H / 2 - sh2 / 2 + H * 0.03, rodH = sw2 * 0.06;
        var CD = lc2_S(ctx, 'cord', 0, 0), gc = lc2_G(CD, 'cord');
        lc2_line(gc, 'cord', [[cx - sw2 * 0.28, y02], [cx, y02 - H * 0.06], [cx + sw2 * 0.28, y02]], sc.sub, Math.max(1.5 * kx, u * 0.0025));
        lc2_circ(gc, 'hook', cx, y02 - H * 0.06, u * 0.006, sc.fg);
        jzSetExpr(jzXf(CD, 'ADBE Opacity'), OP + 'value*a');
        lc2_paint(CD);
        S = lc2_S(ctx, 'scroll', 0, 0); g = lc2_G(S, 'scroll');
        lc2_box(g, 'mount', cx - sw2 / 2, y02, sw2, sh2, mountC);
        var pt = y02 + sh2 * 0.2, pb = y02 + sh2 * 0.84, pw2 = sw2 * 0.78;
        lc2_box(g, 'gold', cx - pw2 / 2 - sw2 * 0.02, pt - sh2 * 0.03, pw2 + sw2 * 0.04, pb - pt + sh2 * 0.06, goldC);
        lc2_box(g, 'paper', cx - pw2 / 2, pt, pw2, pb - pt, C.fill);
        vo = { vertical: true, lead: 1.35, track: 0.08 };
        fb = lc2_fitBlock(t0, pw2 * 0.82, (pb - pt) * 0.86, vo, 2); size = Math.min(fb.size, pw2 * 0.5); mm = lc2_meas(fb.text, size, vo);
        var ty = pt + ((pb - pt) - mm.h) * 0.4;
        if (hasSeal) {
            var ss = Math.min(Math.max(size * 0.42, pw2 * 0.1), pw2 * 0.16), sxx = Math.max(cx - pw2 / 2 + ss * 0.3, cx - mm.w / 2 - ss * 1.1), syy = Math.min(pb - ss * 1.3, ty + mm.h - ss * 0.6);
            var gsl = lc2_sub(g, 'seal');
            lc2_box(gsl, 'seal', sxx, syy, ss, ss, seal);
            lc2_box(gsl, 'seal inner', sxx + ss * 0.18, syy + ss * 0.18, ss * 0.64, ss * 0.64, jzMixHex(seal, C.fill, 0.35), { op: 50 });
            lc2_gx(gsl, 'op', HD + '100*cl((time-0.7)*4)');
        }
        jzSetExpr(jzXf(S, 'ADBE Opacity'), OP + 'value*a');
        lc2_paint(S);
        VV = lc2_V(ctx, fb.text, { font: font, size: size, color: C.text, x: cx, y: ty, lead: 1.35, track: 0.08, name: t0 });
        var EX2 = OP + jzN(sh2) + '*Math.max(0.03,op)';
        lc2_clip(S, cx - sw2, y02 - 2 * kx, cx + sw2, y02, EX2);
        lc2_clip(VV.L, cx - sw2, y02 - 2 * kx, cx + sw2, y02, EX2);
        lc2_main(ctx, VV.L, 0.2);
        // top rod + bottom roller (follows the unrolled edge) with knobs
        var RD = lc2_S(ctx, 'rods', 0, 0), grd = lc2_G(RD, 'rods');
        lc2_box(grd, 'top rod', cx - sw2 * 0.53, y02 - rodH * 0.5, sw2 * 1.06, rodH, rodC, { r: rodH * 0.4 });
        var gro = lc2_sub(grd, 'roller');
        lc2_box(gro, 'roller', cx - sw2 * 0.55, -rodH * 0.3, sw2 * 1.1, rodH * 1.3, rodC, { r: rodH * 0.5 });
        lc2_box(gro, 'knob', cx - sw2 * 0.62, -rodH * 0.45, sw2 * 0.08, rodH * 1.6, goldC, { r: rodH * 0.3 });
        lc2_box(gro, 'knob', cx + sw2 * 0.54, -rodH * 0.45, sw2 * 0.08, rodH * 1.6, goldC, { r: rodH * 0.3 });
        lc2_gx(gro, 'pos', OP + '[0,' + jzN(y02) + '+' + jzN(sh2) + '*Math.max(0.03,op)]');
        jzSetExpr(jzXf(RD, 'ADBE Opacity'), OP + 'value*a');
        lc2_paint(RD);
        return lc2_box4(cx - sw2 / 2, y02, cx + sw2 / 2, y02 + sh2);
    }
});

/* ================================================================== 28 shoji \u2014 \u969C\u5B50 */
// frame + kumiko lattice of one panel (one fill for all bars)
function lc2_shojiFrame(g, name, x, y, w, h, woodC, cols, rows, kx) {
    var s = lc2_sub(g, name), fw = Math.max(4 * kx, w * 0.045), bw = Math.max(2 * kx, w * 0.012), i;
    lc2_rr(s, x, y, w, fw, 0); lc2_rr(s, x, y + h - fw * 1.6, w, fw * 1.6, 0); lc2_rr(s, x, y, fw, h, 0); lc2_rr(s, x + w - fw, y, fw, h, 0);
    for (i = 1; i < cols; i++) lc2_rr(s, x + fw + (w - fw * 2) * i / cols - bw / 2, y, bw, h, 0);
    for (i = 1; i < rows; i++) lc2_rr(s, x, y + fw + (h - fw * 2.6) * i / rows - bw / 2, w, bw, 0);
    jzAddFill(s, woodC);
    return s;
}
jzReg('layout', 'shoji', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['serif', 'display'])), variant: rng.pick(['shadow', 'open', 'shadow']), rows: rng.int(4, 6), cols: rng.int(2, 3) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), kx = lc2_k(ctx), port = jzPortrait(ctx), HD = lc2_H(ctx), i;
        var font = jzP(ctx, 'font', jzSerifF(ctx)), rows = jzP(ctx, 'rows', 5), cols = jzP(ctx, 'cols', 2), C = lc2_card(sc), dark = jzDarkest(sc);
        var paperC = jzLum(sc.bg) < 0.5 ? jzMixHex(C.fill, sc.accent, 0.08) : jzMixHex(C.fill, sc.dim || sc.sub, 0.2), woodC = jzMixHex(dark, lc2_plate(sc, [sc.accent, sc.sub]), 0.35);
        var nP = port ? 2 : 4, pw = W / nP, t0 = jzTrim(c.text), fb, size, T, mm;
        var GLW = 'var glow=0.92+0.08*(0.5+0.5*Math.sin(time*1.9+SD)*Math.cos(time*0.7+SD*0.3));';
        if (jzP(ctx, 'variant', 'shadow') === 'shadow') {
            // the lyric is a silhouette behind the paper; the lattice sits in front of it
            var A = HD + 'var a=oc(cl(time/0.3))*(1-cl((PO-0.5)/0.5));';
            var PP = lc2_S(ctx, 'shoji paper', 0, 0); lc2_box(lc2_G(PP, 'paper'), 'paper', 0, 0, W, H, paperC);
            jzSetExpr(jzXf(PP, 'ADBE Opacity'), A + GLW + 'value*a*glow');
            lc2_paint(PP);
            var LT = lc2_S(ctx, 'shoji light', 0, 0); lc2_box(lc2_G(LT, 'light'), 'light', 0, 0, W, H, '#FFFFFF');
            var ramp = jzEffect(LT, 'ADBE Ramp', 'JZ Lamp');
            jzEP(ramp, 1, [W / 2, H / 2]); jzEP(ramp, 2, jzHex(jzLightest(sc))); jzEP(ramp, 3, [W / 2 + Math.sqrt(W * W + H * H) * 0.6, H / 2]); jzEP(ramp, 4, jzHex(dark)); jzEP(ramp, 5, 2);
            jzXf(LT, 'ADBE Opacity').setValue(25);
            jzSetExpr(jzXf(LT, 'ADBE Opacity'), A + 'value*a');
            lc2_paint(LT);
            fb = lc2_fitBlock(t0, W * 0.8, H * 0.5, { lead: 1.15, track: 0.04 }, port ? 4 : 2); size = Math.min(fb.size, u * 0.26);
            var shade = jzMixHex(paperC, dark, 0.86), NR = HD + 'var near=oc(cl(time/0.9));';
            var RG = lc2_S(ctx, 'silhouette rig', W / 2, H / 2);
            jzSetExpr(jzXf(RG, 'ADBE Position'), NR + '[value[0]+(1-near)*' + jzN(W * 0.03) + ',value[1]]');
            jzSetExpr(jzXf(RG, 'ADBE Scale'), NR + 'var s=lp(1.06,1,near);[value[0]*s,value[1]*s]');
            T = lc2_T(ctx, fb.text, { font: font, size: size, x: W / 2, y: H / 2, lead: 1.15, track: 0.04, color: shade, name: t0 });
            var bl = jzEffect(T, 'ADBE Gaussian Blur 2', 'JZ Behind Paper');
            jzEX(bl, 1, NR + 'lp(' + jzN(size * 0.08) + ',' + jzN(size * 0.012) + ',near)'); jzEP(bl, 3, 1);
            lc2_kid(T, RG); lc2_main(ctx, T, 0);
            lc2_alphaAnim(T, A, 'a>0.01?1:0');     // no silhouette while the paper is invisible (browser returns early)
            var LA = lc2_S(ctx, 'shoji lattice', 0, 0), gl = lc2_G(LA, 'lattice');
            for (i = 0; i < nP; i++) lc2_shojiFrame(gl, 'panel ' + (i + 1), i * pw, 0, pw, H, woodC, cols, rows, kx);
            jzSetExpr(jzXf(LA, 'ADBE Opacity'), A + 'value*a');
            lc2_paint(LA);
            mm = lc2_meas(fb.text, size, { lead: 1.15, track: 0.04 });
            return lc2_box4(W / 2 - mm.w / 2, H / 2 - mm.h / 2, W / 2 + mm.w / 2, H / 2 + mm.h / 2);
        }
        // panels slide open to reveal the lyric; they close again on exit
        fb = lc2_fitBlock(t0, W * (port ? 0.6 : 0.5), H * 0.46, { lead: 1.15, track: 0.04 }, port ? 4 : 3); size = Math.min(fb.size, u * 0.24);
        mm = lc2_meas(fb.text, size, { lead: 1.15, track: 0.04 });
        T = lc2_T(ctx, fb.text, { font: font, size: size, x: W / 2, y: H / 2, lead: 1.15, track: 0.04, color: sc.fg, name: t0 });
        lc2_main(ctx, T, 0.12);
        var gapW = Math.min(W * (port ? 0.76 : 0.9), mm.w + size * 1.4), OPN = HD + 'var op=ioc(cl((time-0.05)/0.55))*(1-ioc(PO));';
        // one side at a time: the 'right' group is only added once 'left' is complete (adding a group invalidates the other in AE)
        var SP = lc2_S(ctx, 'shoji panels', 0, 0), half = nP / 2;
        for (var sd = 0; sd < 2; sd++) {
            var gg = lc2_G(SP, sd ? 'right' : 'left');
            for (i = sd ? half : 0; i < (sd ? nP : half); i++) {
                lc2_box(gg, 'paper', i * pw, 0, pw, H, paperC);
                lc2_gx(lc2_box(gg, 'dim', i * pw, 0, pw, H, sc.bg), 'op', HD + GLW + '(1-glow)*60');
                lc2_shojiFrame(gg, 'frame', i * pw, 0, pw, H, woodC, cols, rows, kx);
            }
            lc2_gx(gg, 'pos', OPN + '[' + (sd ? '' : '-') + jzN(gapW / 2) + '*op,0]');
        }
        lc2_paint(SP);
        return lc2_box4(W / 2 - mm.w / 2, H / 2 - mm.h / 2, W / 2 + mm.w / 2, H / 2 + mm.h / 2);
    }
});

/* ================================================================== 29 clapper \u2014 \u30AB\u30C1\u30F3\u30B3 */
jzReg('layout', 'clapper', {
    plan: function (rng, cut, st) {
        return { font: rng.chance(0.5) ? 'klee' : rng.pick(jzFontsOf(st, ['display', 'body'])), tilt: rng.range(-6, 6), roll: 'A' + rng.int(1, 9), take: rng.int(1, 12) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), kx = lc2_k(ctx), ls = jzSmallSize(ctx), port = jzPortrait(ctx), HD = lc2_H(ctx), i;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), tilt = jzP(ctx, 'tilt', 0), roll = String(jzP(ctx, 'roll', 'A1')), take = jzP(ctx, 'take', 1);
        var bw = port ? W * 0.88 : Math.min(W * 0.56, H * 0.74 * 1.3), bh = bw * 0.62, slate = lc2_plate(sc, [jzDarkest(sc), sc.ink, sc.fg], sc.bg, 1.4), chalk = lc2_onCol(sc, slate);
        var CX = W / 2, CY = H / 2 + bh * 0.1, TC = 0.42;
        var E = HD + 'var e=oc(cl(time/0.4)),eo=ic(PO),a=cl(e*2)*(1-eo),sk=(time>' + TC + '&&time<' + (TC + 0.15) + ')?(hh(Math.floor(time*24)+SD)*2-1)*' + jzN(u * 0.006) + '*(1-(time-' + TC + ')/0.15):0;';
        var R = lc2_S(ctx, 'clapperboard', CX, CY), g = lc2_G(R, 'board'), x0 = -bw / 2, y0 = -bh / 2, sh = bh * 0.14;
        lc2_shadow(g, sc, x0, y0 - bh * 0.3, bw, bh * 1.3, bh * 0.03, u * 0.016);
        lc2_box(g, 'slate', x0, y0, bw, bh, slate, jzContrast(slate, sc.bg) < 1.5 ? { r: bh * 0.03, stroke: sc.fg, sw: 2 * kx } : { r: bh * 0.03 });
        function stick(gp, yy) {
            lc2_box(gp, 'base', x0, yy, bw, sh, slate);
            lc2_polys(gp, 'stripes', lc2_stripes(x0, yy, bw, sh, bw * 0.055, -35, true), chalk);
            lc2_box(gp, 'edge', x0, yy, bw, sh, null, { stroke: chalk, sw: 1.5 * kx });
        }
        var yb = y0 - sh - bh * 0.01;
        stick(lc2_sub(g, 'stick'), yb);
        var gt = lc2_sub(g, 'clap stick'); stick(gt, y0 - sh * 2 - bh * 0.02);
        lc2_gset(gt, 'anc', [x0, yb]); lc2_gset(gt, 'pos', [x0, yb]);
        lc2_gx(gt, 'rot', HD + 'time<' + TC + '?-28*oc(cl(time/0.25))*(1-iq(cl((time-0.28)/' + jzN(TC - 0.28) + '))):0');
        lc2_circ(g, 'hinge', x0 + sh * 0.5, y0 - sh * 1.05, sh * 0.22, chalk);
        var lw = Math.max(1.5 * kx, u * 0.0022), r1 = y0 + bh * 0.5, r2 = y0 + bh * 0.76;
        lc2_lines2(g, 'grid', [[[x0, r1], [x0 + bw, r1]], [[x0, r2], [x0 + bw, r2]], [[x0 + bw / 3, r1], [x0 + bw / 3, y0 + bh]], [[x0 + bw * 2 / 3, r1], [x0 + bw * 2 / 3, y0 + bh]]], chalk, lw, { op: 80 });
        jzSetExpr(jzXf(R, 'ADBE Position'), E + '[value[0]+sk,value[1]+(1-e)*' + jzN(H * 0.5) + '+eo*' + jzN(H * 0.2) + '+sk*0.6]');
        jzSetExpr(jzXf(R, 'ADBE Rotate Z'), 'value+' + jzN(tilt));
        jzSetExpr(jzXf(R, 'ADBE Opacity'), E + 'value*a');
        lc2_paint(R);
        var pad = bw * 0.025, rowH = r2 - r1, labs = [{ t: 'PROD.', x: CX + x0 + pad, y: CY + y0 + pad + ls * 0.4 }], v1 = [], v2 = [];
        var k1 = [['SCENE', jzLineNo(ctx)], ['TAKE', String(take)], ['ROLL', roll]], k2 = [['DATE', jzFmtTime(c.start)], ['DIR.', 'JIZURA'], ['CAM.', 'A']];
        for (i = 0; i < 3; i++) {
            labs.push({ t: k1[i][0], x: CX + x0 + bw * i / 3 + pad, y: CY + r1 + ls * 0.6 }); v1.push({ t: k1[i][1], x: CX + x0 + bw * i / 3 + pad, y: CY + r1 + rowH * 0.62 });
            labs.push({ t: k2[i][0], x: CX + x0 + bw * i / 3 + pad, y: CY + r2 + ls * 0.6 }); v2.push({ t: k2[i][1], x: CX + x0 + bw * i / 3 + pad + ls * 3.2, y: CY + r2 + (y0 + bh - r2) * 0.55 });
        }
        var M1 = lc2_multi(ctx, labs, { font: jzMonoF(ctx), size: ls * 0.72, track: 0.2, align: 'left', color: chalk, name: 'slate labels' });
        var M2 = lc2_multi(ctx, v1, { font: font, size: rowH * 0.46, align: 'left', color: chalk, name: 'slate values' });
        var M3 = lc2_multi(ctx, v2, { font: font, size: Math.min((y0 + bh - r2) * 0.42, ls * 1.3), align: 'left', color: chalk, name: 'slate values 2' });
        lc2_kid(M1.L, R); lc2_kid(M2.L, R); lc2_kid(M3.L, R);
        jzSetExpr(jzXf(M1.L, 'ADBE Opacity'), E + 'value*a*0.75');
        jzSetExpr(jzXf(M2.L, 'ADBE Opacity'), E + 'value*a*cl((time-0.2)*4)'); jzSetExpr(jzXf(M3.L, 'ADBE Opacity'), E + 'value*a*cl((time-0.2)*4)');
        var fb = lc2_fitBlock(jzTrim(c.text), bw - pad * 2, (r1 - y0) - ls * 1.6, { lead: 1.08 }, 2), size = Math.min(fb.size, bh * 0.3);
        var T = lc2_T(ctx, fb.text, { font: font, size: size, x: CX + x0 + pad * 1.4, y: CY + y0 + ls * 1.2 + ((r1 - y0) - ls * 1.2) / 2, align: 'left', lead: 1.08, color: chalk, name: c.text });
        lc2_kid(T, R); lc2_main(ctx, T, 0.25);
        return lc2_box4(CX - bw / 2, CY - bh / 2, CX + bw / 2, CY + bh / 2);
    }
});

/* ================================================================== 30 warningLabel \u2014 \u8B66\u544A\u30E9\u30D9\u30EB */
// warning triangle with a (flashing) exclamation mark; returns the mark group
function lc2_warnTri(g, cx, cy, s, fill, mark, kx) {
    var pts = [[cx, cy - s * 0.52], [cx + s * 0.58, cy + s * 0.46], [cx - s * 0.58, cy + s * 0.46]];
    lc2_poly(g, 'triangle', pts, fill, { stroke: fill, sw: Math.max(2 * kx, s * 0.1), cap: 2 });
    var gm = lc2_sub(g, 'mark');
    lc2_box(gm, 'bar', cx - s * 0.055, cy - s * 0.24, s * 0.11, s * 0.42, mark, { r: s * 0.05 });
    lc2_circ(gm, 'dot', cx, cy + s * 0.3, s * 0.065, mark);
    return gm;
}
jzReg('layout', 'warningLabel', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display'])), variant: rng.pick(['header', 'stripe', 'side']), word: rng.int(0, 3), tilt: rng.range(-3, 3) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), kx = lc2_k(ctx), ls = jzSmallSize(ctx), port = jzPortrait(ctx), HD = lc2_H(ctx);
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), variant = jzP(ctx, 'variant', 'header'), tilt = jzP(ctx, 'tilt', 0), C = lc2_card(sc);
        var lw0 = port ? W * 0.9 : Math.min(W * 0.78, H * 1.55), lh0 = lw0 * (port ? 0.78 : 0.5), CX = W / 2, CY = H / 2;
        var Q = HD + 'var q=ob(cl(time/0.32),1.5),eo=ic(PO),a=cl(q*3)*(1-eo),fl=time<0.9?(Math.floor(time/0.12)%2?0.25:1):1,he=oc(cl((time-0.06)/0.35));';
        var warnC = lc2_plate(sc, [sc.accent, sc.accent2, sc.ink], C.fill, 1.6), inkC = lc2_plate(sc, [jzDarkest(sc), sc.ink, sc.fg], C.fill, 3), hazard = lc2_plate(sc, [sc.accent, sc.accent2, C.fill], inkC, 1.8);
        var wp = LC2_WARN[(jzP(ctx, 'word', 0) || 0) % 4], we = wp[0], wj = wp[1], onW = lc2_onCol(sc, warnC);
        var R = lc2_S(ctx, 'warning label', CX, CY), g = lc2_G(R, 'label'), x0 = -lw0 / 2, y0 = -lh0 / 2, kids = [], stripes = null;
        lc2_shadow(g, sc, x0, y0, lw0, lh0, lh0 * 0.04, u * 0.014);
        lc2_box(g, 'label', x0, y0, lw0, lh0, C.fill, { r: lh0 * 0.04, stroke: inkC, sw: Math.max(3 * kx, u * 0.004) });
        var ax0 = x0, ay0 = y0, aw = lw0, ah = lh0, gm;
        if (variant === 'header') {
            var hh = lh0 * 0.26, gh = lc2_sub(g, 'header');
            lc2_box(gh, 'band', x0, y0, lw0, hh, warnC, { r: lh0 * 0.04 }); lc2_box(gh, 'band low', x0, y0 + hh * 0.5, lw0, hh * 0.5, warnC);
            var gtri = lc2_sub(g, 'sign'); gm = lc2_warnTri(gtri, x0 + hh * 0.75, y0 + hh * 0.52, hh * 0.72, onW, warnC, kx);
            lc2_gx(gtri, 'op', Q + '100*he');
            var ht = we + '  ' + wj, hs = Math.min(hh * 0.46, lc2_fitSize(ht, lw0 - hh * 1.8, hh, { track: 0.12 }));
            kids.push([lc2_T(ctx, ht, { font: 'gothic_black', size: hs, track: 0.12, align: 'left', x: CX + x0 + hh * 1.45, y: CY + y0 + hh * 0.52, color: onW }), 'a*he']);
            ay0 = y0 + hh; ah = lh0 - hh;
        } else if (variant === 'stripe') {
            var sb = lh0 * 0.11;
            gm = lc2_warnTri(lc2_sub(g, 'sign'), x0 + lw0 / 2, y0 + sb + lh0 * 0.16, lh0 * 0.2, warnC, onW, kx);
            kids.push([lc2_T(ctx, wj + '\u3000' + we, { font: 'gothic_black', size: lh0 * 0.06, track: 0.2, x: CX, y: CY + y0 + sb + lh0 * 0.33, color: inkC }), 'a']);
            ay0 = y0 + sb + lh0 * 0.38; ah = lh0 - sb * 2 - lh0 * 0.38;
            // hazard bands: stripes crawl sideways inside two band masks
            stripes = lc2_S(ctx, 'hazard stripes', CX, CY);
            var sw = sb * 0.7, per = sw * 2, V = sb * 1.2, bands = [[y0 + 2 * kx, 1], [y0 + lh0 - sb - 2 * kx, -1]];
            for (var bi = 0; bi < 2; bi++) {
                var by = bands[bi][0], gb = lc2_G(stripes, 'band ' + (bi + 1));
                lc2_box(gb, 'base', x0 + 2 * kx, by, lw0 - 4 * kx, sb, inkC);
                var gs = lc2_polys(gb, 'stripes', lc2_stripes(x0 - per, by, lw0 + per * 2, sb, sw, 45, false), hazard);
                lc2_gx(gs, 'pos', 'var P=' + jzN(per) + ',o=((' + bands[bi][1] + '*time*' + jzN(V) + ')%P+P)%P;[o-P,0]');
                jzMaskRect(stripes, x0 + 2 * kx, by, x0 + lw0 - 2 * kx, by + sb, 0);
            }
            jzSetExpr(jzXf(stripes, 'ADBE Opacity'), Q + 'value*a');
            lc2_paint(stripes);
        } else {
            var sw2 = lw0 * (port ? 0.3 : 0.26), gside = lc2_sub(g, 'side');
            lc2_box(gside, 'band', x0, y0, sw2, lh0, warnC, { r: lh0 * 0.04 }); lc2_box(gside, 'band right', x0 + sw2 * 0.5, y0, sw2 * 0.5, lh0, warnC);
            gm = lc2_warnTri(lc2_sub(g, 'sign'), x0 + sw2 / 2, y0 + lh0 * 0.42, sw2 * 0.68, onW, warnC, kx);
            kids.push([lc2_T(ctx, we, { font: 'gothic_black', size: sw2 * 0.14, track: 0.1, x: CX + x0 + sw2 / 2, y: CY + y0 + lh0 * 0.78, color: onW }), 'a']);
            ax0 = x0 + sw2; aw = lw0 - sw2;
        }
        lc2_gx(gm, 'op', Q + '100*fl');
        jzSetExpr(jzXf(R, 'ADBE Rotate Z'), Q + 'value+' + jzN(tilt) + '+(1-q)*4');
        jzSetExpr(jzXf(R, 'ADBE Scale'), Q + 'var s=0.85+0.15*q;[value[0]*s,value[1]*s]');
        jzSetExpr(jzXf(R, 'ADBE Opacity'), Q + 'value*a');
        lc2_paint(R);
        if (stripes) lc2_kid(stripes, R);
        if (variant !== 'stripe') kids.push([lc2_T(ctx, 'No.' + jzLineNo(ctx) + '  \u2014  ' + jzFmtTime(c.start), { font: jzMonoF(ctx), size: ls * 0.8, track: 0.2, align: 'right', x: CX + ax0 + aw - ls, y: CY + ay0 + ah - ls * 0.9, color: inkC }), 'a*0.6*K']);
        for (var i = 0; i < kids.length; i++) { lc2_kid(kids[i][0], R); jzSetExpr(jzXf(kids[i][0], 'ADBE Opacity'), Q + 'value*' + kids[i][1]); }
        var fb = lc2_fitBlock(jzTrim(c.text), aw * 0.86, ah * 0.66, { lead: 1.08, track: 0.02 }, port ? 3 : 2), size = Math.min(fb.size, u * 0.2);
        var T = lc2_T(ctx, fb.text, { font: font, size: size, x: CX + ax0 + aw / 2, y: CY + ay0 + ah / 2 - (variant === 'stripe' ? 0 : ah * 0.04), lead: 1.08, track: 0.02, color: inkC, name: c.text });
        lc2_kid(T, R); lc2_main(ctx, T, 0.12);
        return lc2_box4(CX - lw0 / 2, CY - lh0 / 2, CX + lw0 / 2, CY + lh0 / 2);
    }
});

/* ================================================================== 31 priceTag \u2014 \u5024\u672D */
function lc2_yen(v) { var s = String(Math.round(v)), o = ''; while (s.length > 3) { o = ',' + s.substr(s.length - 3) + o; s = s.substr(0, s.length - 3); } return '\u00A5' + s + o; }
jzReg('layout', 'priceTag', {
    plan: function (rng, cut, st) {
        var price = rng.pick([980, 1280, 1980, 2480, 3300, 4980, 580, 12800]);
        var font = rng.pick(jzFontsOf(st, ['display', 'body'])), variant = rng.pick(['hang', 'hang', 'shelf']), was = Math.round(price * rng.range(1.25, 1.6) / 10) * 10, col = rng.pick(['card', 'accent']);
        var sw = rng.range(10, 18); sw *= rng.pick([1, -1]);
        return { font: font, variant: variant, price: price, was: was, col: col, swing: sw };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), kx = lc2_k(ctx), ls = jzSmallSize(ctx), port = jzPortrait(ctx), HD = lc2_H(ctx), i;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), price = jzP(ctx, 'price', 1980), was = jzP(ctx, 'was', 2980), swing = jzP(ctx, 'swing', 14), C = lc2_card(sc), t0 = jzTrim(c.text);
        var tagC = jzP(ctx, 'col', 'card') === 'accent' ? lc2_plate(sc, [sc.accent, sc.ink]) : C.fill, tc = lc2_onCol(sc, tagC), red = lc2_plate(sc, [sc.accent, sc.accent2, tc], tagC, 1.8);
        var fb, size, T, R, g;
        if (jzP(ctx, 'variant', 'hang') === 'shelf') {
            // supermarket shelf label
            var bw = port ? W * 0.9 : Math.min(W * 0.8, H * 1.8), bh = bw * (port ? 0.5 : 0.34), x0 = -bw / 2, y0 = -bh / 2, CX = W / 2, CY = H / 2;
            var E = HD + 'var e=cl(time/0.45),eo=ic(PO),a=cl(e*3)*(1-eo),pa=cl(ob(cl((time-0.3)/0.35),1.70158)*K);';
            R = lc2_S(ctx, 'shelf label', CX, CY); g = lc2_G(R, 'label');
            lc2_shadow(g, sc, x0, y0, bw, bh, bh * 0.03, u * 0.012);
            lc2_box(g, 'label', x0, y0, bw, bh, tagC, jzContrast(tagC, sc.bg) < 1.4 ? { r: bh * 0.03, stroke: jzMixHex(sc.fg, tagC, 0.4), sw: 1.5 * kx } : { r: bh * 0.03 });
            var hdr = bh * 0.2; lc2_box(g, 'header', x0, y0, bw, hdr, red);
            var gbc = lc2_sub(g, 'barcode'), bx = x0 + bw * 0.04, byy = y0 + bh * 0.88, xb = bx;
            for (i = 0; i < 36 && xb < bx + bw * 0.22; i++) { var w2 = bw * (0.002 + 0.004 * jzR(price, i, 3)); lc2_rr(gbc, xb, byy - bh * 0.06, w2, bh * 0.09, 0); xb += w2 + bw * (0.002 + 0.003 * jzR(price, i, 4)); }
            jzAddFill(gbc, tc, 80);
            jzSetExpr(jzXf(R, 'ADBE Position'), E + '[value[0],value[1]+(1-ob(e,1.3))*' + jzN(H * 0.3) + '+eo*' + jzN(H * 0.1) + ']');
            jzSetExpr(jzXf(R, 'ADBE Opacity'), E + 'value*a');
            lc2_paint(R);
            var hT = lc2_T(ctx, '\u304A\u8CB7\u3044\u5F97  \u00B7  No.' + jzLineNo(ctx), { font: jzBodyF(ctx), size: hdr * 0.5, track: 0.2, align: 'left', x: CX + x0 + bw * 0.03, y: CY + y0 + hdr / 2, color: lc2_onCol(sc, red) });
            lc2_kid(hT, R); jzSetExpr(jzXf(hT, 'ADBE Opacity'), E + 'value*a');
            var nameW = bw * (port ? 0.9 : 0.62), nTop = y0 + hdr, nBot = y0 + bh * (port ? 0.62 : 0.8);
            fb = lc2_fitBlock(t0, nameW * 0.92, (nBot - nTop) * 0.86, { lead: 1.05 }, 2); size = Math.min(fb.size, bh * 0.3);
            var px = x0 + bw * 0.96, py = port ? y0 + bh * 0.76 : y0 + hdr + (bh - hdr) * 0.44, ps = Math.min((bh - hdr) * (port ? 0.26 : 0.3), size * 0.8);
            var PT = lc2_T(ctx, lc2_yen(price), { font: 'gothic_black', size: ps, align: 'right', x: CX + px, y: CY + py, color: red });
            var TX = lc2_T(ctx, '\u7A0E\u8FBC', { font: jzBodyF(ctx), size: ls * 0.9, align: 'right', x: CX + px, y: CY + py + ps * 0.62, color: tc });
            lc2_kid(PT, R); lc2_kid(TX, R);
            jzSetExpr(jzXf(PT, 'ADBE Scale'), E + '[value[0]*pa,value[1]*pa]'); jzSetExpr(jzXf(PT, 'ADBE Opacity'), E + 'value*pa'); jzSetExpr(jzXf(TX, 'ADBE Opacity'), E + 'value*pa*0.8');
            T = lc2_T(ctx, fb.text, { font: font, size: size, x: CX + x0 + bw * 0.04, y: CY + (nTop + nBot) / 2, align: 'left', lead: 1.05, color: tc, name: t0 });
            lc2_kid(T, R); lc2_main(ctx, T, 0.2);
            return lc2_box4(CX + x0, CY + y0, CX + x0 + bw, CY + y0 + bh);
        }
        // hanging swing tag
        var tw = port ? W * 0.62 : Math.min(W * 0.34, H * 0.46), th = tw * 1.45, hx = W / 2 + (port ? 0 : W * 0.04), hy = H * 0.08, cord = H * (port ? 0.12 : 0.1);
        var E2 = HD + 'var e=cl(time/0.45),eo=ic(PO),a=cl(e*3)*(1-eo),pa=cl(ob(cl((time-0.35)/0.35),1.70158)*K),SW=' + jzN(swing) + ',tt=Math.max(0,time);';
        var NL = lc2_S(ctx, 'nail', 0, 0); lc2_circ(lc2_G(NL, 'nail'), 'nail', hx, hy, u * 0.008, sc.sub);
        jzSetExpr(jzXf(NL, 'ADBE Opacity'), E2 + 'value*a'); lc2_paint(NL);
        R = lc2_S(ctx, 'swing tag', hx, hy); g = lc2_G(R, 'tag');
        lc2_line(g, 'string', [[0, 0], [-tw * 0.1, cord], [0, cord + tw * 0.12], [tw * 0.1, cord], [0, 0]], sc.sub, Math.max(1.5 * kx, u * 0.002), { cap: 2 });
        var top = cord + tw * 0.05, chn = tw * 0.22, pts = [[-tw / 2 + chn, top], [tw / 2 - chn, top], [tw / 2, top + chn], [tw / 2, top + th], [-tw / 2, top + th], [-tw / 2, top + chn]], sp = [];
        for (i = 0; i < pts.length; i++) sp.push([pts[i][0] + u * 0.008, pts[i][1] + u * 0.012]);
        lc2_poly(g, 'shadow', sp, lc2_shCol(sc), { op: lc2_shOp(sc) });
        lc2_poly(g, 'tag', pts, tagC, jzContrast(tagC, sc.bg) < 1.4 ? { stroke: jzMixHex(sc.fg, tagC, 0.4), sw: 1.5 * kx } : null);
        lc2_circ(g, 'eyelet', 0, top + tw * 0.13, tw * 0.045, sc.bg, { stroke: jzMixHex(tagC, tc, 0.3), sw: Math.max(1.5 * kx, tw * 0.012) });
        var m = tw * 0.1, ws = tw * 0.1, wasS = lc2_yen(was), wm = lc2_meas(wasS, ws, { mono: true }).w;
        lc2_line(g, 'rule', [[-tw / 2 + m, top + th * 0.62], [tw / 2 - m, top + th * 0.62]], tc, Math.max(1 * kx, u * 0.0015), { op: 50 });
        var gk = lc2_line(g, 'strike', [[-wm / 2 - ws * 0.2, top + th * 0.71], [wm / 2 + ws * 0.2, top + th * 0.71]], red, Math.max(2 * kx, ws * 0.12));
        jzAddTrimPaths(gk, E2 + '100*pa');
        jzSetExpr(jzXf(R, 'ADBE Position'), E2 + '[value[0],value[1]-(1-ob(e,1.4))*' + jzN(H * 0.4) + ']');
        jzSetExpr(jzXf(R, 'ADBE Rotate Z'), E2 + 'value+SW*Math.exp(-tt*2.2)*Math.cos(tt*5.2)*(1-eo)+Math.sin(time*1.1)*1.2+eo*SW*1.5');
        jzSetExpr(jzXf(R, 'ADBE Opacity'), E2 + 'value*a');
        lc2_paint(R);
        var WT = lc2_T(ctx, wasS, { font: jzMonoF(ctx), size: ws, x: hx, y: hy + top + th * 0.71, color: tc });
        var NT = lc2_T(ctx, lc2_yen(price), { font: 'gothic_black', size: tw * 0.17, x: hx, y: hy + top + th * 0.86, color: red });
        lc2_kid(WT, R); lc2_kid(NT, R);
        jzSetExpr(jzXf(WT, 'ADBE Opacity'), E2 + 'value*pa*0.6');
        jzSetExpr(jzXf(NT, 'ADBE Scale'), E2 + '[value[0]*pa,value[1]*pa]'); jzSetExpr(jzXf(NT, 'ADBE Opacity'), E2 + 'value*pa');
        fb = lc2_fitBlock(t0, tw - m * 2, th * 0.4, { lead: 1.08 }, 3); size = Math.min(fb.size, tw * 0.3);
        T = lc2_T(ctx, fb.text, { font: font, size: size, x: hx, y: hy + top + th * 0.36, lead: 1.08, color: tc, name: t0 });
        lc2_kid(T, R); lc2_main(ctx, T, 0.2, { noHold: true, treat: false });
        return lc2_box4(hx - tw / 2, hy + cord, hx + tw / 2, hy + cord + th);
    }
});

/* ================================================================== 32 nameTag \u2014 \u540D\u672D */
jzReg('layout', 'nameTag', {
    plan: function (rng, cut, st) {
        return { font: rng.chance(0.6) ? 'klee' : rng.pick(jzFontsOf(st, ['display', 'body'])), variant: rng.pick(['hello', 'hello', 'school']), tilt: rng.range(-6, 6), grade: rng.int(1, 6), cls: rng.int(1, 4) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), kx = lc2_k(ctx), port = jzPortrait(ctx), HD = lc2_H(ctx), i;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), hello = jzP(ctx, 'variant', 'hello') !== 'school', tilt = jzP(ctx, 'tilt', 0), C = lc2_card(sc), t0 = jzTrim(c.text);
        var bandC = lc2_plate(sc, [sc.accent, sc.accent2, sc.ink], C.fill, 1.6), bw = port ? W * 0.86 : Math.min(W * 0.62, H * 1.2), bh = bw * (hello ? 0.66 : 0.56);
        var E = HD + 'var e=cl(time/0.5),eo=ic(PO),a=cl(e*3)*(1-cl((PO-0.5)/0.5)),q=ob(e,1.6);', CX = W / 2, CY = H / 2;
        var R = lc2_S(ctx, 'name tag', CX, CY), g = lc2_G(R, 'tag'), x0 = -bw / 2, y0 = -bh / 2, r = bh * 0.08, kids = [], fb, size, ty, rot = 0;
        lc2_shadow(g, sc, x0, y0, bw, bh, r, u * 0.014);
        if (hello) {
            var hh = bh * 0.3, hc = lc2_onCol(sc, bandC);
            lc2_box(g, 'badge', x0, y0, bw, bh, bandC, { r: r });
            lc2_box(g, 'name field', x0 + bw * 0.035, y0 + hh, bw * 0.93, bh * 0.58, C.fill);
            kids.push(lc2_T(ctx, 'HELLO', { font: 'gothic_black', size: hh * 0.52, track: 0.08, x: CX, y: CY + y0 + hh * 0.4, color: hc }));
            kids.push(lc2_T(ctx, 'my name is', { font: jzBodyF(ctx), size: hh * 0.2, track: 0.15, x: CX, y: CY + y0 + hh * 0.82, color: hc }));
            fb = lc2_fitBlock(t0, bw * 0.84, bh * 0.46, { lead: 1.05 }, 2); size = Math.min(fb.size, bh * 0.36); ty = y0 + hh + bh * 0.29; rot = -2;
        } else {
            lc2_box(g, 'badge', x0, y0, bw, bh, C.fill, { r: r, stroke: bandC, sw: Math.max(4 * kx, bw * 0.014) });
            var pinY = y0 - bh * 0.06, fy = y0 + bh * 0.2;
            lc2_line(g, 'pin', [[x0 + bw * 0.2, pinY], [x0 + bw * 0.8, pinY]], sc.sub, Math.max(3 * kx, u * 0.004), { cap: 2 });
            lc2_circ(g, 'pin head', x0 + bw * 0.8, pinY, u * 0.008, sc.sub);
            lc2_line(g, 'field rule', [[x0 + bw * 0.06, fy + bh * 0.09], [x0 + bw * 0.94, fy + bh * 0.09]], bandC, Math.max(2 * kx, u * 0.003));
            lc2_circ(g, 'mark', x0 + bw * 0.88, y0 + bh * 0.2, bh * 0.08, bandC);
            kids.push(lc2_T(ctx, jzP(ctx, 'grade', 1) + ' \u306D\u3093\u3000' + jzP(ctx, 'cls', 1) + ' \u304F\u307F', { font: font, size: bh * 0.1, align: 'left', x: CX + x0 + bw * 0.08, y: CY + fy, color: C.text }));
            kids.push(lc2_T(ctx, '\u306A\u307E\u3048', { font: jzBodyF(ctx), size: bh * 0.07, align: 'left', x: CX + x0 + bw * 0.08, y: CY + fy + bh * 0.19, color: bandC }));
            fb = lc2_fitBlock(t0, bw * 0.84, bh * 0.44, { lead: 1.05 }, 2); size = Math.min(fb.size, bh * 0.34); ty = y0 + bh * 0.66;
        }
        jzSetExpr(jzXf(R, 'ADBE Position'), E + '[value[0]+eo*' + jzN(W * 0.1) + ',value[1]+(1-q)*' + jzN(-H * 0.35) + '+eo*' + jzN(H * 0.5) + ']');
        jzSetExpr(jzXf(R, 'ADBE Rotate Z'), E + 'value+' + jzN(tilt) + '*q+(1-q)*-25+eo*18');
        jzSetExpr(jzXf(R, 'ADBE Opacity'), E + 'value*a');
        lc2_paint(R);
        for (i = 0; i < kids.length; i++) { lc2_kid(kids[i], R); jzSetExpr(jzXf(kids[i], 'ADBE Opacity'), E + 'value*a'); }
        var T = lc2_T(ctx, fb.text, { font: font, size: size, x: CX, y: CY + ty, lead: 1.05, color: C.text, rot: rot, name: t0 });
        lc2_kid(T, R); lc2_main(ctx, T, 0.3);
        return lc2_box4(CX - bw / 2, CY - bh / 2, CX + bw / 2, CY + bh / 2);
    }
});

/* ================================================================== 33 stickyNotes \u2014 \u4ED8\u7B8B */
jzReg('layout', 'stickyNotes', {
    plan: function (rng, cut, st) {
        var n = cut.n, k = n <= 4 ? 1 : n <= 9 ? 2 : n <= 14 ? 3 : 4, i;
        var font = rng.chance(0.6) ? 'klee' : rng.pick(jzFontsOf(st, ['display', 'body'])), chunks = lc2_splitK(cut.text, k, k), layout = rng.pick(['scatter', 'cascade']), rots = [], offs = [], cols = [];
        for (i = 0; i < 4; i++) rots.push(rng.range(-7, 7));
        for (i = 0; i < 4; i++) offs.push(rng.range(-1, 1));
        for (i = 0; i < 4; i++) cols.push(rng.int(0, 3));
        return { font: font, chunks: chunks, layout: layout, rots: rots, offs: offs, cols: cols, pin: rng.pick(['glue', 'tape']) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), kx = lc2_k(ctx), port = jzPortrait(ctx), HD = lc2_H(ctx), i;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), rots = jzP(ctx, 'rots', [-3, 4, -2, 5]), offs = jzP(ctx, 'offs', [0, 0.5, -0.5, 0.2]), pcols = jzP(ctx, 'cols', [0, 1, 2, 3]);
        var glue = jzP(ctx, 'pin', 'glue') === 'glue', C = lc2_card(sc), dark = jzDarkest(sc), c0 = jzP(ctx, 'chunks', null), chunks = [];
        if (c0 && c0.length) { for (i = 0; i < c0.length; i++) if (c0[i]) chunks.push(jzTrim(String(c0[i]))); }
        if (!chunks.length) chunks = [jzTrim(c.text)];
        var k = chunks.length, acc = lc2_plate(sc, [sc.accent, sc.ink]);
        var col2 = sc.accent2 && jzLum(sc.accent2) > 0.3 && jzContrast(sc.accent2, sc.bg) > 1.3 ? sc.accent2 : jzMixHex(C.fill, acc, 0.2), pal = [acc, jzMixHex(C.fill, acc, 0.45), col2, C.fill];
        var ns, pos = [];
        if (k === 1) { ns = Math.min(W, H) * 0.62; pos = [[W / 2, H / 2]]; }
        else if (jzP(ctx, 'layout', 'scatter') === 'cascade' || port) {
            var dx = port ? 0.14 : 0.88, dy = port ? 0.86 : 0.3;
            ns = Math.min(W * 0.86 / (1 + (k - 1) * dx), H * 0.84 / (1 + (k - 1) * dy), Math.min(W, H) * 0.62);
            for (i = 0; i < k; i++) pos.push([W / 2 + (i - (k - 1) / 2) * ns * dx, H / 2 + (i - (k - 1) / 2) * ns * dy]);
        } else {
            ns = Math.min(W * 0.84 / (k * 1.02), H * 0.62);
            for (i = 0; i < k; i++) pos.push([W / 2 + (i - (k - 1) / 2) * ns * 1.02, H / 2 + (offs[i % 4] || 0) * H * 0.06]);
        }
        var bb = null, hs = ns / 2;
        for (i = 0; i < k; i++) {
            var x = pos[i][0], y = pos[i][1], t1 = 0.03 + i * 0.13, col = pal[(pcols[i % 4] || 0) % pal.length], tc = lc2_onCol(sc, col);
            var E = HD + 'var e=cl((time-' + jzN(t1) + ')/0.22),sq=lp(1.18,1,oc(e)),pl=ic(cl(PO*1.3-' + jzN(i * 0.1) + ')),a=cl(e*3)*(1-cl((pl-0.6)/0.4));';
            var R = lc2_S(ctx, 'note ' + (i + 1), x, y - hs), g = lc2_G(R, 'note');
            // curled-corner shadow, note with a folded corner, glue strip / tape
            var sh = [[-hs + ns * 0.02, ns * 0.04], [hs + ns * 0.025, ns * 0.04], [hs + ns * 0.02, ns - ns * 0.02]], qa = [hs + ns * 0.02, ns - ns * 0.02], qc = [hs - ns * 0.1, ns + ns * 0.05], qb = [-hs + ns * 0.04, ns + ns * 0.03];
            for (var j = 1; j <= 8; j++) { var t = j / 8; sh.push([(1 - t) * (1 - t) * qa[0] + 2 * (1 - t) * t * qc[0] + t * t * qb[0], (1 - t) * (1 - t) * qa[1] + 2 * (1 - t) * t * qc[1] + t * t * qb[1]]); }
            lc2_poly(g, 'curl shadow', sh, lc2_shCol(sc), { op: jzLum(sc.bg) > 0.5 ? 22 : 55 });
            var np = [[-hs, 0], [hs, 0], [hs, ns - ns * 0.06], [hs - ns * 0.1, ns], [-hs, ns]];
            lc2_poly(g, 'note', np, col, jzContrast(col, sc.bg) < 1.3 ? { stroke: jzMixHex(sc.fg, col, 0.4), sw: 1.2 * kx } : null);
            lc2_poly(g, 'fold', [[hs, ns - ns * 0.06], [hs - ns * 0.1, ns], [hs - ns * 0.085, ns - ns * 0.075]], jzMixHex(col, dark, 0.25));
            if (glue) lc2_box(g, 'glue', -hs, 0, ns, ns * 0.1, jzMixHex(col, dark, 0.08));
            else lc2_box(g, 'tape', -ns * 0.18, -ns * 0.05, ns * 0.36, ns * 0.11, jzLightest(sc), { op: 55 });
            jzSetExpr(jzXf(R, 'ADBE Position'), E + '[value[0],value[1]+pl*' + jzN(H * 0.3) + ']');
            jzSetExpr(jzXf(R, 'ADBE Rotate Z'), E + 'value+' + jzN(rots[i % 4] || 0) + '+pl*25*' + (i % 2 ? 1 : -1));
            jzSetExpr(jzXf(R, 'ADBE Scale'), E + '[value[0]*sq,value[1]*sq*(1-pl*0.3)]');
            jzSetExpr(jzXf(R, 'ADBE Opacity'), E + 'value*a');
            lc2_paint(R);
            var fb = lc2_fitBlock(chunks[i], ns * 0.8, ns * 0.64, { lead: 1.12 }, 3);
            var T = lc2_T(ctx, fb.text, { font: font, size: Math.min(fb.size, ns * 0.36), x: x, y: y + ns * 0.04, lead: 1.12, color: tc, rot: -1.5, name: chunks[i] });
            lc2_kid(T, R); lc2_main(ctx, T, t1 + 0.08);
            bb = jzUnion(bb, lc2_box4(x - hs, y - hs, x + hs, y + hs));
        }
        return bb;
    }
});

/* ================================================================== 34 karuta \u2014 \u304B\u308B\u305F\u672D */
jzReg('layout', 'karuta', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['serif', 'display'])), variant: lc2_port(cut) ? rng.pick(['single', 'pair']) : rng.pick(['pair', 'pair', 'single']),
            from: rng.pick([1, -1]), tilt: rng.range(-4, 4), art: rng.pick(['sun', 'wave', 'mount']) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), kx = lc2_k(ctx), port = jzPortrait(ctx), HD = lc2_H(ctx), i;
        var font = jzP(ctx, 'font', jzSerifF(ctx)), pair = jzP(ctx, 'variant', 'pair') === 'pair', from = jzP(ctx, 'from', 1), tilt = jzP(ctx, 'tilt', 0), art = jzP(ctx, 'art', 'sun');
        var C = lc2_card(sc), t0 = lc2_vt(c.text), first = jzChars(t0)[0] || '', fc = [sc.accent, sc.accent2, sc.ink, C.text], frameC = C.text;
        for (i = 0; i < fc.length; i++) if (fc[i] && jzContrast(fc[i], C.fill) >= 1.3 && jzContrast(fc[i], sc.bg) >= 1.3) { frameC = fc[i]; break; }
        var red = lc2_plate(sc, [sc.accent, sc.accent2, C.text], C.fill, 1.7);
        var ch = port ? Math.min(H * (pair ? 0.42 : 0.72), W * (pair ? 0.56 : 0.8) / 0.72) : Math.min(H * 0.8, pair ? W * 0.9 / 2.2 / 0.72 : H), cw = ch * 0.72;
        var hyp = Math.sqrt(cw * cw + ch * ch);
        // a card that slaps down (slides in, settles with a wobble, impact lines); returns its rig
        function card(idx, cx, cy) {
            var t1 = idx * 0.12, SL = HD + 'var T1=' + jzN(t1) + ',e=cl((time-T1)/0.28),q=oc(e),bp=e>=1?Math.exp(-(time-T1-0.28)*14)*Math.sin((time-T1-0.28)*40)*0.02:0,eo=ic(PO),a=cl(e*3)*(1-eo);';
            var IM = lc2_S(ctx, 'impact ' + (idx + 1), cx, cy), gi = lc2_G(IM, 'impact'), ls0 = [];
            for (var j = 0; j < 10; j++) {
                var ang = j / 10 * Math.PI * 2 + 0.3, r0 = hyp * 0.55, r1 = r0 + u * 0.05, fx = Math.cos(ang) * (cw / hyp) * 1.4, fy = Math.sin(ang) * (ch / hyp) * 1.4;
                ls0.push([[fx * r0, fy * r0], [fx * r1, fy * r1]]);
            }
            var gl = lc2_lines2(gi, 'lines', ls0, sc.sub, Math.max(2 * kx, u * 0.003));
            jzAddTrimPaths(gl, SL + 'var k=(time-T1-0.26)/0.25;100*(1-cl(k))');
            lc2_gx(gi, 'sc', SL + 'var k=cl((time-T1-0.26)/0.25),s=100*(0.55+k*0.1)/0.55;[s,s]');
            jzSetExpr(jzXf(IM, 'ADBE Opacity'), SL + 'var k=(time-T1-0.26)/0.25;(k>0&&k<1)?100*(1-k):0');
            lc2_paint(IM);
            var R = lc2_S(ctx, 'karuta ' + (idx + 1), cx, cy), g = lc2_G(R, 'card');
            lc2_shadow(g, sc, -cw / 2, -ch / 2, cw, ch, cw * 0.05, u * 0.014);
            lc2_box(g, 'frame', -cw / 2, -ch / 2, cw, ch, frameC, { r: cw * 0.05 });
            lc2_box(g, 'face', -cw / 2 + cw * 0.06, -ch / 2 + cw * 0.06, cw * 0.88, ch - cw * 0.12, C.fill, { r: cw * 0.03 });
            jzSetExpr(jzXf(R, 'ADBE Position'), SL + '[value[0]+(1-q)*' + jzN(W * 0.7 * from) + ',value[1]+eo*' + jzN(H * 0.15) + ']');
            jzSetExpr(jzXf(R, 'ADBE Rotate Z'), SL + 'value+(1-q)*' + jzN(35 * from) + '+' + jzN(tilt * (idx ? -0.6 : 1)));
            jzSetExpr(jzXf(R, 'ADBE Scale'), SL + '[value[0]*(1+bp),value[1]*(1-bp)]');
            jzSetExpr(jzXf(R, 'ADBE Opacity'), SL + 'value*a');
            return { R: R, g: g, SL: SL, t1: t1, cx: cx, cy: cy };
        }
        function stat(K2, L) { lc2_kid(L, K2.R); jzSetExpr(jzXf(L, 'ADBE Opacity'), K2.SL + 'value*a'); }
        function ring(K2) {
            var Rr = cw * 0.2, ccx = cw * 0.2, ccy = -ch / 2 + cw * 0.3;
            lc2_circ(K2.g, 'ring', ccx, ccy, Rr, C.fill, { stroke: red, sw: Math.max(3 * kx, cw * 0.02) });
            return lc2_T(ctx, first, { font: font, size: Rr * 1.3, x: K2.cx + ccx, y: K2.cy + ccy, color: red });
        }
        function verse(K2, big) {
            var iw = cw * 0.72, ih = big ? ch - cw * 0.72 : ch * 0.8, vo = { vertical: true, lead: 1.3, track: 0.05 };
            var fb = lc2_fitBlock(t0, iw, ih, vo, 3), size = Math.min(fb.size, cw * 0.3), mm = lc2_meas(fb.text, size, vo);
            var y = big ? -ch / 2 + cw * 0.58 + (ih - mm.h) * 0.3 : -mm.h / 2;
            var VV = lc2_V(ctx, fb.text, { font: font, size: size, color: C.text, x: K2.cx, y: K2.cy + y, lead: 1.3, track: 0.05, name: t0 });
            lc2_kid(VV.L, K2.R); lc2_main(ctx, VV.L, K2.t1 + 0.2);
        }
        if (pair) {
            var gap = cw * 0.18, ax = port ? W / 2 : W / 2 + (cw + gap) / 2, ay = port ? H / 2 - (ch + gap) / 2 : H / 2, bx = port ? W / 2 : W / 2 - (cw + gap) / 2, by = port ? H / 2 + (ch + gap) / 2 : H / 2;
            // \u8AAD\u307F\u672D (the verse) + \u53D6\u308A\u672D (the first glyph + a picture)
            var A = card(0, ax, ay);
            lc2_paint(A.R);
            stat(A, lc2_T(ctx, '\u8AAD', { font: jzSerifF(ctx), size: cw * 0.1, x: ax + cw * 0.34, y: ay - ch / 2 + cw * 0.16, color: red }));
            verse(A, false);
            var B = card(1, bx, by), aY = ch * 0.12, aw = cw * 0.66, artC = lc2_plate(sc, [sc.accent2, sc.accent, sc.sub], C.fill, 1.3), ga = lc2_sub(B.g, 'picture');
            if (art === 'sun') { lc2_circ(ga, 'sun', 0, aY, aw * 0.26, artC, { op: 90 }); lc2_box(ga, 'horizon', -aw / 2, aY + aw * 0.2, aw, aw * 0.05, C.text, { op: 60 }); }
            else if (art === 'wave') {
                var wl = [];
                for (var j2 = 0; j2 < 3; j2++) { var pts = []; for (i = 0; i <= 20; i++) pts.push([-aw / 2 + aw * i / 20, aY + j2 * aw * 0.14 + Math.sin(i / 20 * Math.PI * 3) * aw * 0.05]); wl.push(pts); }
                lc2_lines2(ga, 'waves', wl, artC, Math.max(3 * kx, aw * 0.03), { op: 90, cap: 2 });
            } else lc2_poly(ga, 'mountain', [[-aw / 2, aY + aw * 0.3], [-aw * 0.1, aY - aw * 0.25], [aw * 0.1, aY + aw * 0.02], [aw * 0.25, aY - aw * 0.12], [aw / 2, aY + aw * 0.3]], artC, { op: 90 });
            var fl = ring(B);
            lc2_paint(B.R);
            stat(B, fl);
            return lc2_box4(ax - cw / 2, ay - ch / 2, ax + cw / 2, ay + ch / 2);
        }
        var S = card(0, W / 2, H / 2), fl2 = ring(S);
        lc2_paint(S.R);
        stat(S, fl2);
        verse(S, true);
        return lc2_box4(W / 2 - cw / 2, H / 2 - ch / 2, W / 2 + cw / 2, H / 2 + ch / 2);
    }
});

// ================================================================ pack layoutsD part 1 (AE port of src/11p_layoutsD.js, entries 1-17)
// cube, cylinder, flipCards, accordion, flag, ribbon, pendulum, pile, blocks, balloons, magnets, tiles, bulbs, ledScroll,
// billboard, crowdBubbles, crossword
//
// Techniques used here (no 3D layers \u2014 the faux 3D is done with 2D affine maps, as in the browser):
// - an affine map M = [a, b, c, d, e, f] (x' = a*x + c*y + e, y' = b*x + d*y + f) is split by aff() in the expressions into
//   Rotation * Skew * Scale: shape groups take it as their group transform, a text layer through a Transform effect.
// - deforming quads (cloth strips, ribbon segments) are pairs of triangles: a 100 px unit triangle mapped by its group
//   transform. All triangles of one surface live in sub-groups of ONE group with ONE fill, so they fill as a union (no seams).
// - "glyph layers": one glyph per line, anchor = position (layer space == comp space); per-glyph animators move / turn /
//   scale / hide each glyph, so many freely placed glyphs stay ONE editable text layer.
// - moving objects that carry a lyric glyph are separate shape layers and the text is parented to them, so the cut's
//   enter / hold / exit expressions on the text layer stay untouched.

var LD1_CY = 0.38;     // glyph centre above the baseline (em)
// expression helpers appended after jzTH(ctx): in-out cubic, affine split, colour mix
var LD1_FX = 'function iocu(x){x=cl(x);return x<0.5?4*x*x*x:1-Math.pow(-2*x+2,3)/2;}' +
    'function aff(a,b,c,d){var sx=Math.sqrt(a*a+b*b);if(sx<1e-6)sx=1e-6;var r=Math.atan2(b,a),kk=(a*c+b*d)/sx,sy=(a*d-b*c)/sx,t=Math.abs(sy)<1e-6?0:kk/sy;t=Math.max(-11,Math.min(11,t));return [r*180/Math.PI,-Math.atan(t)*180/Math.PI,sx*100,sy*100];}' +
    'function mixc(a,b,t){return [a[0]+(b[0]-a[0])*t,a[1]+(b[1]-a[1])*t,a[2]+(b[2]-a[2])*t,1];}\n';

/* ---------------------------------------------------------------- small helpers */
function ld1_H(ctx) { return jzTH(ctx) + LD1_FX; }
function ld1_mot(ctx) { return ctx.fx && ctx.fx.motion != null ? ctx.fx.motion : 0.7; }
function ld1_col(hex) { var c = jzHex(hex); return '[' + jzN(c[0]) + ',' + jzN(c[1]) + ',' + jzN(c[2]) + ',1]'; }
function ld1_box(x0, y0, x1, y1) { return { x0: x0, y0: y0, x1: x1, y1: y1, cx: (x0 + x1) / 2, cy: (y0 + y1) / 2 }; }
function ld1_stag(ctx) { return Math.max(0.005, ctx.cut.stagger || 0.04); }
function ld1_mi(ctx, t) { return Math.floor(Math.max(0, t) / ld1_stag(ctx) + 1e-6); }      // browser miAt
function ld1_plateHold(ctx) { return jzIndexOf(['still', 'jitter', 'breathe', 'glitchtick'], ctx.cut.hold || 'still') < 0; }
function ld1_light(sc) { return jzLum(sc.fg) > jzLum(sc.bg) ? sc.fg : sc.bg; }
function ld1_dark(sc) { return jzLum(sc.fg) > jzLum(sc.bg) ? sc.bg : sc.fg; }
function ld1_onCol(sc, fill) {
    var c = [sc.bg, sc.fg, sc.ink, sc.accent, sc.sub], best = null, bv = 0;
    for (var i = 0; i < c.length; i++) { if (!c[i] || String(c[i]).toUpperCase() === String(fill).toUpperCase()) continue; var k = jzContrast(c[i], fill); if (k > bv) { bv = k; best = c[i]; } }
    return bv >= 2.4 ? best : (jzLum(fill) > 0.5 ? '#111111' : '#FFFFFF');
}
function ld1_plateCol(sc, pref) { for (var i = 0; i < pref.length; i++) if (pref[i] && jzContrast(pref[i], sc.bg) >= 1.6) return pref[i]; return sc.fg; }
function ld1_shade(sc, c, s) { return s >= 0 ? jzMixHex(c, ld1_light(sc), Math.min(0.9, s)) : jzMixHex(c, ld1_dark(sc), Math.min(0.9, -s)); }
function ld1_objCols(sc) {
    var src = [sc.accent, sc.fg, sc.accent2, sc.ink, sc.sub], out = [];
    for (var i = 0; i < src.length; i++) if (src[i] && jzContrast(src[i], sc.bg) >= 1.7 && jzIndexOf(out, src[i]) < 0) out.push(src[i]);
    return out.length ? out : [sc.fg];
}
function ld1_alt(ctx) { var c = ctx.cut; if (c.lineText && jzStrip(c.lineText) !== jzStrip(c.text)) return jzFlat(c.lineText); return jzRomajiOf(ctx) || 'No.' + jzLineNo(ctx); }
function ld1_arr(a) { return jzArrExpr(a); }
function ld1_strArr(a) { var o = []; for (var i = 0; i < a.length; i++) o.push('"' + String(a[i]).replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"'); return '[' + o.join(',') + ']'; }
function ld1_sub(g, name) { var q = jzVecs(g).addProperty('ADBE Vector Group'); if (name) q.name = name; return q; }
// re-fetch a named top-level group of a shape layer (a sibling added to Contents invalidates older group references in AE)
function ld1_rg(S, name) { return S.property('ADBE Root Vectors Group').property(name); }
function ld1_gOp(g, ex) { jzSetExpr(jzGX(g).property('ADBE Vector Group Opacity'), ex); }
function ld1_gPos(g, ex) { jzSetExpr(jzGX(g).property('ADBE Vector Position'), ex); }
function ld1_gSc(g, ex) { jzSetExpr(jzGX(g).property('ADBE Vector Scale'), ex); }
function ld1_gRot(g, ex) { jzSetExpr(jzGX(g).property('ADBE Vector Rotation'), ex); }
function ld1_rr(g, w, h, r, x, y) { return jzAddRect(g, Math.max(0.5, w), Math.max(0.5, h), Math.max(0, Math.min(r, w / 2, h / 2)), x || 0, y || 0); }
// parent without converting the child's values: every parent here has anchor = position (layer space == comp space)
function ld1_parent(L, P) { try { L.setParentWithJump(P); } catch (e) { try { L.parent = P; } catch (e2) { jzWarn('parent: ' + e2.toString()); } } }
function ld1_behind(list, L) { for (var i = 0; i < list.length; i++) list[i].moveAfter(L); }
// dashed stroke (AE starts with an empty Dashes group; the preview model has fixed children).
// Every addProperty invalidates the dash properties added before it, so add them all first, then fetch them by matchName.
function ld1_dash(st, d, gp, offExpr) {
    var D = st.property('ADBE Vector Stroke Dashes'), p1 = null, p2 = null, p3 = null;
    try { D.addProperty('ADBE Vector Stroke Dash 1'); D.addProperty('ADBE Vector Stroke Gap 1'); if (offExpr) D.addProperty('ADBE Vector Stroke Offset'); } catch (e) {}
    try { p1 = D.property('ADBE Vector Stroke Dash 1'); p2 = D.property('ADBE Vector Stroke Gap 1'); if (offExpr) p3 = D.property('ADBE Vector Stroke Offset'); } catch (e1) { jzWarn('dashes: ' + e1.toString()); }
    if (p1) p1.setValue(d); if (p2) p2.setValue(gp);
    if (p3 && offExpr) jzSetExpr(p3, offExpr);
}
function ld1_cap(st, c) { try { st.property('ADBE Vector Stroke Line Cap').setValue(c); } catch (e) {} }

// ---- affine maps: body is expression code that defines M = [a, b, c, d, e, f] (group-local px -> comp px)
function ld1_affGrp(g, head, body, opExpr) {
    var tg = jzGX(g), pre = head + body + ';var AF=aff(M[0],M[1],M[2],M[3]);';
    jzSetExpr(tg.property('ADBE Vector Position'), head + body + ';[M[4],M[5]]');
    jzSetExpr(tg.property('ADBE Vector Rotation'), pre + 'AF[0]');
    jzSetExpr(tg.property('ADBE Vector Skew'), pre + 'AF[1]');
    jzSetExpr(tg.property('ADBE Vector Scale'), pre + '[AF[2],AF[3]]');
    if (opExpr) jzSetExpr(tg.property('ADBE Vector Group Opacity'), head + body + ';' + opExpr);
}
// triangle sub-group: body defines T = [x0, y0, x1, y1, x2, y2] in comp px (the parent group has an identity transform)
function ld1_tri(parent, name, head, body) {
    var g = ld1_sub(parent, name);
    jzAddPath(g, [[0, 0], [100, 0], [0, 100]], true);
    ld1_affGrp(g, head, body + ';var M=[(T[2]-T[0])/100,(T[3]-T[1])/100,(T[4]-T[0])/100,(T[5]-T[1])/100,T[0],T[1]]');
    return g;
}
// quad A B C D (in order round the outline) as two triangles; body defines Q = [ax, ay, bx, by, cx, cy, dx, dy]
function ld1_quad(parent, name, head, body) {
    ld1_tri(parent, name + ' a', head, body + ';var T=[Q[0],Q[1],Q[2],Q[3],Q[6],Q[7]]');
    ld1_tri(parent, name + ' b', head, body + ';var T=[Q[4],Q[5],Q[6],Q[7],Q[2],Q[3]]');
}
// text layer mapped by M (content relative to the layer's anchor); P = the layer's own (rest) position in comp px
function ld1_fxAff(L, head, body, opExpr, P) {
    var A = jzXf(L, 'ADBE Anchor Point').value, e = jzEffect(L, 'ADBE Geometry2', 'JZ Face');
    if (!e) return null;
    var pre = head + body + ';var AF=aff(M[0],M[1],M[2],M[3]);';
    jzEP(e, 1, [A[0], A[1]]);
    jzEX(e, 2, head + body + ';[' + jzN(A[0] - P[0]) + '+M[4],' + jzN(A[1] - P[1]) + '+M[5]]');
    jzEP(e, 3, 0);
    jzEX(e, 4, pre + 'AF[3]'); jzEX(e, 5, pre + 'AF[2]'); jzEX(e, 6, pre + 'AF[1]'); jzEP(e, 7, 0); jzEX(e, 8, pre + 'AF[0]');
    if (opExpr) jzEX(e, 9, head + body + ';' + opExpr);
    return e;
}

// ---- ghosts: the browser draws every plate / helper graphic of these layouts with ghost off (main pass, g=false or gIn only while
// flying in); only the lyric is ghosted. ld1_anim records the lyric layers, ld1_reg marks every other layer of the build jzNoGhost.
var LD1_LYR = [];
function ld1_anim(ctx, L, o) { LD1_LYR.push(L); return jzAnimate(ctx, L, o); }
function ld1_reg(key, def) {
    var b = def.build;
    def.build = function (ctx) {
        LD1_LYR = [];
        var bb = b(ctx), keep = {}, i;
        for (i = 0; i < LD1_LYR.length; i++) { try { keep[LD1_LYR[i].index] = 1; } catch (e) {} }
        for (i = 1; i <= ctx.comp.numLayers; i++) if (!keep[i]) jzNoGhost(ctx.comp.layer(i));
        LD1_LYR = [];
        return bb;
    };
    jzReg('layout', key, def);
}

/* ---------------------------------------------------------------- text helpers */
// advance width in em (the browser measures real fonts; full-width glyphs are exactly 1 em)
function ld1_adv(ch) {
    var c = ch.charCodeAt(0);
    if (ch === ' ') return 0.34;
    if (c < 0x80) {
        if (/[A-Z]/.test(ch)) return /[MW]/.test(ch) ? 0.9 : (ch === 'I' ? 0.32 : 0.7);
        if (/[a-z]/.test(ch)) return /[mw]/.test(ch) ? 0.88 : /[ijl]/.test(ch) ? 0.28 : /[frt]/.test(ch) ? 0.42 : 0.61;
        if (/[0-9]/.test(ch)) return 0.62;
        return /[.,:;!'|]/.test(ch) ? 0.28 : 0.42;
    }
    if (c < 0x250) return 0.56;
    if (c >= 0xFF61 && c <= 0xFF9F) return 0.5;
    return 1;
}
function ld1_lineW(str, track) { var a = jzChars(str), w = 0; for (var i = 0; i < a.length; i++) w += ld1_adv(a[i]) + (i < a.length - 1 ? (track || 0) : 0); return w; }
// { w, h } in em of a block ('\r' separated lines)
function ld1_meas(str, track, lead) {
    var ls = String(str).split(/\r\n|\r|\n/), w = 0.01;
    for (var i = 0; i < ls.length; i++) w = Math.max(w, ld1_lineW(ls[i], track));
    return { w: w, h: (ls.length - 1) * (lead || 1.2) + 1 };
}
// browser J.fitSize: the font size that fits the block into maxW x maxH
function ld1_fit(str, maxW, maxH, track, lead) { var m = ld1_meas(str, track, lead); return Math.min(maxW / m.w, maxH / m.h); }
// a text block centred at (x, y); o: { font, size, color, track, lead, fill, stroke, strokeColor, opacity, name, align }
function ld1_T(ctx, str, o) {
    var multi = /\r/.test(str);
    return jzText(ctx, str, { font: o.font, size: o.size, color: o.color || ctx.sc.fg, x: o.x || 0, y: o.y || 0, track: o.track || 0, leading: multi ? o.size * (o.lead || 1.2) : null,
        fill: o.fill, stroke: o.stroke, strokeColor: o.strokeColor, strokeOver: o.strokeOver, opacity: o.opacity, name: o.name, align: o.align });
}
// fade a secondary layer: value * expr (expr uses the jzTH header)
function ld1_opx(ctx, L, ex) { jzSetExpr(jzXf(L, 'ADBE Opacity'), ld1_H(ctx) + 'value*(' + ex + ')'); }

/* ---- chunking (port of the browser pack's chunksK / rowsOf / brk) */
function ld1_splitWord(w) {
    var n = jzChars(w).length, o = [], i;
    if (/[A-Za-z]/.test(w)) {
        if (/[^\x00-\x7F]/.test(w)) { var c = jzChunk(w); if (c.length > 1) return c; }
        return n > 10 ? [w.substr(0, Math.ceil(w.length / 2)), w.substr(Math.ceil(w.length / 2))] : [w];
    }
    if (n < 2) return [w];
    var p = jzSplitLines(w, Math.ceil(n / 2)).split('\r');
    for (i = 0; i < p.length; i++) if (p[i]) o.push(p[i]);
    return o;
}
function ld1_chunksK(text, k) {
    var t = jzTrim(String(text || '')), i, j, words = [];
    if (!t) return [''];
    if (/\s/.test(t) && jzHasLatin(t)) {
        var ws = t.split(/\s+/);
        for (i = 0; i < ws.length; i++) if (ws[i]) words.push({ t: ws[i], sp: true });
        if (words.length) words[words.length - 1].sp = false;
    } else {
        var chs = jzChunk(jzStrip(t));
        for (i = 0; i < chs.length; i++) { var q0 = jzTrim(chs[i]); if (q0) words.push({ t: q0, sp: false }); }
    }
    if (!words.length) words = [{ t: t, sp: false }];
    k = Math.max(1, Math.min(k, jzCount(t)));
    var hard = {};
    function gl(w) { return jzCount(w.t); }
    function splitAt(idx) {
        var w = words[idx], parts = ld1_splitWord(w.t), rep = [];
        if (parts.length < 2) { hard[w.t] = 1; return false; }
        for (var q = 0; q < parts.length; q++) rep.push({ t: parts[q], sp: q === parts.length - 1 ? w.sp : false });
        words = words.slice(0, idx).concat(rep, words.slice(idx + 1));
        return true;
    }
    for (var guard = 0; words.length < k && guard < 20; guard++) {
        var bi = -1, bl = 1;
        for (i = 0; i < words.length; i++) if (!hard[words[i].t] && gl(words[i]) > bl) { bl = gl(words[i]); bi = i; }
        if (bi < 0) break;
        splitAt(bi);
    }
    function part() {
        var n = words.length, kk = Math.min(k, n), pre = [0], g, a, b, dp = [], by = [];
        for (a = 0; a < n; a++) pre.push(pre[a] + gl(words[a]) + 0.4);
        var tgt = pre[n] / kk;
        for (g = 0; g <= kk; g++) { dp.push([]); by.push([]); for (a = 0; a <= n; a++) { dp[g].push(1e18); by[g].push(0); } }
        dp[0][0] = 0;
        for (g = 1; g <= kk; g++) for (a = g; a <= n; a++) for (b = g - 1; b < a; b++) { var d = pre[a] - pre[b] - tgt, v = dp[g - 1][b] + d * d; if (v < dp[g][a]) { dp[g][a] = v; by[g][a] = b; } }
        var cuts = [], x = n, out = [], prev = 0;
        for (g = kk; g >= 1; g--) { cuts.unshift(x); x = by[g][x]; }
        for (a = 0; a < cuts.length; a++) { out.push(words.slice(prev, cuts[a])); prev = cuts[a]; }
        return out;
    }
    var groups = part();
    for (var it = 0; it < 3 && groups.length > 1; it++) {
        var mx = -1, mn = 1e9, gi = 0;
        for (i = 0; i < groups.length; i++) { var s = 0; for (j = 0; j < groups[i].length; j++) s += gl(groups[i][j]); if (s > mx) { mx = s; gi = i; } if (s < mn) mn = s; }
        if (mx <= mn * 1.7 + 1) break;
        var big = groups[gi], bw = null, wi = -1;
        for (j = 0; j < big.length; j++) if (!hard[big[j].t] && gl(big[j]) >= 4 && (!bw || gl(big[j]) > gl(bw))) bw = big[j];
        if (!bw) break;
        for (j = 0; j < words.length; j++) if (words[j] === bw) { wi = j; break; }
        if (wi < 0 || !splitAt(wi)) break;
        groups = part();
    }
    var res = [];
    for (i = 0; i < groups.length; i++) { var s2 = ''; for (j = 0; j < groups[i].length; j++) s2 += groups[i][j].t + (j < groups[i].length - 1 && groups[i][j].sp ? ' ' : ''); if (s2) res.push(s2); }
    return res.length ? res : [t];
}
// rows of glyph slots (a ' ' slot is a word gap) broken at chunk boundaries
function ld1_rowsOf(text, maxPer) {
    var t = jzFlat(text), n = jzCount(t), rs = n > maxPer ? ld1_chunksK(t, Math.ceil(n / maxPer)) : [t], out = [];
    for (var i = 0; i < rs.length; i++) out.push(jzChars(jzTrim(rs[i])));
    return out;
}
function ld1_brk(text, maxPer) { var t = jzFlat(text), n = jzCount(t); return n <= maxPer ? t : ld1_chunksK(t, Math.ceil(n / maxPer)).join('\r'); }

/* ---- glyph layers: ONE text layer, one glyph per line; anchor = position = (cx, cy) keeps layer space == comp space */
function ld1_G(ctx, chars, o) {
    var t = [], i;
    for (i = 0; i < chars.length; i++) t.push(chars[i] === '' || chars[i] === ' ' ? '\u30FB' : chars[i]);
    var L = jzText(ctx, t.join('\r'), { font: o.font, size: o.size, color: o.color || ctx.sc.fg, x: 0, y: 0, track: 0, leading: o.size, fill: o.fill, stroke: o.stroke,
        strokeColor: o.strokeColor, strokeOver: o.strokeOver, name: o.name || 'glyphs' });
    var cx = o.cx || 0, cy = o.cy || 0;
    jzXf(L, 'ADBE Anchor Point').setValue([cx, cy]); jzXf(L, 'ADBE Position').setValue([cx, cy]);
    return { L: L, fs: o.size, LD: o.size, n: t.length };
}
// per-glyph transform from one expression body (i = glyph index): it sets x, y (glyph centre, comp px) and optionally
// r (deg), sx / sy (scale factors -1..3), a (opacity 0..1); which = letters of 'prsa' for the animators to create
function ld1_GX(ctx, G, name, head, body, which) {
    var pre = head + 'var i=textIndex-1,x=0,y=0,r=0,sx=1,sy=1,a=1;' + body + ';', KB = Math.max(ctx.W, ctx.H) * 3 + G.n * G.LD;
    if (which.indexOf('p') >= 0) jzAnimator(G.L, name + ' Place', [['ADBE Text Position 3D', [KB, KB, 0]]], pre + '[x/' + jzN(KB) + '*100,(y+' + jzN(LD1_CY * G.fs) + '-i*' + jzN(G.LD) + ')/' + jzN(KB) + '*100,0]');
    if (which.indexOf('r') >= 0) jzAnimator(G.L, name + ' Turn', [['ADBE Text Rotation', 360]], pre + 'Math.max(-100,Math.min(100,r/3.6))');
    if (which.indexOf('s') >= 0) jzAnimator(G.L, name + ' Size', [['ADBE Text Scale 3D', [300, 300, 100]]], pre + '[Math.max(-100,Math.min(100,(sx-1)*50)),Math.max(-100,Math.min(100,(sy-1)*50)),0]');
    if (which.indexOf('a') >= 0) jzAnimator(G.L, name + ' Show', [['ADBE Text Opacity', 0]], pre + '(1-cl(a))*100');
}
// static placement of a glyph layer: pts[i] = [x, y] glyph centres (comp px)
function ld1_place(G, pts, name) {
    var offs = [];
    for (var i = 0; i < pts.length; i++) offs.push([pts[i][0], pts[i][1] + LD1_CY * G.fs - i * G.LD]);
    return jzCharOffsets(G.L, offs, name || 'JZ Place');
}
// glyph centres of a row of slots (advance widths), centred on cx; returns [{ ch, x, w }]
function ld1_row(chars, size, k, cx) {
    var tot = 0, i, out = [];
    for (i = 0; i < chars.length; i++) tot += ld1_adv(chars[i]) * size * k;
    var x = cx - tot / 2;
    for (i = 0; i < chars.length; i++) { var w = ld1_adv(chars[i]) * size * k; out.push({ ch: chars[i], x: x + w / 2, w: w }); x += w; }
    out.tot = tot;
    return out;
}

/* ================================================================== 1 cube \u2014 \u7ACB\u65B9\u4F53 */
// orthographic cube (yaw th, pitch ph): every face is a parallelogram, so faces are affine-mapped rectangles and the
// lyric on a face is the text layer mapped by the same matrix (Transform effect)
function ld1_cubeTh(mode, dir, k, T, t) {
    var D = Math.PI / 180, x = Math.max(0, Math.min(1, t / 0.8)), c = 2.1, sIn = 1 - (1 + c * Math.pow(x - 1, 3) + 1.1 * Math.pow(x - 1, 2));
    if (mode === 'turn') {
        var acc = 0;
        for (var j = 1; j < k; j++) { var q = Math.max(0, Math.min(1, (t - j * T + 0.25) / 0.5)); acc += q < 0.5 ? 4 * q * q * q : 1 - Math.pow(-2 * q + 2, 3) / 2; }
        return dir * (-(acc * Math.PI / 2) - 20 * D - sIn * 90 * D);
    }
    return dir * ((mode === 'pair' ? -45 : -24) * D) + sIn * 100 * D * dir;
}
ld1_reg('cube', {
    plan: function (rng, cut, st) {
        var n = cut.n, mode = n >= 4 && rng.chance(0.45) ? 'pair' : 'single';
        if (n >= 6 && cut.dur > 2.6 && rng.chance(0.35)) mode = 'turn';
        var k = mode === 'pair' ? 2 : mode === 'turn' ? Math.min(4, Math.max(2, Math.ceil(n / 5))) : 1;
        return {
            mode: mode, chunks: k > 1 ? ld1_chunksK(cut.text, k) : [jzFlat(cut.text)], font: rng.pick(jzFontsOf(st, ['display', 'display', 'serif'])),
            pitch: rng.range(20, 30), dir: rng.pick([1, -1]), face: rng.pick(['ink', 'accent', 'ink']), top: rng.pick(['accent', 'shade']), sway: rng.range(3, 7)
        };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), i, f;
        var chunks = jzP(ctx, 'chunks', null);
        if (!chunks || !chunks.length) chunks = [jzFlat(c.text)];
        var mode = chunks.length < 2 ? 'single' : jzP(ctx, 'mode', 'pair');
        if (mode === 'single') chunks = [jzFlat(c.text)];
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), ph = jzP(ctx, 'pitch', 25) * Math.PI / 180, dir = jzP(ctx, 'dir', 1) < 0 ? -1 : 1;
        var h = Math.min(W * (mode === 'pair' ? 0.27 : 0.3), H * 0.285, u * 0.36), cx = W / 2, cy = H / 2 + h * Math.sin(ph) * 0.55;
        var pc = ld1_plateCol(sc, jzP(ctx, 'face', 'ink') === 'accent' ? [sc.accent, sc.ink] : [sc.ink, sc.fg, sc.accent]), tc = ld1_onCol(sc, pc);
        var topC = jzP(ctx, 'top', 'shade') === 'accent' && jzContrast(sc.accent, pc) > 1.3 ? sc.accent : ld1_shade(sc, pc, 0.22);
        var k = chunks.length, T = c.dur / k, DK = ld1_dark(sc), sp = Math.sin(ph), cp = Math.cos(ph);
        var HD = ld1_H(ctx) + 'var DG=Math.PI/180,DIR=' + dir + ',SP=' + jzN(sp) + ',CP=' + jzN(cp) + ',H0=' + jzN(h) + ',CX=' + jzN(cx) + ',CY=' + jzN(cy) + ';' +
            'var pp=ob(cl(time/0.4),1.3)*(1-0.15*ic(PO)),hs=H0*pp,sIn=1-ob(cl(time/0.8),1.1),th;';
        if (mode === 'turn') HD += 'var acc=0;for(var j=1;j<' + k + ';j++)acc+=iocu((time-j*' + jzN(T) + '+0.25)/0.5);th=DIR*(-(acc*Math.PI/2)-20*DG-sIn*90*DG);';
        else HD += 'th=DIR*(' + (mode === 'pair' ? -45 : -24) + '*DG)+sIn*100*DG*DIR;';
        HD += 'th+=Math.sin(time*0.7)*' + jzN(jzP(ctx, 'sway', 5) * ld1_mot(ctx)) + '*DG+ic(PO)*70*DG*DIR;' +
            'function face(f){if(f<4){var a=f*Math.PI/2+th,ca=Math.cos(a),sa=Math.sin(a);return [ca,-sa*SP,0,CP,CX+hs*sa,CY+hs*ca*SP,ca*CP,sa,ca];}' +
            'var ct=Math.cos(th),s2=Math.sin(th);return [ct,-s2*SP,s2,ct*SP,CX,CY-hs*CP,SP,0,0];}\n';
        var CL = 'var PCc=' + ld1_col(pc) + ',LIc=' + ld1_col(ld1_light(sc)) + ',DKc=' + ld1_col(DK) + ';function shd(s){return s>=0?mixc(PCc,LIc,Math.min(0.9,s)):mixc(PCc,DKc,Math.min(0.9,-s));}';
        // faces (visible faces of a convex solid never overlap) + contact shadow below them
        var S = jzShapeLayer(ctx, 'cube', 0, 0), lw = Math.max(1.2 * ctx.u, u * 0.0022);
        for (f = 0; f < 5; f++) {
            var g = jzGrp(S, f === 4 ? 'top' : 'face ' + (f + 1));
            jzAddRect(g, 2 * h, 2 * h, 0);
            var fb = 'var F=face(' + f + ');var M=[F[0]*pp,F[1]*pp,F[2]*pp,F[3]*pp,F[4],F[5]]';
            var lx = HD + CL + fb + ';var LT=shd((-0.55*F[7]*DIR+0.55*F[8])*0.35);';
            // each item is fully set up before its next sibling is added (adding one invalidates older item references in AE)
            var stk = jzAddStroke(g, jzMixHex(f === 4 ? topC : pc, DK, 0.35), lw);
            if (f < 4) jzSetExpr(stk.property('ADBE Vector Stroke Color'), lx + 'mixc(LT,DKc,0.35)');
            var fl = jzAddFill(g, f === 4 ? topC : pc);
            if (f < 4) jzSetExpr(fl.property('ADBE Vector Fill Color'), lx + 'LT');
            ld1_affGrp(g, HD, fb, '(F[6]>0.004?100:0)');
        }
        var gS = jzGrp(S, 'shadow'); jzAddRect(gS, 2.08 * h, 2.08 * h, 0); jzAddFill(gS, DK);
        ld1_affGrp(gS, HD, 'var F=face(4);var M=[F[0]*pp,F[1]*pp,F[2]*pp,F[3]*pp,CX,CY+hs*CP+hs*0.14]', '35');
        jzSetExpr(jzXf(S, 'ADBE Opacity'), HD + 'hs<1?0:100*K');
        // which face carries which chunk
        function faceOf(f) {
            if (mode === 'turn') { var idx = dir > 0 ? f : (4 - f) % 4; return idx < k ? idx : -1; }
            if (mode === 'pair') return dir > 0 ? (f === 0 ? 0 : f === 1 ? 1 : -1) : (f === 3 ? 0 : f === 0 ? 1 : -1);
            return f === 0 ? 0 : -1;
        }
        function restC(f, t) { var th = ld1_cubeTh(mode, dir, k, T, t), a = f * Math.PI / 2 + th; return [cx + h * Math.sin(a), cy + h * Math.cos(a) * sp]; }
        function faceText(L, f, P, opE) { ld1_fxAff(L, HD, 'var F=face(' + f + ');var M=[F[0]*pp,F[1]*pp,F[2]*pp,F[3]*pp,F[4],F[5]]', '(F[6]>=0.08?' + opE + ':0)', P); }
        // top label
        var P4 = [cx, cy - h * cp], TL = jzText(ctx, 'No.' + jzLineNo(ctx), { font: jzMonoF(ctx), size: h * 0.2, track: 0.2, color: ld1_onCol(sc, topC), x: P4[0], y: P4[1], name: 'cube top label' });
        faceText(TL, 4, P4, '90*K');
        for (f = 0; f < 4; f++) {
            var ci = faceOf(f), L, P;
            if (ci >= 0 && chunks[ci]) {
                var t = ld1_brk(chunks[ci], mode === 'single' ? (port ? 4 : 5) : 4), size = Math.min(ld1_fit(t, h * 1.64, h * 1.5, 0.02, 1.12), h * 0.9);
                var arrive = mode === 'turn' ? ci * T + (ci ? 0.05 : 0.12) : 0.18 + ci * 0.12;
                P = restC(f, arrive + 0.6);
                L = ld1_T(ctx, t, { font: font, size: size, color: tc, x: P[0], y: P[1], track: 0.02, lead: 1.12, name: chunks[ci] });
                ld1_anim(ctx, L, { mi: ld1_mi(ctx, arrive), noHold: ld1_plateHold(ctx) });
                faceText(L, f, P, '100');
            } else if (mode === 'single' && f === (dir > 0 ? 1 : 3)) {
                var at = ld1_brk(ld1_alt(ctx), 6), fs = Math.min(ld1_fit(at, h * 1.5, h * 1.2, 0.08, 1.4), h * 0.2);
                P = restC(f, 1);
                L = ld1_T(ctx, at, { font: jzBodyF(ctx), size: fs, color: tc, x: P[0], y: P[1], track: 0.08, lead: 1.4, name: 'cube side copy' });
                faceText(L, f, P, '75*K*oe((time-0.3)/0.4)');
            } else if (mode === 'single' && f === (dir > 0 ? 3 : 1)) {
                P = restC(f, 1);
                L = ld1_T(ctx, jzLineNo(ctx), { font: font, size: h * 1.1, color: tc, x: P[0], y: P[1], fill: false, stroke: Math.max(1.5 * ctx.u, h * 0.012), strokeColor: tc, name: 'cube side number' });
                faceText(L, f, P, '50*K');
            }
        }
        return ld1_box(cx - h * 1.35, cy - h * (1 + sp) - h * 0.1, cx + h * 1.35, cy + h * (cp + sp) + h * 0.1);
    }
});

/* ================================================================== 2 cylinder \u2014 \u5186\u7B52 */
ld1_reg('cylinder', {
    plan: function (rng, cut, st) {
        return {
            font: rng.pick(jzFontsOf(st, ['display', 'serif'])), fs: rng.pick(jzFontsOf(st, ['body', 'display'])), rings: rng.pick([2, 4, 4, 6]), band: rng.chance(0.45),
            dir: rng.pick([1, -1]), speed: rng.range(0.25, 0.45), tilt: rng.range(0.13, 0.2), sep: rng.pick([' \u30FB ', ' / ', ' \u2014 ']), glass: rng.chance(0.7)
        };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), i, j;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), fsF = jzP(ctx, 'fs', jzBodyF(ctx)), dir = jzP(ctx, 'dir', 1) < 0 ? -1 : 1;
        var t0 = jzFlat(c.text), n = jzCount(t0);
        if (!n) return null;
        var rowsT = n > (port ? 7 : 9) ? ld1_chunksK(t0, 2) : [t0], nR = rowsT.length;
        var R = Math.min(W * (port ? 0.38 : 0.3), H * 0.42), cx = W / 2, ek = jzP(ctx, 'tilt', 0.16), yMid = H / 2, size = H * 0.2;
        for (i = 0; i < rowsT.length; i++) size = Math.min(size, 2.1 * R / Math.max(1, ld1_lineW(rowsT[i], 0) * 1.04));
        size = Math.min(size, u * (nR > 1 ? 0.15 : 0.19));
        var rowGap = size * 1.45, blockH = nR * rowGap, mot = ld1_mot(ctx);
        var ss = Math.max(12 * ctx.u, size * 0.3), unit = jzChars(jzFlat(c.lineText || c.text) + jzP(ctx, 'sep', ' \u30FB ')), nSec = jzP(ctx, 'rings', 4), ringY = [];
        for (i = 0; i < nSec; i++) { var side = i % 2 ? 1 : -1, kk = Math.floor(i / 2); ringY.push({ y: yMid + side * (blockH / 2 + ss * 0.9 + kk * ss * 1.6), dir: (i % 2 ? 1 : -1) * dir, k: kk }); }
        var yTop = yMid - blockH / 2, yBot = yMid + blockH / 2;
        for (i = 0; i < ringY.length; i++) { yTop = Math.min(yTop, ringY[i].y - ss * 0.8); yBot = Math.max(yBot, ringY[i].y + ss * 0.8); }
        yTop += -ek * R - size * 0.12; yBot += -ek * R + size * 0.12;
        var HD = ld1_H(ctx) + 'var DIR=' + dir + ',RR=' + jzN(R) + ',EK=' + jzN(ek) + ',CX=' + jzN(cx) + ';var spin=-(1-oc(time/0.8))*1.7*DIR+Math.sin(time*0.8)*' + jzN(0.05 * mot) + '+ic(PO)*1.9*DIR;';
        function ell(y, a0, a1, N) { var pts = []; for (var q = 0; q <= N; q++) { var t = a0 + (a1 - a0) * q / N; pts.push([cx + R * Math.sin(t), y + ek * R * Math.cos(t)]); } return pts; }
        // glass body
        if (jzP(ctx, 'glass', true)) {
            var lw = Math.max(1 * ctx.u, u * 0.0016), GS = jzShapeLayer(ctx, 'cylinder glass', 0, 0), gg = jzGrp(GS, 'glass');
            jzAddPath(gg, ell(yTop, -Math.PI, Math.PI, 48), true);
            jzAddPath(gg, ell(yBot, -Math.PI / 2, Math.PI / 2, 24), false);
            jzAddStroke(gg, sc.sub, lw);
            var gl2 = jzGrp(GS, 'sides');
            jzAddPath(gl2, [[cx - R, yTop], [cx - R, yBot]], false); jzAddPath(gl2, [[cx + R, yTop], [cx + R, yBot]], false);
            jzAddStroke(gl2, sc.sub, lw); jzAddTrimPaths(gl2, ld1_H(ctx) + 'oc(time/0.6)*100');
            ld1_opx(ctx, GS, '0.45*oc(time/0.6)*K');
        }
        // wrap band behind the lyric (radial ramp = the browser's horizontal light gradient)
        var bandC = ld1_plateCol(sc, [sc.accent, sc.ink]), onBand = !!jzP(ctx, 'band', false);
        if (onBand) {
            var yb0 = yMid - blockH / 2 + size * 0.02 - ek * R, yb1 = yMid + blockH / 2 - size * 0.02 - ek * R;
            var top = ell(yb0, -Math.PI / 2, Math.PI / 2, 36), bot = ell(yb1, -Math.PI / 2, Math.PI / 2, 36).reverse();
            var BS = jzShapeLayer(ctx, 'cylinder band', 0, 0), gb = jzGrp(BS, 'band');
            jzAddPath(gb, top.concat(bot), true); jzAddFill(gb, bandC);
            var rp = jzEffect(BS, 'ADBE Ramp', 'JZ Band Light'), ymb = (yb0 + yb1) / 2 + ek * R;
            jzEP(rp, 1, [cx + R * 0.1, ymb]); jzEP(rp, 2, jzHex(ld1_shade(sc, bandC, 0.12))); jzEP(rp, 3, [cx + R * 1.15, ymb]); jzEP(rp, 4, jzHex(ld1_shade(sc, bandC, -0.5))); jzEP(rp, 5, 2);
            ld1_opx(ctx, BS, 'oc(time/0.6)*K');
        }
        // secondary rings: one glyph layer each, glyphs placed round the drum (back half mirrored & dim)
        var speed = jzP(ctx, 'speed', 0.35);
        for (i = 0; i < ringY.length; i++) {
            var rg = ringY[i], cnt = Math.min(72, Math.max(unit.length, Math.floor(Math.PI * 2 * R / (ss * 1.05)))), chs = [], JJ = [];
            for (j = 0; j < cnt; j++) { var ch = unit[j % unit.length]; if (ch === ' ' || ch === '\u3000') continue; chs.push(ch); JJ.push(j); }
            if (!chs.length) continue;
            var G = ld1_G(ctx, chs, { font: fsF, size: ss, color: sc.sub, name: 'cylinder ring ' + (i + 1), cx: cx, cy: rg.y });
            var rh = HD + 'var JJ=' + ld1_arr(JJ) + ',ea=oc((time-' + jzN(0.05 + rg.k * 0.08) + ')/0.5)*K;';
            ld1_GX(ctx, G, 'JZ Ring', rh, 'var t=time*' + jzN(speed * rg.dir) + '+' + jzN(rg.k * 0.7) + '+spin*0.5+JJ[i]/' + cnt + '*Math.PI*2,c=Math.cos(t),s=Math.sin(t);' +
                'x=CX+RR*s;y=' + jzN(rg.y) + '-EK*RR+EK*RR*c;sx=c;r=Math.atan2(-EK*s,1)*180/Math.PI*0.8;a=Math.abs(c)<0.06?0:(c>0?0.45+0.55*c:0.12-0.1*c)*ea', 'prsa');
        }
        // the lyric rows on the front of the drum (one glyph layer)
        var gl = [], A0 = [], Y0 = [], RI = [];
        for (var ri = 0; ri < nR; ri++) {
            var y0 = yMid + (ri - (nR - 1) / 2) * rowGap, row = ld1_row(jzChars(rowsT[ri]), size, 1.04, 0);
            for (j = 0; j < row.length; j++) { if (row[j].ch === ' ') continue; gl.push(row[j].ch); A0.push(row[j].x); Y0.push(y0); RI.push(ri % 2 ? 0.08 : -0.08); }
        }
        var ML = ld1_G(ctx, gl, { font: font, size: size, color: onBand ? ld1_onCol(sc, bandC) : sc.fg, name: t0, cx: cx, cy: yMid });
        ld1_GX(ctx, ML, 'JZ Drum', HD + 'var A0=' + ld1_arr(A0) + ',Y0=' + ld1_arr(Y0) + ',RI=' + ld1_arr(RI) + ';',
            'var t=A0[i]/RR+spin+RI[i]*' + (nR > 1 ? '(1-oc(time/0.9))' : '0') + ',c=Math.cos(t),s=Math.sin(t);x=CX+RR*s;y=Y0[i]-EK*RR+EK*RR*c;sx=Math.max(0.02,c);' +
            'r=Math.atan(-EK*s/Math.max(c,0.5))*180/Math.PI;a=c<0.03?0:0.35+0.65*Math.pow(c,0.6)', 'prsa');
        ld1_anim(ctx, ML.L, { mi: 0, noHold: true });
        return ld1_box(cx - R, yMid - blockH / 2, cx + R, yMid + blockH / 2);
    }
});

/* ================================================================== 3 flipCards \u2014 \u30AB\u30FC\u30C9\u3081\u304F\u308A */
ld1_reg('flipCards', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), order: rng.pick(['ltr', 'ltr', 'random']), table: rng.chance(0.55), back: rng.pick(['accent', 'ink']), peek: rng.chance(0.6), idx: rng.chance(0.7) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, s = c.seed, u = jzU(ctx), port = jzPortrait(ctx), i, j;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), table = !!jzP(ctx, 'table', false), peek = !!jzP(ctx, 'peek', false), idx = !!jzP(ctx, 'idx', true);
        var rowsC = ld1_rowsOf(c.text, port ? 4 : 7), cards = [], per = 1;
        for (i = 0; i < rowsC.length; i++) { per = Math.max(per, rowsC[i].length); for (j = 0; j < rowsC[i].length; j++) if (rowsC[i][j] !== ' ') cards.push({ ch: rowsC[i][j], r: i, j: j, cnt: rowsC[i].length }); }
        var n = cards.length;
        if (!n) return null;
        var rowsL = rowsC.length, rows = rowsL + (table ? 2 : 0), asp = 1.38, gap = 0.16;
        var k = Math.min(W * 0.86 / (per + (per - 1) * gap), H * (table ? 0.86 : 0.7) / (rows * asp + (rows - 1) * gap), u * 0.26);
        var cw = k, chh = k * asp, sx = k * (1 + gap), sy = chh + k * gap;
        var faceC = ld1_light(sc), faceT = ld1_dark(sc), backC = null, cand = (jzP(ctx, 'back', 'accent') === 'accent' ? [sc.accent, sc.ink] : [sc.ink, sc.accent]).concat([sc.accent2, sc.sub, ld1_dark(sc)]);
        for (i = 0; i < cand.length && !backC; i++) if (cand[i] && jzContrast(cand[i], faceC) >= 1.6 && jzContrast(cand[i], sc.bg) >= 1.25) backC = cand[i];
        if (!backC) backC = jzMixHex(faceC, ld1_dark(sc), 0.6);
        var backL = jzMixHex(backC, ld1_onCol(sc, backC), 0.35);
        var fd = jzClamp(c.dur * 0.12, 0.22, 0.36), gapT = jzClamp(c.dur * 0.34 / n, 0.04, 0.12), rank = [];
        for (i = 0; i < n; i++) rank.push(i);
        if (jzP(ctx, 'order', 'ltr') === 'random') rank.sort(function (a, b) { return jzR(s, a, 31) - jzR(s, b, 31); });
        var ord = []; for (i = 0; i < n; i++) ord.push(0);
        for (i = 0; i < n; i++) ord[rank[i]] = i;
        var lw = Math.max(1 * ctx.u, k * 0.012), rr = k * 0.08, TH = ld1_H(ctx), DK = ld1_dark(sc);
        // card geometry at flip progress p (0 face-down .. 1 face-up), card turns round its vertical axis
        var FL = 'function card(p){var an=p*Math.PI,cc=Math.cos(an),sn=Math.sin(an),s2=1+0.1*sn;return [Math.max(0.02,Math.abs(cc))*s2,s2,sn,p>=0.5?1:0];}';
        function cardGroups(S, name, x, y, pE, aE, xh) {
            var g = jzGrp(S, name), hd = TH + FL + (xh || '') + 'var P=' + pE + ',C=card(P);';
            var gf = ld1_sub(g, 'face'); ld1_rr(gf, cw, chh, rr * 0.5); jzAddStroke(gf, jzMixHex(faceC, faceT, 0.25), lw); jzAddFill(gf, faceC);
            ld1_gOp(gf, hd + 'C[3]*100');
            var gb = ld1_sub(g, 'back'), ins = k * 0.09, dd = Math.min(cw - ins * 2, chh - ins * 2) * 0.28;
            var gd = ld1_sub(gb, 'diamond'); jzAddPath(gd, [[0, -dd * 1.2], [dd * 0.9, 0], [0, dd * 1.2], [-dd * 0.9, 0]], true); jzAddFill(gd, backL);
            var gi = ld1_sub(gb, 'inner'); ld1_rr(gi, cw - ins * 2, chh - ins * 2, rr * 0.6); jzAddStroke(gi, backL, lw);
            var gb2 = ld1_sub(gb, 'plate'); ld1_rr(gb2, cw, chh, rr * 0.5); jzAddFill(gb2, backC);
            ld1_gOp(gb, hd + '(1-C[3])*100');
            var gs = ld1_sub(g, 'shadow'); ld1_rr(gs, cw, chh, rr * 0.5); jzAddFill(gs, DK);
            ld1_gPos(gs, hd + '[' + jzN(k * 0.05) + '*(1+C[2]*1.5)/C[0],' + jzN(k * 0.07) + '*(1+C[2]*1.5)/C[1]]'); ld1_gOp(gs, '25');
            jzGX(g).property('ADBE Vector Position').setValue([x, y]);
            ld1_gSc(g, hd + '[C[0]*100,C[1]*100]');
            ld1_gOp(g, hd + '100*(' + aE + ')');
            return g;
        }
        // decoy table rows (face-down), one decoy peeks now and then
        if (table) {
            var DS = jzShapeLayer(ctx, 'card table', 0, 0), PE = [], PX = [], PY = [];
            var ta = 'oc(time/0.4)*(1-ic((PO-0.4)/0.6))*0.8', q = 0;
            var pk = 'function pk(q){if(!' + (peek ? 1 : 0) + ')return 0;var T=1.3,kk=Math.floor(time/T),f=time/T-kk;if(kk<1||Math.floor(hh(kk*5+' + (s % 997) + ')*' + (per * 2) + ')!==q)return 0;return f<0.25?iocu(f/0.25):f<0.6?1:1-iocu((f-0.6)/0.25);}';
            for (var side = 0; side < 2; side++) {
                var rr0 = side ? rowsL : -1;
                for (j = 0; j < per; j++) {
                    var x = W / 2 + (j - (per - 1) / 2) * sx, y = H / 2 + (rr0 - (rowsL - 1) / 2) * sy, qi = j + (side ? per : 0);
                    cardGroups(DS, 'decoy ' + (q + 1), x, y, 'pk(' + qi + ')', ta, pk);
                    PX.push(x); PY.push(y); PE.push(qi); q++;
                }
            }
            if (peek) {
                var gl = [], QI = [];
                for (i = 0; i < PE.length; i++) { gl.push(jzR(s, i, 6) < 0.5 ? '\uFF1F' : '\u2605'); QI.push(PE[i]); }
                var DG2 = ld1_G(ctx, gl, { font: font, size: k * 0.46, color: sc.accent === faceC ? faceT : sc.accent, name: 'card peeks', cx: W / 2, cy: H / 2 });
                var PXa = [], PYa = []; for (i = 0; i < PX.length; i++) { PXa.push(PX[i]); PYa.push(PY[i]); }
                ld1_GX(ctx, DG2, 'JZ Peek', TH + FL + pk + 'var PX=' + ld1_arr(PXa) + ',PY=' + ld1_arr(PYa) + ',QI=' + ld1_arr(QI) + ';',
                    'var P=pk(QI[i]),C=card(P);x=PX[i];y=PY[i];sx=C[0];sy=C[1];a=C[3]*' + ta, 'psa');
            }
        }
        // the lyric cards: plates in one shape layer, one text layer per card (scaled by a per-glyph animator)
        var CS = jzShapeLayer(ctx, 'cards', 0, 0), bb = null, IX = [], IP = [], IS = [];
        var PO_ = function (i) { return 'var ti=' + jzN(0.12 + ord[i] * gapT) + ',p=iocu((time-ti)/' + jzN(fd) + '),po=cl(PO*1.5-' + jzN(ord[i] / Math.max(1, n) * 0.5) + ');if(po>0)p=Math.min(p,1-iocu(po));p'; };
        var cardP = [];
        for (i = 0; i < n; i++) {
            var cd = cards[i], x2 = W / 2 + (cd.j - (cd.cnt - 1) / 2) * sx, y2 = H / 2 + (cd.r - (rowsL - 1) / 2) * sy;
            cardP.push([x2, y2]);
            var pE = '(function(){' + PO_(i).replace(/p$/, 'return p;') + '})()';
            cardGroups(CS, 'card ' + (i + 1), x2, y2, pE, 'oc((time-' + jzN(ord[i] * 0.02) + ')/0.25)*(1-ic((PO-0.55)/0.45))');
        }
        for (i = 0; i < n; i++) {
            var pE2 = '(function(){' + PO_(i).replace(/p$/, 'return p;') + '})()';
            var T = ld1_T(ctx, cards[i].ch, { font: font, size: k * 0.6, color: faceT, x: cardP[i][0], y: cardP[i][1], name: cards[i].ch });
            var fh = TH + FL + 'var P=' + pE2 + ',C=card(P);';
            jzAnimator(T, 'JZ Flip', [['ADBE Text Scale 3D', [300, 300, 100]]], fh + '[(C[0]-1)*50,(C[1]-1)*50,0]');
            jzAnimator(T, 'JZ Face Up', [['ADBE Text Opacity', 0]], fh + '(1-C[3])*100');
            ld1_anim(ctx, T, { mi: ld1_mi(ctx, 0.12 + ord[i] * gapT + fd * 0.5), noHold: ld1_plateHold(ctx) });
            bb = jzUnion(bb, ld1_box(cardP[i][0] - cw / 2, cardP[i][1] - chh / 2, cardP[i][0] + cw / 2, cardP[i][1] + chh / 2));
            if (idx) { IX.push(cards[i].ch, cards[i].ch); IP.push(i, i); IS.push(-1, 1); }
        }
        // corner indices of all cards: one glyph layer
        if (idx && IX.length) {
            var XS = [], YS = [], PS = [];
            for (i = 0; i < n; i++) { XS.push(cardP[i][0]); YS.push(cardP[i][1]); }
            var CI = ld1_G(ctx, IX, { font: font, size: k * 0.15, color: sc.accent, name: 'card indices', cx: W / 2, cy: H / 2 });
            var ph = TH + FL + 'var XS=' + ld1_arr(XS) + ',YS=' + ld1_arr(YS) + ',IP=' + ld1_arr(IP) + ',IS=' + ld1_arr(IS) + ';function pc(i){';
            for (i = 0; i < n; i++) ph += (i ? 'else ' : '') + 'if(i===' + i + '){' + PO_(i).replace(/p$/, 'return p;') + '}';
            ph += 'return 0;}';
            ld1_GX(ctx, CI, 'JZ Index', ph, 'var q=IP[i],C=card(pc(q)),sg=IS[i];x=XS[q]+sg*' + jzN(cw * 0.34) + '*C[0];y=YS[q]+sg*' + jzN(chh * 0.36) + '*C[1];sx=C[0];sy=C[1];r=sg>0?180:0;' +
                'a=C[3]*(C[0]>0.33?1:0)*oc((time-' + '0.1)/0.25)*K', 'prsa');
        }
        return bb;
    }
});

// glyph centres of a block laid out like the browser's layoutText (relative to the block centre)
function ld1_layout(str, size, track, lead) {
    var lines = String(str).split(/\r\n|\r|\n/), nL = lines.length, LDd = (lead || 1.2) * size, out = [], mw = 0;
    for (var li = 0; li < nL; li++) {
        var a = jzChars(lines[li]), ws = [], w = 0, j;
        for (j = 0; j < a.length; j++) { ws.push(ld1_adv(a[j]) * size); w += ws[j] + (j < a.length - 1 ? (track || 0) * size : 0); }
        mw = Math.max(mw, w);
        var x = -w / 2, y = (li - (nL - 1) / 2) * LDd;
        for (j = 0; j < a.length; j++) { out.push({ ch: a[j], x: x + ws[j] / 2, y: y, sp: a[j] === ' ' || a[j] === '\u3000' }); x += ws[j] + (track || 0) * size; }
    }
    out.W = mw; out.H = nL * LDd - (LDd - size);
    return out;
}

/* ================================================================== 4 accordion \u2014 \u86C7\u8179 */
// panels are trapezoids (alternate edges taller): rect + two right-angle wedges scaled by the group transform, one fill
ld1_reg('accordion', {
    plan: function (rng, cut, st) {
        var port = cut.H > cut.W * 1.08;
        return {
            font: rng.pick(jzFontsOf(st, ['display', 'serif'])), fold: rng.range(26, 38), orient: port && cut.n <= 10 && rng.chance(0.7) ? 'v' : 'h',
            covers: rng.chance(0.7), plate: rng.pick(['ink', 'fg', 'accent']), breathe: rng.range(3, 7), start: rng.pick([1, -1])
        };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), i, j, r;
        var n = jzCount(c.text);
        if (!n) return null;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), fold = jzP(ctx, 'fold', 32), vert = jzP(ctx, 'orient', 'h') === 'v' && port && n <= 10;
        var rowsA = ld1_rowsOf(c.text, vert ? 10 : port ? 5 : 9), rows = rowsA.length, per = 1;
        for (r = 0; r < rows; r++) per = Math.max(per, rowsA[r].length);
        var cA = Math.cos(fold * Math.PI / 180), kap = 0.13, st0 = jzP(ctx, 'start', 1) > 0 ? 0 : 1;
        var Pw = Math.min((vert ? H * 0.82 : W * 0.86) / (per * cA + 0.5), (vert ? W * 0.5 : H * 0.62 / rows) / 1.25, u * 0.3), Ph = Pw * 1.2;
        var pl = jzP(ctx, 'plate', 'ink'), pc = ld1_plateCol(sc, pl === 'accent' ? [sc.accent, sc.ink] : pl === 'fg' ? [sc.fg, sc.ink] : [sc.ink, sc.fg]);
        var tc = ld1_onCol(sc, pc), lit = ld1_shade(sc, pc, 0.1), dim = ld1_shade(sc, pc, -0.32), cv = ld1_plateCol(sc, [sc.accent, sc.ink, sc.fg]);
        var covC = cv === pc ? ld1_shade(sc, pc, -0.45) : cv, DK = ld1_dark(sc), lw = Math.max(1 * ctx.u, u * 0.0016);
        var AL = vert ? H / 2 : W / 2;                      // centre along the strip
        var HD = ld1_H(ctx) + 'var FD=' + jzN(fold) + ',PW=' + jzN(Pw) + ',PH=' + jzN(Ph) + ',KP=' + kap + ';var opn=ob(cl((time-0.02)/0.7),1.25);' +
            'var al=FD+(1-opn)*(86-FD)+Math.sin(time*2.1)*' + jzN(jzP(ctx, 'breathe', 5) * ld1_mot(ctx)) + '*cl(time/0.8)+ic(PO)*(88-FD);al=Math.max(2,Math.min(88.5,al))*Math.PI/180;' +
            'var ca=Math.cos(al),sa=Math.sin(al),pw=PW*ca,hmn=PH*(1-KP*sa),dd=PH*KP*sa;\n';
        function P2(a, b) { return vert ? '[' + b + ',' + a + ']' : '[' + a + ',' + b + ']'; }
        var S = jzShapeLayer(ctx, 'accordion', 0, 0), gl = [], GJ = [], GC = [], GY = [];
        for (r = 0; r < rows; r++) {
            var row = rowsA[r], cnt = row.length, cy = (vert ? W : H) / 2 + (r - (rows - 1) / 2) * Ph * 1.3;
            var rh = HD + 'var CNT=' + cnt + ',CY=' + jzN(cy) + ',C0=' + jzN(AL) + '-CNT*pw/2;';
            // end covers
            if (jzP(ctx, 'covers', true)) {
                var gc = jzGrp(S, 'covers ' + (r + 1)), cw2 = Ph * 0.09;
                for (var e2 = 0; e2 < 2; e2++) {
                    var rc = ld1_rr(gc, 1, 1, 0), tallL = (st0 % 2) === 0, pe = e2 ? cnt : 0, tall = ((pe + st0) % 2) === 0;
                    jzSetExpr(rc.property('ADBE Vector Rect Position'), rh + 'var a=' + (e2 ? 'C0+CNT*pw+' + jzN(cw2 / 2) : 'C0-' + jzN(cw2 / 2)) + ';' + P2('a', 'CY'));
                    jzSetExpr(rc.property('ADBE Vector Rect Size'), rh + 'var hq=' + (tall ? 'PH*(1+KP*sa)' : 'hmn') + '*1.16;' + P2(jzN(cw2), 'hq'));
                }
                jzAddFill(gc, covC); ld1_gOp(gc, rh + '100*oc(time/0.3)*K');
            }
            for (j = 0; j < cnt; j++) {
                var par = (j + st0) % 2, dirW = par === 0 ? 1 : -1, col = par === 0 ? lit : dim;   // par 0: tall edge on the left
                var g = jzGrp(S, 'panel ' + (r + 1) + '.' + (j + 1)), ph2 = rh + 'var a0=C0+' + j + '*pw,a1=a0+pw,xt=' + (par === 0 ? 'a0' : 'a1') + ';';
                var gcr = ld1_sub(g, 'crease'), rcr = ld1_rr(gcr, 1, 1, 0);
                jzSetExpr(rcr.property('ADBE Vector Rect Position'), ph2 + P2('a0', 'CY'));
                jzSetExpr(rcr.property('ADBE Vector Rect Size'), ph2 + P2(jzN(lw), (par === 0 ? 'hmn+2*dd' : 'hmn')));
                jzAddFill(gcr, jzMixHex(col, DK, 0.4));
                for (var w2 = 0; w2 < 2; w2++) {
                    var gw = ld1_sub(g, w2 ? 'wedge bottom' : 'wedge top');
                    jzAddPath(gw, vert ? [[0, 0], [0, 100], [(w2 ? 100 : -100), 0]] : [[0, 0], [100, 0], [0, (w2 ? 100 : -100)]], true);
                    ld1_gPos(gw, ph2 + 'var b=CY' + (w2 ? '+' : '-') + 'hmn/2;' + P2('xt', 'b'));
                    ld1_gSc(gw, ph2 + P2(dirW + '*pw', 'dd+0.5'));
                }
                var rp = ld1_rr(g, 1, 1, 0);
                jzSetExpr(rp.property('ADBE Vector Rect Position'), ph2 + P2('(a0+a1)/2', 'CY'));
                jzSetExpr(rp.property('ADBE Vector Rect Size'), ph2 + P2('pw+0.6', 'hmn+1'));
                jzAddFill(g, col);
                ld1_gOp(g, ph2 + '100*cl((time-' + jzN(j * 0.03) + ')/0.2)*K');
                if (row[j] !== ' ') { gl.push(row[j]); GJ.push(j); GC.push(cnt); GY.push(cy); }
            }
            // shadow under the strip
            var gs = jzGrp(S, 'shadow ' + (r + 1)), rs = ld1_rr(gs, 1, 1, 0);
            jzSetExpr(rs.property('ADBE Vector Rect Position'), rh + P2(jzN(AL + Ph * 0.06), 'CY+PH*(1+KP*sa)/2+' + jzN(Ph * 0.045)));
            jzSetExpr(rs.property('ADBE Vector Rect Size'), rh + P2('CNT*pw', jzN(Ph * 0.05)));
            jzAddFill(gs, DK); ld1_gOp(gs, rh + '30*K');
        }
        // the lyric: one glyph layer, glyph j sits on the middle of panel j, squashed along the strip by cos(fold)
        var size = Math.min(Pw * 0.66, Ph * 0.62);
        var G = ld1_G(ctx, gl, { font: font, size: size, color: tc, name: jzFlat(c.text), cx: W / 2, cy: H / 2 });
        ld1_GX(ctx, G, 'JZ Panel', HD + 'var GJ=' + ld1_arr(GJ) + ',GC=' + ld1_arr(GC) + ',GY=' + ld1_arr(GY) + ';',
            'var al2=' + jzN(AL) + '+(GJ[i]-GC[i]/2+0.5)*pw;' + (vert ? 'x=GY[i];y=al2;sy=ca' : 'x=al2;y=GY[i];sx=ca') + ';a=cl((time-GJ[i]*0.03)/0.06)', 'psa');
        ld1_anim(ctx, G.L, { mi: ld1_mi(ctx, 0.12), noHold: ld1_plateHold(ctx) });
        var tw = per * Pw * cA, th = (rows - 1) * Ph * 1.3 + Ph;
        return vert ? ld1_box(W / 2 - th / 2, H / 2 - tw / 2, W / 2 + th / 2, H / 2 + tw / 2) : ld1_box(W / 2 - tw / 2, H / 2 - th / 2, W / 2 + tw / 2, H / 2 + th / 2);
    }
});

/* ================================================================== 5 flag \u2014 \u306F\u305F\u3081\u304F\u65D7 */
// the cloth is sampled in the expressions (N strips); every strip is two triangles of one union fill
var LD1_FLAG_N = 12;
ld1_reg('flag', {
    plan: function (rng, cut, st) {
        return {
            font: rng.pick(jzFontsOf(st, ['display', 'serif'])), mode: rng.pick(['unfurl', 'unfurl', 'raise']), cloth: rng.pick(['accent', 'ink', 'accent']), trim: rng.pick(['none', 'bands', 'edge']),
            tail: rng.pick(['rect', 'rect', 'swallow']), wind: rng.range(0.7, 1.15), side: rng.pick([1, 1, -1])
        };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), i, q;
        var side = jzP(ctx, 'side', 1) < 0 ? -1 : 1, t0 = jzFlat(c.text), n = jzCount(t0);
        if (!n) return null;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), mode = jzP(ctx, 'mode', 'unfurl'), trim = jzP(ctx, 'trim', 'none'), sw = jzP(ctx, 'tail', 'rect') === 'swallow', wind = jzP(ctx, 'wind', 0.9);
        var text = ld1_brk(t0, port ? 5 : n <= 5 ? 5 : Math.max(4, Math.ceil(n / 2)));
        var Wf = W * (port ? 0.78 : 0.62), Hf = Math.min(H * (port ? 0.34 : 0.52), Wf * 0.64), poleX = side > 0 ? W / 2 - Wf / 2 - W * 0.03 : W / 2 + Wf / 2 + W * 0.03;
        var col = ld1_plateCol(sc, jzP(ctx, 'cloth', 'accent') === 'ink' ? [sc.ink, sc.accent] : [sc.accent, sc.ink]), tc = ld1_onCol(sc, col);
        var trimC = jzContrast(sc.fg, col) > 1.5 ? (col === sc.accent ? ld1_onCol(sc, col) : sc.accent) : sc.accent;
        var N = LD1_FLAG_N, mot = ld1_mot(ctx), un = mode === 'unfurl' ? 1 : 0, ra = mode === 'raise' ? 1 : 0;
        var HD = ld1_H(ctx) + 'var WF=' + jzN(Wf) + ',HF=' + jzN(Hf) + ',PX=' + jzN(poleX) + ',SI=' + side + ',NN=' + N + ',OM=' + jzN(3.2 * wind) + ';' +
            'var unf=' + (un ? 'oc(cl((time-0.05)/0.75))' : '1') + ',rai=' + (ra ? 'oc(cl(time/0.8))' : '1') + ',low=' + (ra ? 'ic(PO)' : '0') + ',furl=' + (un ? 'ic(PO)' : '0') + ';' +
            'var Y0=' + jzN(H / 2 - Hf / 2 - H * 0.03) + '+(1-rai)*' + jzN(H * 0.55) + '+low*' + jzN(H * 0.6) + ',ext=Math.max(0.001,unf*(1-furl));' +
            'var AMP=HF*0.1*' + jzN(wind * (0.5 + 0.7 * mot)) + '*(1+(1-unf)*1.2),DZ=WF*1.3;' +
            'function zf(x){return AMP*Math.pow(Math.max(0,x),0.85)*Math.sin(2*Math.PI*1.05*x-OM*time)+AMP*0.35*x*Math.sin(2*Math.PI*1.3*x-OM*0.7*time+1.3);}' +
            'function samp(){var S=[],xa=0;for(var q=0;q<=NN;q++){var uu=q/NN*ext,z=zf(uu),dz=(zf(uu+0.01)-zf(uu-0.01))/0.02/WF;if(q>0)xa+=(WF*ext/NN)/Math.sqrt(1+dz*dz);' +
            'var hs=1+z/DZ,dr=uu*uu*HF*0.05-z*0.12;S.push([PX+SI*xa,Y0+dr-(hs-1)*HF/2,Y0+dr+HF*hs-(hs-1)*HF/2,dz,hs]);}return S;}' +
            'function at(S,uu){var f=cl(uu/ext)*NN,k=Math.min(NN-1,Math.floor(f)),t=f-k,a=S[k],b=S[k+1];return [a[0]+(b[0]-a[0])*t,a[1]+(b[1]-a[1])*t,a[2]+(b[2]-a[2])*t,a[3]+(b[3]-a[3])*t,a[4]+(b[4]-a[4])*t];}' +
            'function vy(p,v){return p[1]+(p[2]-p[1])*v;}\nvar S=samp();';
        var pa = ra ? 'oc(time/0.3)*(1-ic((PO-0.5)/0.5))' : 'oc(time/0.3)*K', show = '(unf*K>0.001&&Y0<' + jzN(H * 1.2) + '?100:0)';
        // pole + finial (behind the cloth)
        var pw = Math.max(3 * ctx.u, u * 0.011), ptop = H / 2 - Hf / 2 - H * 0.03 - Hf * 0.3;
        var PL = jzShapeLayer(ctx, 'flag pole', 0, 0), gp = jzGrp(PL, 'finial'); jzAddEllipse(gp, pw * 3, pw * 3, poleX, ptop); jzAddFill(gp, sc.accent);
        var gp2 = jzGrp(PL, 'pole'); ld1_rr(gp2, pw, H * 1.2, 0, poleX, ptop + H * 0.6); jzAddFill(gp2, sc.sub);
        ld1_opx(ctx, PL, pa);
        // cloth, trims, fold shading
        var CS = jzShapeLayer(ctx, 'flag cloth', 0, 0), qn = sw ? N - 2 : N;
        function band(name, v0, v1, colr, q0, q1) {
            var g = jzGrp(CS, name);
            for (var qq = q0; qq < q1; qq++) ld1_quad(g, 'strip ' + (qq + 1), HD, 'var a=S[' + qq + '],b=S[' + (qq + 1) + '];var Q=[a[0],vy(a,' + v0 + '),b[0],vy(b,' + v0 + '),b[0],vy(b,' + v1 + '),a[0],vy(a,' + v1 + ')]');
            if (colr) jzAddFill(g, colr);
            return g;
        }
        if (trim === 'edge') { var ge = band('edge trim', -0.01, 1.01, trimC, 0, 1); }
        if (trim === 'bands') { band('band top', 0.08, 0.15, trimC, 0, N); band('band bottom', 0.85, 0.92, trimC, 0, N); }
        var gsh = jzGrp(CS, 'folds');
        for (q = 0; q < qn; q++) {
            var gq = ld1_sub(gsh, 'fold ' + (q + 1)), vv = 'var v=Math.max(-0.4,Math.min(0.2,-(S[' + q + '][3]+S[' + (q + 1) + '][3])/2*SI*0.9));';
            ld1_quad(gq, 'q', HD, 'var a=S[' + q + '],b=S[' + (q + 1) + '];var Q=[a[0],a[1],b[0],b[1],b[0],b[2],a[0],a[2]]');
            var ff = jzAddFill(gq, ld1_dark(sc));
            jzSetExpr(ff.property('ADBE Vector Fill Color'), HD + vv + 'v<0?' + ld1_col(ld1_dark(sc)) + ':' + ld1_col(ld1_light(sc)));
            ld1_gOp(gq, HD + vv + 'Math.abs(v)<0.02?0:Math.abs(v)*80');
        }
        var gcl = band('cloth', 0, 1, null, 0, qn);
        if (sw) {       // swallowtail: fan of triangles round the notch point
            var nb = 'var p10=S[' + (N - 2) + '],p11=S[' + (N - 1) + '],p12=S[' + N + '],mx=p10[0]+(p11[0]-p10[0])*0.32,my=(vy(p10,0.5)+vy(p11,0.5))/2;';
            ld1_tri(gcl, 'fork 1', HD, nb + 'var T=[mx,my,p10[0],p10[1],p10[0],p10[2]]');
            ld1_tri(gcl, 'fork 2', HD, nb + 'var T=[mx,my,p10[0],p10[1],p11[0],p11[1]]');
            ld1_tri(gcl, 'fork 3', HD, nb + 'var T=[mx,my,p11[0],p11[1],p12[0],p12[1]]');
            ld1_tri(gcl, 'fork 4', HD, nb + 'var T=[mx,my,p10[0],p10[2],p11[0],p11[2]]');
            ld1_tri(gcl, 'fork 5', HD, nb + 'var T=[mx,my,p11[0],p11[2],p12[0],p12[2]]');
        }
        jzAddFill(gcl, col);
        jzSetExpr(jzXf(CS, 'ADBE Opacity'), HD + show + '*K');
        // rope rings
        var RS = jzShapeLayer(ctx, 'flag rings', 0, 0);
        for (i = 0; i < 2; i++) {
            var gr = jzGrp(RS, 'ring ' + (i + 1)); jzAddEllipse(gr, pw * 1.8, pw * 1.8); jzAddStroke(gr, sc.sub, Math.max(1 * ctx.u, pw * 0.3));
            ld1_gPos(gr, HD + '[' + jzN(poleX + side * pw * 0.2) + ',Y0' + (i ? '+HF-' : '+') + jzN(Hf * 0.04) + ']');
        }
        jzSetExpr(jzXf(RS, 'ADBE Opacity'), HD + show + '*K');
        // the lyric printed on the cloth
        var size = Math.min(ld1_fit(text, Wf * (sw ? 0.66 : 0.78), Hf * (trim === 'bands' ? 0.58 : 0.68), 0.04, 1.15), Hf * 0.42);
        var lay = ld1_layout(text, size, 0.04, 1.15), gl = [], UU = [], VV = [], uC = (sw ? 0.46 : 0.52) + (trim === 'edge' ? 0.03 : 0);
        for (i = 0; i < lay.length; i++) { if (lay[i].sp) continue; gl.push(lay[i].ch); UU.push(uC + side * lay[i].x / Wf); VV.push(0.5 + lay[i].y / Hf); }
        var G = ld1_G(ctx, gl, { font: font, size: size, color: tc, name: t0, cx: W / 2, cy: H / 2 });
        ld1_GX(ctx, G, 'JZ Cloth', HD + 'var UU=' + ld1_arr(UU) + ',VV=' + ld1_arr(VV) + ';',
            'var uu=UU[i],p=at(S,uu),pa=at(S,uu+0.01),pb=at(S,uu-0.01);x=p[0];y=vy(p,VV[i]);' +
            'var sl=((pa[1]+pa[2])-(pb[1]+pb[2]))/2/(Math.abs(pa[0]-pb[0])+0.001);r=Math.atan(sl*SI)*180/Math.PI;sx=1/Math.sqrt(1+p[3]*p[3]);sy=p[4];a=uu>ext?0:1', 'prsa');
        ld1_anim(ctx, G.L, { mi: ld1_mi(ctx, 0.1), noHold: ld1_plateHold(ctx) });
        var x0 = Math.min(poleX, poleX + side * Wf), y0 = H / 2 - Hf / 2 - H * 0.03;
        return ld1_box(x0, y0, x0 + Wf, y0 + Hf);
    }
});

/* ================================================================== 6 ribbon \u2014 \u30EA\u30DC\u30F3 */
// the band is sampled along its arc length in the expressions; segments = quads (two triangles) in one union fill per side
ld1_reg('ribbon', {
    plan: function (rng, cut, st) {
        return {
            font: rng.pick(jzFontsOf(st, ['display', 'serif'])), amp: rng.range(0.05, 0.1), freq: rng.range(0.55, 0.9), ph: rng.range(0, 6), col: rng.pick(['accent', 'accent', 'ink']),
            reveal: rng.pick(['center', 'left']), tilt: rng.range(-6, 6)
        };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), i, j, q;
        var t0 = jzFlat(c.text), n = jzCount(t0);
        if (!n) return null;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), left = jzP(ctx, 'reveal', 'center') === 'left';
        var rowsT = n > (port ? 6 : 10) ? ld1_chunksK(t0, n > 12 && port ? 3 : 2) : [t0], nR = rowsT.length;
        var pc = ld1_plateCol(sc, jzP(ctx, 'col', 'accent') === 'ink' ? [sc.ink, sc.accent] : [sc.accent, sc.ink]), back = ld1_shade(sc, pc, -0.42), tc = ld1_onCol(sc, pc);
        var x0 = W * 0.09, x1 = W * 0.91, LL = x1 - x0, size = u * 0.16, DK = ld1_dark(sc);
        for (i = 0; i < nR; i++) size = Math.min(size, LL * 0.58 / Math.max(1, ld1_lineW(rowsT[i], 0) * 1.05));
        size = Math.min(size, H * 0.62 / (nR * 2.3));
        var bw = size * 0.72, mot = ld1_mot(ctx);
        // f breakpoints: the twist crosses zero at f = 0.085 / 0.915 (so no segment straddles the flip)
        var FB = [0, 0.04, 0.085], NF = 16;
        for (i = 1; i <= NF; i++) FB.push(0.085 + (0.83 * i) / NF);
        FB.push(0.96, 1);
        var HD0 = ld1_H(ctx) + 'var X0=' + jzN(x0) + ',LL=' + jzN(LL) + ',BW=' + jzN(bw) + ',FR=' + jzN(jzP(ctx, 'freq', 0.7)) + ';' +
            'function tau(f){var d=Math.abs(f-0.5);return d<0.33?1:Math.cos(Math.PI*(d-0.33)/0.17);}' +
            'function tab(){var X=[],Y=[],A=[0];for(var q=0;q<=48;q++){var x=X0+LL*q/48;X.push(x);Y.push(pth(x));if(q)A.push(A[q-1]+Math.sqrt((X[q]-X[q-1])*(X[q]-X[q-1])+(Y[q]-Y[q-1])*(Y[q]-Y[q-1])));}return [X,Y,A];}' +
            'function atS(T,s){var X=T[0],Y=T[1],A=T[2];s=Math.max(0,Math.min(A[48],s));var lo=0,hi=48;while(hi-lo>1){var m=Math.floor((lo+hi)/2);if(A[m]<=s)lo=m;else hi=m;}' +
            'var f=(s-A[lo])/Math.max(1e-6,A[hi]-A[lo]);return [X[lo]+(X[hi]-X[lo])*f,Y[lo]+(Y[hi]-Y[lo])*f,Math.atan2(Y[hi]-Y[lo],X[hi]-X[lo])];}' +
            'function edge(T,f,sg){var p=atS(T,f*T[2][48]),t=tau(f)*sg*BW;return [p[0]-Math.sin(p[2])*t,p[1]+Math.cos(p[2])*t];}';
        function rowHead(ri) {
            var cy = H / 2 + (ri - (nR - 1) / 2) * size * 2.35, tl = Math.tan(jzP(ctx, 'tilt', 0) * Math.PI / 180) * (ri % 2 ? -1 : 1);
            return HD0 + 'var CYr=' + jzN(cy) + ',AMP=' + jzN(H * jzP(ctx, 'amp', 0.07) * (nR > 1 ? 0.55 : 1)) + '*(1+0.12*Math.sin(time*1.1+' + ri + ')),PHS=' + jzN(jzP(ctx, 'ph', 0) + ri * 2.1) + '+time*' + jzN(0.9 * mot * 0.6) + ';' +
                'var pth=function(x){return CYr+AMP*Math.sin(2*Math.PI*FR*(x-X0)/LL+PHS)+(x-' + jzN(W / 2) + ')*' + jzN(tl) + ';};' +
                'var e=iocu((time-' + jzN(ri * 0.12) + ')/0.75)*(1-ic(PO)),w0=' + (left ? '0' : '0.5-e/2') + ',w1=' + (left ? 'e' : '0.5+e/2') + ';var TB=tab();\n';
        }
        var RS = jzShapeLayer(ctx, 'ribbon', 0, 0), shadeG = [], tailsG = [];
        var gl = [], OF = [], RI = [], TS = [];
        for (var ri = 0; ri < nR; ri++) {
            var HD = rowHead(ri);
            jzGrp(RS, 'front ' + (ri + 1)); jzGrp(RS, 'back ' + (ri + 1)); jzGrp(RS, 'twist shade ' + (ri + 1));
            // fetch the three groups after all were added (each addition invalidates the older sibling references in AE)
            var gF = ld1_rg(RS, 'front ' + (ri + 1)), gB = ld1_rg(RS, 'back ' + (ri + 1)), gT = ld1_rg(RS, 'twist shade ' + (ri + 1));
            for (q = 0; q < FB.length - 1; q++) {
                var fa = FB[q], fb = FB[q + 1], fm = (fa + fb) / 2, dm = Math.abs(fm - 0.5), tm = dm < 0.33 ? 1 : Math.cos(Math.PI * (dm - 0.33) / 0.17);
                var body = 'var fa=Math.max(' + jzN(fa) + ',w0),fb=Math.min(' + jzN(fb) + ',w1);if(fb<fa)fb=fa;var A1=edge(TB,fa,1),B1=edge(TB,fb,1),B2=edge(TB,fb,-1),A2=edge(TB,fa,-1);var Q=[A1[0],A1[1],B1[0],B1[1],B2[0],B2[1],A2[0],A2[1]]';
                ld1_quad(tm >= 0 ? gF : gB, 'seg ' + (q + 1), HD, body);
                if (Math.abs(tm) < 0.97) { var gs = ld1_sub(gT, 'shade ' + (q + 1)); ld1_quad(gs, 'q', HD, body); jzAddFill(gs, DK); ld1_gOp(gs, jzN((1 - Math.abs(tm)) * 40)); }
            }
            jzAddFill(gF, pc); jzAddFill(gB, back);
            // notched tails
            var gt = jzGrp(RS, 'tails ' + (ri + 1));
            for (var en = 0; en < 2; en++) {
                var fE = en ? 1 : 0, dn = en ? -1 : 1;
                var tb = 'var p=atS(TB,' + fE + '*TB[2][48]),a=edge(TB,' + fE + ',1),b=edge(TB,' + fE + ',-1),ex=-Math.cos(p[2])*BW*1.3*' + dn + ',ey=-Math.sin(p[2])*BW*1.3*' + dn + ',k=(' + fE + '>=w0&&' + fE + '<=w1)?1:0;' +
                    'ex*=k;ey*=k;var ax=a[0],ay=a[1],bx=b[0],by=b[1];if(!k){ax=bx=p[0];ay=by=p[1];}';
                ld1_tri(gt, 'tail ' + (en + 1) + 'a', HD, tb + 'var T=[ax,ay,ax+ex,ay+ey,p[0]+ex*0.45,p[1]+ey*0.45]');
                ld1_tri(gt, 'tail ' + (en + 1) + 'b', HD, tb + 'var T=[ax,ay,p[0]+ex*0.45,p[1]+ey*0.45,bx,by]');
                ld1_tri(gt, 'tail ' + (en + 1) + 'c', HD, tb + 'var T=[bx,by,p[0]+ex*0.45,p[1]+ey*0.45,bx+ex,by+ey]');
            }
            jzAddFill(gt, ld1_shade(sc, back, -0.12));
            // lyric glyphs along the flat middle (arc-length offsets from the centre)
            var row = ld1_row(jzChars(rowsT[ri]), size, 1.05, 0);
            for (j = 0; j < row.length; j++) { if (row[j].ch === ' ') continue; gl.push(row[j].ch); OF.push(row[j].x); RI.push(ri); }
        }
        // shadow: a copy of the band, dark and offset
        var SH = null;
        try { SH = RS.duplicate(); } catch (eD) { SH = null; }
        if (SH) {
            SH.name = 'ribbon shadow'; SH.moveAfter(RS);
            var sh = size * 0.1;
            jzXf(SH, 'ADBE Position').setValue([sh, sh * 1.4]);
            var rv = SH.property('ADBE Root Vectors Group');
            for (i = rv.numProperties; i >= 1; i--) { var nm = rv.property(i).name; if (/^(twist|tails)/.test(nm)) rv.property(i).remove(); }
            for (i = 1; i <= rv.numProperties; i++) {
                var cg = rv.property(i).property('ADBE Vectors Group');
                for (j = 1; j <= cg.numProperties; j++) if (cg.property(j).matchName === 'ADBE Vector Graphic - Fill') cg.property(j).property('ADBE Vector Fill Color').setValue(jzHex(DK));
            }
            jzXf(SH, 'ADBE Opacity').setValue(22);
        }
        // one glyph layer for all rows: each glyph finds its row's curve (same maths per row)
        var hd = ld1_H(ctx) + 'var RI=' + ld1_arr(RI) + ',OF=' + ld1_arr(OF) + ';', rb = '';
        for (ri = 0; ri < nR; ri++) rb += (ri ? 'else ' : '') + 'if(RI[i]===' + ri + '){' + rowHead(ri).replace(ld1_H(ctx), '').replace(/\n/g, '') + 'R=[TB,w0,w1];}';
        var G = ld1_G(ctx, gl, { font: font, size: size, color: tc, name: t0, cx: W / 2, cy: H / 2 });
        ld1_GX(ctx, G, 'JZ Ribbon', hd, 'var R=null;' + rb + 'var TB=R[0],s=TB[2][48]/2+OF[i],f=s/TB[2][48],p=atS(TB,s);x=p[0];y=p[1];r=p[2]*180/Math.PI;sy=tau(f);a=(f>=R[1]&&f<=R[2])?1:0', 'prsa');
        ld1_anim(ctx, G.L, { mi: 0, noHold: ld1_plateHold(ctx) });
        var hH = (nR - 1) * size * 2.35 / 2 + H * jzP(ctx, 'amp', 0.07) + bw;
        return ld1_box(x0, H / 2 - hH, x1, H / 2 + hH);
    }
});

/* ================================================================== 7 pendulum \u2014 \u632F\u308A\u5B50 */
// a line segment as a thin rect (rotated group): body defines X0, Y0, X1, Y1 (comp px)
function ld1_seg(parent, name, head, body, lw) {
    var g = ld1_sub(parent, name), r = jzAddRect(g, 100, lw, 0, 50, 0), pre = head + body + ';var DX=X1-X0,DY=Y1-Y0,LN=Math.sqrt(DX*DX+DY*DY);';
    ld1_gPos(g, pre + '[X0,Y0]'); ld1_gRot(g, pre + 'Math.atan2(DY,DX)*180/Math.PI');
    jzSetExpr(r.property('ADBE Vector Rect Size'), pre + '[Math.max(0.01,LN),' + jzN(lw) + ']');
    jzSetExpr(r.property('ADBE Vector Rect Position'), pre + '[Math.max(0.01,LN)/2,0]');
    return g;
}
ld1_reg('pendulum', {
    plan: function (rng, cut, st) {
        var n = cut.n, port = cut.H > cut.W * 1.08, spaced = /\s/.test(jzTrim(String(cut.text))), opts = [];
        if (n <= (port ? 5 : 8) && !spaced) opts.push('cradle', 'cradle');
        if (n <= 12 && !spaced) opts.push('fan', port ? 'fan' : 'chunks');
        if (n >= 4) opts.push('chunks');
        var mode = rng.pick(opts.length ? opts : ['chunks']);
        return { mode: mode, chunks: ld1_chunksK(cut.text, jzClamp(Math.ceil(n / 4), 2, 4)), font: rng.pick(jzFontsOf(st, ['display', 'serif'])), period: rng.range(1.05, 1.4), amp: rng.range(22, 30), phase: rng.range(0, 6), side: rng.pick([1, -1]) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), i, q;
        var all = jzChars(jzFlat(c.text)), chs = [];
        for (i = 0; i < all.length; i++) if (all[i] !== ' ') chs.push(all[i]);
        var n = chs.length;
        if (!n) return null;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), mode = jzP(ctx, 'mode', 'fan'), mot = ld1_mot(ctx), period = jzP(ctx, 'period', 1.2), side = jzP(ctx, 'side', 1) < 0 ? -1 : 1;
        if (mode === 'cradle' && n > (port ? 6 : 9)) mode = 'fan';
        if (mode === 'fan' && n > 13) mode = 'chunks';
        var lw = Math.max(1 * ctx.u, u * 0.0015), pc = ld1_plateCol(sc, [sc.ink, sc.fg]), tc = ld1_onCol(sc, pc), DK = ld1_dark(sc);
        var TH = ld1_H(ctx), fa = 'oc(time/0.45)*K', bb = null;
        if (mode === 'cradle') {
            var r = Math.min(W * 0.84 / (n * 2.02 + 1.2), H * 0.13, u * 0.12), L = Math.min(H * 0.42, r * 5.2), cx = W / 2, x0 = cx - (n - 1) * r;
            var py = H / 2 - L * 0.62, by = py + L, fw = (n - 1) * r + r * 2.6, fb = by + r + H * 0.06;
            var FR = jzShapeLayer(ctx, 'cradle frame', 0, 0), gf = jzGrp(FR, 'frame');
            jzAddPath(gf, [[cx - fw, fb], [cx - fw, py], [cx + fw, py], [cx + fw, fb]], false); jzAddStroke(gf, sc.sub, Math.max(2 * ctx.u, u * 0.006));
            var gbs = jzGrp(FR, 'base'); ld1_rr(gbs, fw * 2 + r * 1.4, r * 0.28, r * 0.1, cx, fb + r * 0.14); jzAddFill(gbs, sc.sub);
            ld1_opx(ctx, FR, fa);
            var A = jzP(ctx, 'amp', 26) * (0.6 + 0.5 * mot), w = Math.PI * 2 / period;
            var CH = TH + 'var W2=' + jzN(w) + ',AA=' + jzN(A) + '*Math.PI/180,LL=' + jzN(L) + ',PY=' + jzN(py) + ';var tau=time-0.75,sA=tau>0?-Math.cos(W2*tau):-1,lift=tau>0?1:iocu((time-0.25)/0.45),dec=1-0.25*cl(tau/6);' +
                'function th(i){if(' + n + '===1)return AA*dec*sA*lift;if(i===0)return AA*dec*Math.min(0,sA)*(tau>0?1:lift);if(i===' + (n - 1) + ')return AA*dec*Math.max(0,sA);return 0;}' +
                'function drp(i){return ob(cl((time-i*0.05)/0.35),1.4);}';
            var SL = jzShapeLayer(ctx, 'cradle strings', 0, 0), d = r * 0.85, gst = jzGrp(SL, 'strings');
            for (i = 0; i < n; i++) {
                var px = x0 + i * r * 2, bE = 'var t_=th(' + i + '),Lc=LL*drp(' + i + ');var X1=' + jzN(px) + '+Math.sin(t_)*Lc,Y1=PY+Math.cos(t_)*Lc;';
                ld1_seg(gst, 'string ' + (i + 1) + 'a', CH, bE + 'var X0=' + jzN(px - d) + ',Y0=PY', lw);
                ld1_seg(gst, 'string ' + (i + 1) + 'b', CH, bE + 'var X0=' + jzN(px + d) + ',Y0=PY', lw);
            }
            jzAddFill(gst, sc.sub);
            // impact sparks on the end balls
            if (n > 1) {
                for (var en = 0; en < 2; en++) {
                    var ie = en ? n - 1 : 0, sxp = x0 + ie * r * 2 + (en ? -r : r), dr = en ? -1 : 1, gk = jzGrp(SL, 'sparks ' + (en + 1));
                    for (q = -1; q <= 1; q++) { jzAddPath(gk, [[sxp + dr * r * 0.2, by + q * r * 0.5], [sxp + dr * r * 0.55, by + q * r * 0.9]], false); }
                    jzAddStroke(gk, sc.accent, Math.max(1.5 * ctx.u, r * 0.05));
                    ld1_gOp(gk, CH + 'var ph=((W2*tau)/Math.PI)%2,nr=Math.min(Math.abs(ph-0.5),Math.abs(ph-1.5));(tau>0&&nr<0.08&&' + (en ? '!' : '') + '(Math.abs(ph-0.5)<0.08))?100*(1-nr/0.08):0');
                }
            }
            ld1_opx(ctx, SL, '0.9*K');
            for (i = 0; i < n; i++) {
                var px2 = x0 + i * r * 2, B = jzShapeLayer(ctx, 'ball ' + (i + 1), px2, py);
                jzXf(B, 'ADBE Anchor Point').setValue([px2, py]);
                var gh = jzGrp(B, 'gloss'); jzAddEllipse(gh, r * 0.4, r * 0.4, px2 - r * 0.35, by - r * 0.38); jzAddFill(gh, ld1_light(sc)); ld1_gOp(gh, '35');
                var gb = jzGrp(B, 'ball'); jzAddEllipse(gb, r * 1.97, r * 1.97, px2, by); jzAddStroke(gb, jzMixHex(pc, DK, 0.4), lw); jzAddFill(gb, pc);
                jzSetExpr(jzXf(B, 'ADBE Rotate Z'), CH + '-th(' + i + ')*180/Math.PI');
                jzSetExpr(jzXf(B, 'ADBE Position'), CH + '[value[0],value[1]-LL*(1-drp(' + i + '))]');
                jzSetExpr(jzXf(B, 'ADBE Opacity'), CH + '(drp(' + i + ')<=0?0:100)*K');
                var T = ld1_T(ctx, chs[i], { font: font, size: r * 1.08, color: tc, x: px2, y: by, name: chs[i] });
                ld1_parent(T, B);
                ld1_anim(ctx, T, { mi: ld1_mi(ctx, 0.15 + i * 0.05), noHold: ld1_plateHold(ctx) });
                bb = jzUnion(bb, ld1_box(px2 - r, by - r, px2 + r, by + r));
            }
            return bb;
        }
        // single pivot: pendulums of different length (a pendulum wave)
        var items = [], k, cks = jzP(ctx, 'chunks', null);
        if (mode === 'fan') for (i = 0; i < n; i++) items.push(chs[i]);
        else { if (!cks || !cks.length) cks = ld1_chunksK(c.text, 3); for (i = 0; i < cks.length; i++) items.push(jzFlat(cks[i])); }
        k = items.length;
        var pX = W / 2, pY = H * 0.06, size, step, L0, plateW = [];
        if (mode === 'fan') { step = Math.min(H * 0.84 / (k + 1.2), W * 0.2); size = step * 0.84; L0 = step * 1.4; }
        else {
            var wMax = W * (port ? 0.8 : 0.56); size = u * 0.14;
            for (i = 0; i < k; i++) size = Math.min(size, ld1_fit(items[i], wMax, H * 0.8 / (k + 1) * 0.62, 0.03, 1.2));
            step = size * 1.75; L0 = Math.max(H * 0.9 - step * k, size * 2);
            for (i = 0; i < k; i++) plateW.push(ld1_lineW(items[i], 0.03) * size + size * 0.8);
        }
        var Lmax = L0 + step * (k - 1), A2 = (mode === 'fan' ? 7 : 5) * (0.5 + 0.7 * mot);
        var PV = jzShapeLayer(ctx, 'pivot', 0, 0), gv = jzGrp(PV, 'pin'); var rp = Math.max(3 * ctx.u, u * 0.008); jzAddEllipse(gv, rp * 2, rp * 2, pX, pY); jzAddFill(gv, sc.accent);
        var gbar = jzGrp(PV, 'bar'), bh = Math.max(3 * ctx.u, u * 0.005); ld1_rr(gbar, u * 0.12, bh, 0, pX, pY - Math.max(2 * ctx.u, u * 0.003) + bh / 2); jzAddFill(gbar, sc.sub);
        ld1_gSc(gbar, TH + '[value[0]*oc(time/0.45),value[1]]');
        ld1_opx(ctx, PV, fa);
        var arms = [];
        for (i = 0; i < k; i++) {
            var Li = L0 + step * i, Ti = period * 1.6 * Math.sqrt(Li / Lmax), col = i % 2 && jzContrast(sc.accent, sc.bg) > 1.6 ? sc.accent : pc;
            var AH = TH + 'var gr=oc((time-' + jzN(i * 0.04) + ')/0.4),LI=' + jzN(Li) + ';var th=' + jzN(A2 * side) + '*Math.cos(2*Math.PI*Math.max(0,time-0.2)/' + jzN(Ti) + ')*iocu((time-0.1)/0.6)+ic(PO)*30*' + jzN(side * (0.5 + i / k)) + ';';
            var AR = jzShapeLayer(ctx, 'pendulum ' + (i + 1), pX, pY);
            jzXf(AR, 'ADBE Anchor Point').setValue([pX, pY]);
            var off = mode === 'fan' ? size * 0.55 : size * 0.62;
            // bob
            var gB = jzGrp(AR, 'bob');
            if (mode === 'fan') jzAddEllipse(gB, size * 1.24, size * 1.24);
            else {
                var gHo = ld1_sub(gB, 'hole'), h2 = size * 1.24; jzAddEllipse(gHo, Math.max(4 * ctx.u, size * 0.12), Math.max(4 * ctx.u, size * 0.12), 0, -h2 / 2 + h2 * 0.12); jzAddFill(gHo, sc.bg);
                var gPl = ld1_sub(gB, 'plate'); ld1_rr(gPl, plateW[i], h2, h2 * 0.14); jzAddFill(gPl, col);
            }
            if (mode === 'fan') jzAddFill(gB, col);
            ld1_gPos(gB, AH + '[' + jzN(pX) + ',' + jzN(pY) + '+LI*gr]');
            // string (behind the bob)
            var gS = jzGrp(AR, 'string'), rS = jzAddRect(gS, lw, 10, 0);
            jzSetExpr(rS.property('ADBE Vector Rect Size'), AH + '[' + jzN(lw) + ',Math.max(0.01,LI*gr-' + jzN(off) + ')]');
            jzSetExpr(rS.property('ADBE Vector Rect Position'), AH + '[' + jzN(pX) + ',' + jzN(pY) + '+Math.max(0.01,LI*gr-' + jzN(off) + ')/2]');
            jzAddFill(gS, sc.sub); ld1_gOp(gS, '75');
            jzSetExpr(jzXf(AR, 'ADBE Rotate Z'), AH + '-th');
            jzSetExpr(jzXf(AR, 'ADBE Opacity'), AH + '(gr<=0?0:100)*K');
            arms.push({ L: AR, y: pY + Li, col: col, Li: Li, AH: AH });
        }
        // bobs in front of every string: move the string groups of all arms under the first arm layer
        for (i = 0; i < k; i++) {
            var it = arms[i], T2 = ld1_T(ctx, items[i], { font: font, size: mode === 'fan' ? size * 0.78 : size, color: ld1_onCol(sc, it.col), x: pX, y: it.y + (mode === 'fan' ? 0 : size * 0.06), track: 0.03, name: items[i] });
            ld1_parent(T2, it.L);
            jzAnimator(T2, 'JZ Grow', [['ADBE Text Position 3D', [0, -it.Li, 0]]], it.AH + '(1-gr)*100');
            ld1_anim(ctx, T2, { mi: ld1_mi(ctx, 0.12 + i * 0.05), noHold: ld1_plateHold(ctx) });
            var hw = mode === 'fan' ? size * 0.62 : plateW[i] / 2;
            bb = jzUnion(bb, ld1_box(pX - hw, it.y - size * 0.62, pX + hw, it.y + size * 0.62));
        }
        return bb;
    }
});

/* ================================================================== 8 pile \u2014 \u6587\u5B57\u306E\u5C71 */
ld1_reg('pile', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display'])), hf: rng.pick(jzFontsOf(st, ['body', 'display', 'serif'])), peak: rng.range(-0.12, 0.12), hgt: rng.range(0.26, 0.34), order: rng.pick(['ltr', 'random', 'random']), dust: rng.chance(0.7) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, s = c.seed, u = jzU(ctx), port = jzPortrait(ctx), i, j, ri;
        var t0 = jzFlat(c.text), n = jzCount(t0);
        if (!n) return null;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), hf = jzP(ctx, 'hf', jzBodyF(ctx));
        var rowsT = n > (port ? 5 : 8) ? ld1_chunksK(t0, port ? Math.ceil(n / 5) : 2) : [t0], nR = rowsT.length;
        var size = Math.min(u * 0.2, H * 0.5 / (nR + 0.6)), maxTot = 0;
        for (i = 0; i < nR; i++) size = Math.min(size, W * (port ? 0.86 : 0.74) / Math.max(1, ld1_lineW(rowsT[i], 0) * 1.02));
        for (i = 0; i < nR; i++) maxTot = Math.max(maxTot, ld1_lineW(rowsT[i], 0) * 1.02 * size);
        var cx = jzClamp(W * (0.5 + jzP(ctx, 'peak', 0) * (port ? 0.2 : 0.6)), W * 0.06 + maxTot / 2, W * 0.94 - maxTot / 2), sig = Math.max(W * 0.26, maxTot * 0.68);
        var yBase = H * 1.04, Hm = yBase - (H * (0.5 + jzP(ctx, 'hgt', 0.3) * 0.25) + nR * size * 0.49);
        function surf0(x) { return yBase - Hm / (1 + Math.pow((x - cx) / sig, 4)) + Math.sin(x / W * 17 + s % 7) * H * 0.006 + Math.sin(x / W * 31 + (s % 5)) * H * 0.004; }
        var TH = ld1_H(ctx) + 'var HM=' + jzN(Hm) + ';var rise=(1-oc(time/0.5))*HM*1.1+ic(PO)*HM*0.5;';
        // the mound + a heap of small glyphs in its top layers (both ride on "rise")
        var cell = jzClamp(u * 0.045, 16 * ctx.u, 60 * ctx.u), mound = [];
        for (i = 0; i <= 48; i++) { var x = -W * 0.02 + W * 1.04 * i / 48; mound.push([x, surf0(x) + cell * 1.2]); }
        mound.push([W * 1.02, H * 1.9], [-W * 0.02, H * 1.9]);
        var MS = jzShapeLayer(ctx, 'mound', 0, 0), gm = jzGrp(MS, 'mound'); jzAddPath(gm, mound, true); jzAddFill(gm, jzMixHex(sc.bg, sc.sub, 0.14));
        jzSetExpr(jzXf(MS, 'ADBE Position'), TH + '[value[0],value[1]+rise]'); ld1_opx(ctx, MS, 'K');
        var pool = [], own = jzChars(jzStrip((c.lineText || '') + c.text)), KA = jzChars(jzPool('hira'));
        for (i = 0; i < own.length; i++) if (!jzIsPunct(own[i]) && !jzIsLatin(own[i]) && '\u3041\u3043\u3045\u3047\u3049\u3063\u3083\u3085\u3087\u308E\u30A1\u30A3\u30A5\u30A7\u30A9\u30C3\u30E3\u30E5\u30E7\u30EE\u30FC'.indexOf(own[i]) < 0) pool.push(own[i]);
        for (i = 0; i < KA.length; i += 3) pool.push(KA[i]);
        var colsN = Math.ceil(W / cell) + 2, depth = Math.max(3, Math.min(5, Math.floor(200 / colsN))), hp = [], hch = [], hr = [], ha = [], hc = [];
        for (var gx = -1; gx < colsN - 1 && hp.length < 200; gx++) {
            var hx = (gx + 0.5) * cell, top = surf0(hx);
            for (var gy = 0; gy < depth && hp.length < 200; gy++) {
                var hy = top + (gy + 0.5) * cell * 0.82;
                if (hy > H + cell) break;
                hp.push([hx + (jzR(s, gx, gy, 3) * 2 - 1) * cell * 0.3, hy + (jzR(s, gx, gy, 4) * 2 - 1) * cell * 0.2]); hr.push((jzR(s, gx, gy, 5) * 2 - 1) * 70);
                hch.push(pool[jzHash(s, gx, gy, 6) % pool.length]); hc.push(jzR(s, gx, gy, 7) < 0.14); ha.push((0.35 + 0.5 * jzR(s, gx, gy, 8)) * (1 - gy / (depth + 1)));
            }
        }
        if (hp.length) {
            var HG = ld1_G(ctx, hch, { font: hf, size: cell * 0.8, color: sc.sub, name: 'heap', cx: W / 2, cy: H / 2 });
            ld1_place(HG, hp, 'JZ Heap Place'); jzCharRotations(HG.L, hr, 'JZ Heap Tilt'); jzCharColors(HG.L, sc.accent, hc, 'JZ Heap Accent');
            jzAnimator(HG.L, 'JZ Heap Fade', [['ADBE Text Opacity', 0]], 'var a=' + ld1_arr(ha) + ';(1-(a[textIndex-1]||0))*100');
            jzSetExpr(jzXf(HG.L, 'ADBE Position'), TH + '[value[0],value[1]+rise]'); ld1_opx(ctx, HG.L, 'K');
        }
        // lyric glyphs fall onto the heap (lower rows first) and settle along its slope
        var fd = 0.34, gap = jzClamp(c.dur * 0.3 / n, 0.03, 0.09), order = [], when = {};
        for (ri = nR - 1; ri >= 0; ri--) {
            var ch0 = jzChars(rowsT[ri]), ord = [];
            for (i = 0; i < ch0.length; i++) ord.push(i);
            if (jzP(ctx, 'order', 'random') === 'random') ord.sort(function (a, b) { return jzR(s, ri, a, 21) - jzR(s, ri, b, 21); });
            for (i = 0; i < ord.length; i++) order.push([ri, ord[i]]);
        }
        for (i = 0; i < order.length; i++) when[order[i][0] * 100 + order[i][1]] = 0.08 + i * gap;
        var gl = [], GX = [], T1 = [], S0 = [], TL = [], R0 = [], Y0 = [], LY = [], AC = [], bb = null;
        for (ri = 0; ri < nR; ri++) {
            var lay = nR - 1 - ri, row = ld1_row(jzChars(rowsT[ri]), size, 1.02, cx);
            for (i = 0; i < row.length; i++) {
                if (row[i].ch === ' ') continue;
                var gxp = row[i].x, slope = Math.atan2(surf0(gxp + size * 0.5) - surf0(gxp - size * 0.5), size);
                gl.push(row[i].ch); GX.push(gxp); T1.push(when[ri * 100 + i]); S0.push(surf0(gxp) - size * (0.47 + lay * 0.98));
                TL.push(slope / Math.PI * 180 * 0.7 + (jzR(s, ri, i, 9) * 2 - 1) * 6); R0.push((jzR(s, ri, i, 11) * 2 - 1) * 50); Y0.push(-size * 1.2 - jzR(s, ri, i, 10) * H * 0.2); AC.push(jzR(s, ri, i, 12) < 0.12);
                bb = jzUnion(bb, ld1_box(gxp - size / 2, surf0(gxp) - size * (0.97 + lay * 0.98), gxp + size / 2, surf0(gxp) - size * lay * 0.98));
            }
        }
        var FH = TH + 'var T1=' + ld1_arr(T1) + ',S0=' + ld1_arr(S0) + ',GX=' + ld1_arr(GX) + ';function fall(i){var f=(time-T1[i])/' + fd + ',rest=S0[i]+rise;if(f<0)return [0,0,0,1];' +
            'if(f<1){var e=iq(f);return [1,f,rest,1];}var tau=(f-1)*' + fd + ';return [1,f,rest-' + jzN(size * 0.14) + '*Math.abs(Math.sin(tau*16))*Math.exp(-tau/0.09),1-0.16*Math.exp(-tau/0.05)];}';
        var G = ld1_G(ctx, gl, { font: font, size: size, color: sc.fg, name: t0, cx: cx, cy: H / 2 });
        ld1_GX(ctx, G, 'JZ Fall', FH + 'var TL=' + ld1_arr(TL) + ',R0=' + ld1_arr(R0) + ',Y0=' + ld1_arr(Y0) + ';',
            'var F=fall(i);x=GX[i];if(F[1]<1){var e=iq(F[1]);y=Y0[i]+(F[2]-Y0[i])*e;r=R0[i]+(TL[i]-R0[i])*e;}else{y=F[2];r=TL[i];}sy=F[3];y+=' + jzN(size * 0.47) + '*(1-sy);a=F[0]', 'prsa');
        jzCharColors(G.L, sc.accent, AC, 'JZ Accent');
        ld1_anim(ctx, G.L, { mi: ld1_mi(ctx, 0.08) });
        // dust puffs when a glyph lands
        if (jzP(ctx, 'dust', true)) {
            var DS = jzShapeLayer(ctx, 'dust', 0, 0), dr = size * 0.035;
            for (i = 0; i < gl.length; i++) {
                var gd = jzGrp(DS, 'dust ' + (i + 1));
                for (var qd = 0; qd < 4; qd++) {
                    var dir = qd < 2 ? -1 : 1, sp = 0.6 + 0.6 * jzR(s, i, qd, 13), gq = ld1_sub(gd, 'puff'), dh = FH + 'var F=fall(' + i + '),tau=(F[1]-1)*' + fd + ';';
                    jzAddEllipse(gq, dr * 2, dr * 2); jzAddFill(gq, sc.sub);
                    ld1_gPos(gq, dh + '[GX[' + i + ']+' + dir + '*(' + jzN(size * 0.35) + '+tau*' + jzN(size * 1.6 * sp) + '),F[2]+' + jzN(size * 0.45) + '-tau*' + jzN(size) + '*(0.9-tau*2.2)*' + jzN(sp) + ']');
                    ld1_gSc(gq, dh + 'var k=cl(1-tau/0.35);[100*k,100*k]');
                    ld1_gOp(gq, dh + '(F[1]>=1&&tau<0.35)?100*(1-tau/0.35):0');
                }
            }
            ld1_opx(ctx, DS, 'K');
        }
        return bb;
    }
});

/* ================================================================== 9 blocks \u2014 \u7A4D\u307F\u6728 */
// every block is a shape layer (front, top, side, inset) pivoting at its bottom centre; its glyph is parented to it
ld1_reg('blocks', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display'])), deco: rng.chance(0.75), tilt: rng.chance(0.5), hop: rng.chance(0.7), side: rng.pick([1, -1]), colOff: rng.int(0, 5) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, s = c.seed, u = jzU(ctx), port = jzPortrait(ctx), i, j, r;
        var n = jzCount(c.text);
        if (!n) return null;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), side = jzP(ctx, 'side', 1) < 0 ? -1 : 1, colOff = jzP(ctx, 'colOff', 0);
        var rowsB = ld1_rowsOf(c.text, port ? 4 : n <= 6 ? 6 : Math.min(8, Math.ceil(n / 2))), rows = rowsB.length, per = 1;
        for (r = 0; r < rows; r++) per = Math.max(per, rowsB[r].length);
        var dk = 0.34, gk = 1.07, deco = !!jzP(ctx, 'deco', true) && (!port || n <= 3), extra = deco ? 2 : 0;
        var k = Math.min(W * 0.84 / (per * gk + extra * 0.95 + dk), H * 0.7 / (rows * 1.02 + dk + 0.2), u * 0.26);
        var dx = k * dk * 0.8 * side, dy = -k * dk * 0.62, floorY = H / 2 + (rows * k) / 2 + k * 0.1, cols = ld1_objCols(sc), lw = Math.max(1 * ctx.u, k * 0.012);
        var FL = jzShapeLayer(ctx, 'floor', 0, 0), gfl = jzGrp(FL, 'floor'); jzAddPath(gfl, [[W * 0.08, floorY], [W * 0.92, floorY]], false); jzAddStroke(gfl, sc.sub, Math.max(1 * ctx.u, u * 0.002));
        ld1_opx(ctx, FL, '0.6*oe(time/0.4)*K');
        var blk = [], byRow = [], gi = 0;
        for (r = 0; r < rows; r++) byRow.push([]);
        for (r = 0; r < rows; r++) for (j = 0; j < rowsB[r].length; j++) {
            if (rowsB[r][j] === ' ') continue;
            var b0 = { ch: rowsB[r][j], x: W / 2 + (j - (rowsB[r].length - 1) / 2) * k * gk - dx / 2, y: floorY - (rows - r - 0.5) * k * 1.02, r: r, order: (rows - 1 - r) * per + j, col: cols[(gi++ + colOff) % cols.length] };
            blk.push(b0); byRow[r].push(b0);
        }
        for (r = 0; r < rows - 1; r++) for (j = 0; j < byRow[r].length; j++) {
            var bq = byRow[r][j], sup = false;
            for (var z = 0; z < byRow[r + 1].length; z++) if (Math.abs(byRow[r + 1][z].x - bq.x) < k * 0.6) sup = true;
            if (sup) continue;
            var f2 = { ch: null, shape: 3, x: bq.x, y: floorY - (rows - r - 1.5) * k * 1.02, r: r + 1, order: (rows - 2 - r) * per - 1, col: jzMixHex(bq.col, sc.bg, 0.35) };
            blk.push(f2); byRow[r + 1].push(f2);
        }
        if (deco) {
            var cntB = Math.min(per, n), xl = W / 2 - (cntB - 1) / 2 * k * gk - dx / 2 - k * 1.05, xr = W / 2 + (cntB - 1) / 2 * k * gk - dx / 2 + k * 1.05;
            blk.push({ ch: null, shape: 0, x: xl, y: floorY - k * 0.5 * 0.8, sz: 0.8, order: -1, col: cols[(colOff + 2) % cols.length] });
            blk.push({ ch: null, shape: 1, x: xr, y: floorY - k * 0.5 * 0.8, sz: 0.8, order: n + 1, col: cols[(colOff + 3) % cols.length] });
            if (rows === 1) blk.push({ ch: null, shape: 2, x: xr + (jzR(s, 1) * 2 - 1) * k * 0.05, y: floorY - k * 0.8 - k * 0.3, sz: 0.6, order: n + 2, col: cols[(colOff + 1) % cols.length] });
        }
        for (i = 0; i < blk.length; i++) blk[i].bi = i;
        var gap = jzClamp(c.dur * 0.3 / (n + 2), 0.035, 0.1), fd = 0.3, hopOn = !!jzP(ctx, 'hop', true), tilt = !!jzP(ctx, 'tilt', false);
        var TH = ld1_H(ctx) + 'var HOP0=' + jzN(0.3 + (n + 2) * gap + fd) + ';function hop(bi){if(!' + (hopOn ? 1 : 0) + '||time<HOP0)return 0;var kk=Math.floor(time/0.65),f=(time-kk*0.65)/0.32;' +
            'if(Math.floor(hh(kk*4+' + (s % 997) + ')*' + n + ')!==bi||f>=1)return 0;return Math.sin(Math.PI*cl(f));}';
        var sorted = blk.slice(0);
        sorted.sort(function (a, b) { return (b.y - a.y) || (side > 0 ? a.x - b.x : b.x - a.x); });
        var bb = null;
        for (i = 0; i < sorted.length; i++) {
            var b = sorted[i], kz = k * (b.sz || 1), hk = kz / 2, ddx = dx * (b.sz || 1), ddy = dy * (b.sz || 1), t1 = 0.06 + (b.order + 1) * gap;
            var rot = tilt && b.r === 0 && rows > 1 ? (jzR(s, b.bi, 3) * 2 - 1) * 3 : 0, top = ld1_shade(sc, b.col, 0.28), sd = ld1_shade(sc, b.col, -0.3), cy0 = b.y;
            var BL = jzShapeLayer(ctx, b.ch ? 'block ' + b.ch : 'block', b.x, cy0 + hk);
            jzXf(BL, 'ADBE Anchor Point').setValue([b.x, cy0 + hk]);
            var X = b.x, Y = cy0;
            if (b.ch == null && b.shape !== 3) {
                var tcol = jzMixHex(b.col, ld1_onCol(sc, b.col), 0.8), qq = kz * 0.26, gd = jzGrp(BL, 'mark');
                if (b.shape === 0) jzAddEllipse(gd, qq * 2, qq * 2, X, Y);
                else if (b.shape === 1) jzAddPath(gd, [[X, Y - qq * 1.1], [X + qq * 1.05, Y + qq * 0.8], [X - qq * 1.05, Y + qq * 0.8]], true);
                else jzAddPath(gd, [[X, Y - qq], [X + qq, Y], [X, Y + qq], [X - qq, Y]], true);
                jzAddFill(gd, tcol);
            }
            var gin = jzGrp(BL, 'inset'); ld1_rr(gin, kz * 0.84, kz * 0.84, kz * 0.06, X, Y); jzAddStroke(gin, jzMixHex(b.col, ld1_onCol(sc, b.col), 0.35), lw * 1.5);
            var gfr = jzGrp(BL, 'front'); ld1_rr(gfr, kz, kz, 0, X, Y); jzAddFill(gfr, b.col);
            var gsd = jzGrp(BL, 'side'), sx0 = side > 0 ? X + hk : X - hk;
            jzAddPath(gsd, [[sx0, Y - hk], [sx0 + ddx, Y - hk + ddy], [sx0 + ddx, Y + hk + ddy], [sx0, Y + hk]], true); jzAddFill(gsd, sd);
            var gtp = jzGrp(BL, 'top');
            jzAddPath(gtp, [[X - hk, Y - hk], [X + hk, Y - hk], [X + hk + ddx, Y - hk + ddy], [X - hk + ddx, Y - hk + ddy]], true); jzAddFill(gtp, top);
            if (rot) jzXf(BL, 'ADBE Rotate Z').setValue(rot);
            var BH = TH + 'var f=(time-' + jzN(t1) + ')/' + fd + ',y=' + jzN(cy0) + ',sq=1;if(f<1)y=' + jzN(-kz * 2 - (H - cy0) * 0.2) + '+(' + jzN(cy0) + '-(' + jzN(-kz * 2 - (H - cy0) * 0.2) + '))*iq(f);' +
                'else{var tau=(f-1)*' + fd + ';y-=' + jzN(kz * 0.1) + '*Math.abs(Math.sin(tau*14))*Math.exp(-tau/0.1);sq=1-0.1*Math.exp(-tau/0.05);}' +
                'y-=' + jzN(kz * 0.16) + '*hop(' + b.bi + ');y+=iq(cl(PO*1.4-' + jzN((b.order + 1) / (n + 3) * 0.4) + '))*' + jzN(H * 0.7) + ';';
            jzSetExpr(jzXf(BL, 'ADBE Position'), BH + '[value[0],y+' + jzN(hk) + ']');
            jzSetExpr(jzXf(BL, 'ADBE Scale'), BH + '[value[0],value[1]*sq]');
            jzSetExpr(jzXf(BL, 'ADBE Opacity'), BH + '(f<0?0:100)*K');
            if (b.ch != null) {
                var T = ld1_T(ctx, b.ch, { font: font, size: kz * 0.6, color: ld1_onCol(sc, b.col), x: X, y: Y, name: b.ch });
                ld1_parent(T, BL);
                ld1_anim(ctx, T, { mi: ld1_mi(ctx, t1), noHold: ld1_plateHold(ctx) });
                bb = jzUnion(bb, ld1_box(X - hk, Y - hk, X + hk, Y + hk));
            }
        }
        return bb;
    }
});

// per-glyph colours from a palette: one Fill (and optionally Stroke) Color animator per distinct colour
function ld1_palAnim(L, cols, name, stroke) {
    var uniq = [], i, j;
    for (i = 0; i < cols.length; i++) if (jzIndexOf(uniq, cols[i]) < 0) uniq.push(cols[i]);
    for (j = 0; j < uniq.length; j++) {
        var f = [];
        for (i = 0; i < cols.length; i++) f.push(cols[i] === uniq[j] ? 1 : 0);
        var props = [['ADBE Text Fill Color', jzHex(uniq[j])]];
        if (stroke) props.push(['ADBE Text Stroke Color', jzHex(uniq[j])]);
        jzAnimator(L, name + ' ' + (j + 1), props, 'var a=' + ld1_arr(f) + ';(a[textIndex-1]||0)*100');
    }
}

/* ================================================================== 10 balloons \u2014 \u6587\u5B57\u98A8\u8239 */
ld1_reg('balloons', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display'])), tie: rng.pick(['bunch', 'bunch', 'free']), arc: rng.range(0.02, 0.06), colOff: rng.int(0, 4), mono: rng.chance(0.3) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, s = c.seed, u = jzU(ctx), port = jzPortrait(ctx), i, j, ri;
        var t0 = jzFlat(c.text), n = jzCount(t0);
        if (!n) return null;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), bunch = jzP(ctx, 'tie', 'bunch') === 'bunch', arc = jzP(ctx, 'arc', 0.04), colOff = jzP(ctx, 'colOff', 0), mono = !!jzP(ctx, 'mono', false);
        var rowsT = n > (port ? 4 : 7) ? ld1_chunksK(t0, port ? Math.ceil(n / 4) : 2) : [t0], nR = rowsT.length, mot = ld1_mot(ctx);
        var size = Math.min(u * 0.26, H * 0.56 / (nR * 1.25 + 0.3));
        for (i = 0; i < nR; i++) size = Math.min(size, W * 0.84 / Math.max(1, ld1_lineW(rowsT[i], 0) * 1.2));
        var cols = ld1_objCols(sc), gx = W / 2, gy = H * 0.93, gl = [], XS = [], YS = [], PH = [], T1 = [], R3 = [], FL = [], CO = [], DC = [], bb = null;
        for (ri = 0; ri < nR; ri++) {
            var row = ld1_row(jzChars(rowsT[ri]), size, 1.2, W / 2), yRow = H * 0.4 + (ri - (nR - 1) / 2) * size * 1.3;
            for (j = 0; j < row.length; j++) {
                if (row[j].ch === ' ') continue;
                var q = gl.length, u0 = row.tot > 0 ? (row[j].x - W / 2) / (row.tot / 2) : 0, col = mono ? cols[colOff % cols.length] : cols[(q + colOff) % cols.length];
                gl.push(row[j].ch); XS.push(row[j].x); YS.push(yRow - arc * H * (1 - u0 * u0)); PH.push(jzR(s, q, 1) * Math.PI * 2);
                T1.push(0.05 + q * jzClamp(c.dur * 0.3 / n, 0.03, 0.08)); R3.push((jzR(s, q, 3) * 2 - 1) * 4); FL.push(0.8 + 0.4 * jzR(s, q, 2));
                CO.push(col); DC.push(ld1_shade(sc, col, -0.38));
                bb = jzUnion(bb, ld1_box(row[j].x - size * 0.5, YS[q] - size * 0.55, row[j].x + size * 0.5, YS[q] + size * 0.55));
            }
        }
        var BH = ld1_H(ctx) + 'var XS=' + ld1_arr(XS) + ',YS=' + ld1_arr(YS) + ',PH=' + ld1_arr(PH) + ',T1=' + ld1_arr(T1) + ',R3=' + ld1_arr(R3) + ',FL=' + ld1_arr(FL) + ',SZ=' + jzN(size) + ',MO=' + jzN(0.4 + mot) + ';' +
            'var fly=ic(PO)*' + jzN(H * 0.9) + ';function bal(i){var e=cl((time-T1[i])/0.45),ph=PH[i];return [XS[i]+Math.sin(time*0.9+ph)*SZ*0.03,YS[i]+Math.sin(time*1.4+ph)*SZ*0.05*MO+(1-oc(e))*' + jzN(H * 0.35) + '-fly*FL[i],' +
            'Math.sin(time*0.8+ph*1.3)*5*MO+R3[i],e];}\n';
        // strings (knot -> gather point, sagging: two segments) + knots
        var SL = jzShapeLayer(ctx, 'balloon strings', 0, 0), lw = Math.max(1 * ctx.u, u * 0.0016);
        jzGrp(SL, 'strings'); jzGrp(SL, 'knots');
        var gs = ld1_rg(SL, 'strings'), gk = ld1_rg(SL, 'knots');      // fetched after both exist (AE invalidates older sibling refs)
        for (i = 0; i < gl.length; i++) {
            var kb = 'var B=bal(' + i + '),rr=B[2]*Math.PI/180,bx=B[0]-Math.sin(rr)*SZ*0.58,by=B[1]+Math.cos(rr)*SZ*0.58,' +
                (bunch ? 'ex=' + jzN(gx) + '+(B[0]-' + jzN(gx) + ')*0.06,ey=' + jzN(gy) + '-fly' : 'ex=bx+Math.sin(time*1.1+' + i + ')*SZ*0.2,ey=Math.min(' + jzN(H * 1.05) + ',by+' + jzN(H * 0.3) + ')') +
                ',mx=(bx+ex)/2+Math.sin(time*2+' + i + ')*SZ*0.05,my=(by+ey)/2+SZ*0.25,k=B[3]>0?1:0;';
            var s1 = ld1_seg(gs, 'string ' + (i + 1) + 'a', BH, kb + 'var X0=bx,Y0=by,X1=mx,Y1=my', lw);
            ld1_gOp(s1, BH + kb + 'k*100');
            var s2 = ld1_seg(gs, 'string ' + (i + 1) + 'b', BH, kb + 'var X0=mx,Y0=my,X1=ex,Y1=ey', lw);
            ld1_gOp(s2, BH + kb + 'k*100');
            var gkn = ld1_sub(gk, 'knot ' + (i + 1)); jzAddPath(gkn, [[-size * 0.05, size * 0.07], [size * 0.05, size * 0.07], [0, -size * 0.02]], true); jzAddFill(gkn, DC[i]);
            ld1_gPos(gkn, BH + kb + '[bx,by]'); ld1_gOp(gkn, BH + kb + 'k*100');
        }
        jzAddFill(gs, sc.sub);
        jzSetExpr(jzXf(SL, 'ADBE Opacity'), BH + '85*K');
        if (bunch) {
            var KS = jzShapeLayer(ctx, 'balloon gather', 0, 0), gg = jzGrp(KS, 'knot'); jzAddEllipse(gg, size * 0.1, size * 0.1); jzAddFill(gg, sc.sub);
            ld1_gPos(gg, BH + '[' + jzN(gx) + ',' + jzN(gy) + '-fly]'); ld1_opx(ctx, KS, 'oc((time-0.2)/0.3)*K');
        }
        var pb = 'var B=bal(i),q=B[3]<=0?0:ob(B[3],2.2);x=B[0];y=B[1];r=B[2];sx=sy=0.3+0.7*q;a=B[3]>0?1:0';
        // puffy rim (dark, fat stroke under) + body (the lyric) + gloss dots
        var RG = ld1_G(ctx, gl, { font: font, size: size, color: '#000000', stroke: size * 0.13, strokeColor: '#000000', name: 'balloon rims', cx: W / 2, cy: H / 2 });
        ld1_GX(ctx, RG, 'JZ Balloon', BH, pb, 'prsa'); ld1_palAnim(RG.L, DC, 'JZ Rim Colour', true); ld1_opx(ctx, RG.L, 'K');
        var G = ld1_G(ctx, gl, { font: font, size: size, color: CO[0], stroke: size * 0.065, strokeColor: CO[0], name: t0, cx: W / 2, cy: H / 2 });
        ld1_GX(ctx, G, 'JZ Balloon', BH, pb, 'prsa'); ld1_palAnim(G.L, CO, 'JZ Balloon Colour', true);
        ld1_anim(ctx, G.L, { mi: ld1_mi(ctx, T1[0]), treat: false });
        var GS = jzShapeLayer(ctx, 'balloon gloss', 0, 0), gd = jzGrp(GS, 'dots');
        for (i = 0; i < gl.length; i++) {
            var gq = ld1_sub(gd, 'dot ' + (i + 1)); jzAddEllipse(gq, size * 0.09, size * 0.09);
            ld1_gPos(gq, BH + 'var B=bal(' + i + '),sz=' + jzN(size) + '*(0.3+0.7*(B[3]<=0?0:ob(B[3],2.2)));[B[0]-sz*0.22,B[1]-sz*0.26]');
            ld1_gOp(gq, BH + 'var B=bal(' + i + ');70*B[3]');
        }
        jzAddFill(gd, ld1_light(sc)); ld1_opx(ctx, GS, 'K');
        return bb;
    }
});

/* ================================================================== 11 magnets \u2014 \u30DE\u30B0\u30CD\u30C3\u30C8\u6587\u5B57 */
var LD1_DECOY = 'ABCDEFGHKMNPRSTXYZ0123456789\u2605\u2665\u266A?!';
ld1_reg('magnets', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display'])), df: rng.pick(jzFontsOf(st, ['display', 'body'])), decoys: rng.int(6, 11), handle: rng.pick([1, -1]), colOff: rng.int(0, 4), tilt: rng.range(6, 12) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, s = c.seed, u = jzU(ctx), port = jzPortrait(ctx), i, j, ri;
        var chs0 = jzChars(jzFlat(c.text)), n = 0;
        for (i = 0; i < chs0.length; i++) if (chs0[i] !== ' ') n++;
        if (!n) return null;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), df = jzP(ctx, 'df', font), colOff = jzP(ctx, 'colOff', 0), tilt = jzP(ctx, 'tilt', 9), nd = jzP(ctx, 'decoys', 8);
        var mx = W * 0.04, my = H * 0.05, bw = W - mx * 2, bh = H - my * 2, rr = u * 0.03, DK = ld1_dark(sc);
        var board = jzMixHex(sc.bg, sc.fg, jzLum(sc.bg) < 0.5 ? 0.07 : 0.06), TH = ld1_H(ctx);
        // the board (fridge door) + handle
        var BD = jzShapeLayer(ctx, 'magnet board', 0, 0);
        var hx = jzP(ctx, 'handle', 1) > 0 ? mx + bw - u * 0.05 : mx + u * 0.035, hhh = bh * 0.34;
        var gh = jzGrp(BD, 'handle'); ld1_rr(gh, u * 0.016, hhh, u * 0.008, hx + u * 0.008, H / 2); jzAddFill(gh, jzMixHex(board, sc.fg, 0.2));
        var gb = jzGrp(BD, 'board'); ld1_rr(gb, bw, bh, rr, W / 2, H / 2); jzAddStroke(gb, jzMixHex(sc.bg, sc.fg, 0.16), Math.max(1 * ctx.u, u * 0.002)); jzAddFill(gb, board);
        jzSetExpr(jzXf(BD, 'ADBE Position'), TH + '[value[0],value[1]+(1-oc(time/0.45))*' + jzN(H * 0.04) + ']'); ld1_opx(ctx, BD, 'oc(time/0.45)*K');
        // lyric layout in rows
        var per = port ? 4 : 8, ft = jzFlat(c.text), rowsT = n > per ? ld1_chunksK(ft, Math.ceil(n / per)) : [ft], nR = rowsT.length, base = Math.min(u * 0.22, H * 0.56 / (nR * 1.12));
        function kOf(ch) { return jzIsKanji(ch) ? 1 : jzIsPunct(ch) ? 0.6 : '\u3041\u3043\u3045\u3047\u3049\u3063\u3083\u3085\u3087\u308E\u30A1\u30A3\u30A5\u30A7\u30A9\u30C3\u30E3\u30E5\u30E7\u30EE'.indexOf(ch) >= 0 ? 0.7 : 0.86; }
        for (ri = 0; ri < nR; ri++) { var a0 = 0, cc = jzChars(rowsT[ri]); for (j = 0; j < cc.length; j++) a0 += ld1_adv(cc[j]) * (jzIsKanji(cc[j]) ? 1 : 0.86) * 1.1; base = Math.min(base, bw * 0.8 / Math.max(1, a0)); }
        var cols = ld1_objCols(sc), gl = [], XS = [], YS = [], KS = [], RS = [], CO = [], T1 = [], bb = null, gap = jzClamp(c.dur * 0.32 / n, 0.04, 0.1);
        for (ri = 0; ri < nR; ri++) {
            var ch1 = jzChars(rowsT[ri]), ads = [], tot = 0;
            for (j = 0; j < ch1.length; j++) { ads.push(ld1_adv(ch1[j]) * base * kOf(ch1[j]) * 1.1); tot += ads[j]; }
            var x = W / 2 - tot / 2, y = H / 2 + (ri - (nR - 1) / 2) * base * 1.14;
            for (j = 0; j < ch1.length; j++) {
                var cxp = x + ads[j] / 2; x += ads[j];
                if (ch1[j] === ' ') continue;
                var q = gl.length;
                gl.push(ch1[j]); XS.push(cxp + (jzR(s, q, 1) * 2 - 1) * base * 0.04); YS.push(y + (jzR(s, q, 2) * 2 - 1) * base * 0.08); KS.push(kOf(ch1[j]));
                RS.push((jzR(s, q, 3) * 2 - 1) * tilt); CO.push(cols[(q * 7 + colOff) % cols.length]); T1.push(0.18 + q * gap);
                bb = jzUnion(bb, ld1_box(XS[q] - base * KS[q] * 0.5, YS[q] - base * KS[q] * 0.55, XS[q] + base * KS[q] * 0.5, YS[q] + base * KS[q] * 0.55));
            }
        }
        // decoy magnets round the edges (never over the lyric band)
        var bandY0 = H / 2 - nR * base * 0.62, bandY1 = H / 2 + nR * base * 0.62, dg = [], DX = [], DY = [], DS = [], DR = [], DCo = [], DT = [], half = Math.ceil(nd / 2);
        for (var d = 0; d < nd; d++) {
            var topS = d % 2 === 0, slot = Math.floor(d / 2), sz = Math.min(base * (0.36 + 0.22 * jzR(s, d, 14)), u * 0.09);
            var dy2 = topS ? jzLerp(my + bh * 0.08 + sz * 0.6, Math.max(my + bh * 0.1 + sz * 0.6, bandY0 - sz * 0.8), jzR(s, d, 11)) : jzLerp(Math.min(my + bh * 0.9 - sz * 0.6, bandY1 + sz * 0.8), my + bh * 0.92 - sz * 0.6, jzR(s, d, 11));
            var dch = LD1_DECOY.charAt(jzHash(s, d, 13) % LD1_DECOY.length);
            dg.push(dch); DX.push(mx + bw * (0.1 + 0.8 * (slot + 0.2 + 0.6 * jzR(s, d, 12)) / half)); DY.push(dy2); DS.push(sz); DR.push((jzR(s, d, 15) * 2 - 1) * 25); DCo.push(cols[(d + 2 + colOff) % cols.length]); DT.push(0.05 + d * 0.04);
        }
        if (dg.length) {
            var dmax = 0; for (i = 0; i < DS.length; i++) dmax = Math.max(dmax, DS[i]);
            var DH = TH + 'var DX=' + ld1_arr(DX) + ',DY=' + ld1_arr(DY) + ',DS=' + ld1_arr(DS) + ',DR=' + ld1_arr(DR) + ',DT=' + ld1_arr(DT) + ';';
            var db = 'var e=cl((time-DT[i])/0.2),k=1+0.25*(1-oc(e));sx=sy=DS[i]/' + jzN(dmax) + '*k;r=DR[i];a=e;';
            var DSh = ld1_G(ctx, dg, { font: df, size: dmax, color: DK, stroke: dmax * 0.08, strokeColor: DK, name: 'decoy shadows', cx: W / 2, cy: H / 2 });
            ld1_GX(ctx, DSh, 'JZ Decoy', DH, db + 'x=DX[i]+DS[i]*0.05;y=DY[i]+DS[i]*0.07', 'prsa'); ld1_opx(ctx, DSh.L, '0.28*K');
            var DB = ld1_G(ctx, dg, { font: df, size: dmax, color: DCo[0], stroke: dmax * 0.08, strokeColor: DCo[0], name: 'decoys', cx: W / 2, cy: H / 2 });
            ld1_GX(ctx, DB, 'JZ Decoy', DH, db + 'x=DX[i];y=DY[i]', 'prsa'); ld1_palAnim(DB.L, DCo, 'JZ Decoy Colour', true); ld1_opx(ctx, DB.L, '0.85*K');
        }
        // the lyric magnets: snap onto the board (shadow shrinks as they land)
        var MH = TH + 'var XS=' + ld1_arr(XS) + ',YS=' + ld1_arr(YS) + ',KS=' + ld1_arr(KS) + ',RS=' + ld1_arr(RS) + ',T1=' + ld1_arr(T1) + ',BS=' + jzN(base) + ';' +
            'function mg(i){var f=(time-T1[i])/0.16;if(f<0)return [0,0,0];var sn=f<1?1.28-0.28*iq(f):1-0.05*Math.exp(-(f-1)*0.16/0.05)*Math.cos((f-1)*3);return [1,sn,f<1?1-f:0];}';
        var SH = ld1_G(ctx, gl, { font: font, size: base, color: DK, stroke: base * 0.08, strokeColor: DK, name: 'magnet shadows', cx: W / 2, cy: H / 2 });
        ld1_GX(ctx, SH, 'JZ Magnet', MH, 'var m=mg(i);x=XS[i]+BS*KS[i]*(0.04+m[2]*0.12);y=YS[i]+BS*KS[i]*(0.06+m[2]*0.18);r=RS[i];sx=sy=KS[i]*m[1];a=m[0]*(0.3-m[2]*0.15)', 'prsa');
        ld1_opx(ctx, SH.L, 'K');
        var G = ld1_G(ctx, gl, { font: font, size: base, color: CO[0], stroke: base * 0.08, strokeColor: CO[0], name: ft, cx: W / 2, cy: H / 2 });
        ld1_GX(ctx, G, 'JZ Magnet', MH, 'var m=mg(i);x=XS[i];y=YS[i];r=RS[i];sx=sy=KS[i]*m[1];a=m[0]', 'prsa');
        ld1_palAnim(G.L, CO, 'JZ Magnet Colour', true);
        ld1_anim(ctx, G.L, { mi: ld1_mi(ctx, 0.18), treat: false });
        return bb;
    }
});

/* ================================================================== 12 tiles \u2014 \u6587\u5B57\u30BF\u30A4\u30EB */
var LD1_LPTS = { A: 1, B: 3, C: 3, D: 2, E: 1, F: 4, G: 2, H: 4, I: 1, J: 8, K: 5, L: 1, M: 3, N: 1, O: 1, P: 3, Q: 10, R: 1, S: 1, T: 1, U: 1, V: 4, W: 4, X: 8, Y: 4, Z: 10 };
function ld1_tilePts(ch) {
    var up = ch.toUpperCase();
    if (LD1_LPTS[up]) return LD1_LPTS[up];
    var sid = jzHash(ch, 0, 0, 0);
    if (jzIsKanji(ch)) return 3 + (sid % 8);
    if ('\u3041\u3043\u3045\u3047\u3049\u3063\u3083\u3085\u3087\u308E\u30A1\u30A3\u30A5\u30A7\u30A9\u30C3\u30E3\u30E5\u30E7\u30EE'.indexOf(ch) >= 0) return 5;
    if (jzIsPunct(ch)) return 0;
    if (jzIsKata(ch)) return 2 + (sid % 2);
    return 1 + (sid % 3);
}
ld1_reg('tiles', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), from: rng.pick(['right', 'right', 'drop']), rack: rng.pick(['accent', 'ink', 'sub']), score: rng.chance(0.75) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, s = c.seed, u = jzU(ctx), port = jzPortrait(ctx), i, j, r;
        var n = jzCount(c.text);
        if (!n) return null;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), drop = jzP(ctx, 'from', 'right') === 'drop', rk = jzP(ctx, 'rack', 'accent');
        var rowsT = ld1_rowsOf(c.text, port ? 5 : 9), rows = rowsT.length, per = 1;
        for (r = 0; r < rows; r++) per = Math.max(per, rowsT[r].length);
        var k = Math.min(W * 0.84 / (per * 1.08 + 0.6), H * 0.62 / (rows * 1.75), u * 0.2), DK = ld1_dark(sc);
        var tileC = jzMixHex(ld1_light(sc), sc.accent, 0.1), tileT = DK, edge = jzMixHex(tileC, DK, 0.3), hi = jzMixHex(tileC, '#FFFFFF', 0.35);
        var rackC = ld1_plateCol(sc, rk === 'accent' ? [sc.accent, sc.ink] : rk === 'sub' ? [sc.sub, sc.ink] : [sc.ink, sc.accent]);
        var gap = jzClamp(c.dur * 0.36 / n, 0.05, 0.12), fd = 0.28, lean = -k * 0.05;
        var TH = ld1_H(ctx) + 'var ra=oc(time/0.4)*K;';
        var tl = [], TX = [], TY = [], TT = [], TJ = [], TC = [], PT = [], total = 0, bb = null, rowY = [], rowW = [];
        for (r = 0; r < rows; r++) {
            var cnt = rowsT[r].length, ry = H / 2 + (r - (rows - 1) / 2) * k * 1.75;
            rowY.push(ry); rowW.push(cnt * k * 1.08 + k * 0.5);
            for (j = 0; j < cnt; j++) {
                if (rowsT[r][j] === ' ') continue;
                var x = W / 2 + (j - (cnt - 1) / 2) * k * 1.08, pts = ld1_tilePts(rowsT[r][j]);
                tl.push(rowsT[r][j]); TX.push(x); TY.push(ry); TT.push(0.12 + (tl.length - 1) * gap); TJ.push(j / Math.max(1, cnt)); PT.push(pts); total += pts;
                bb = jzUnion(bb, ld1_box(x - k / 2, ry - k / 2 + lean, x + k / 2, ry + k / 2 + lean));
            }
        }
        var nT = tl.length, RR = [];
        for (i = 0; i < nT; i++) RR.push((jzR(s, i, 2) * 2 - 1) * 30);
        // tile motion: t = [x, y, rot, visible]
        var MH = TH + 'var TX=' + ld1_arr(TX) + ',TY=' + ld1_arr(TY) + ',TT=' + ld1_arr(TT) + ',TJ=' + ld1_arr(TJ) + ',RR=' + ld1_arr(RR) + ',KK=' + jzN(k) + ';' +
            'function tile(i){var f=cl((time-TT[i])/' + fd + '),x=TX[i],y=TY[i],r=0;if(time<TT[i])return [x,y,0,0];' +
            (drop ? 'y=TY[i]-' + jzN(H * 0.5) + '*(1-iq(f));r=(1-f)*RR[i];if(f>=1){var d=time-TT[i]-' + fd + ';y-=KK*0.06*Math.abs(Math.sin(d*18))*Math.exp(-d/0.08);}'
                : 'var e=oc(f);x=' + jzN(W + k) + '+(TX[i]-' + jzN(W + k) + ')*e;r=(1-e)*10;y=TY[i]-Math.sin(Math.PI*f)*KK*0.15;') +
            'var po=ic(cl(PO*1.4-TJ[i]*0.4));x-=po*(TX[i]+KK*3);return [x,y+' + jzN(lean) + ',r,1];}';
        // rack back slot + shadow (behind the tiles)
        var RB = jzShapeLayer(ctx, 'rack back', 0, 0);
        for (r = 0; r < rows; r++) {
            var ryy = rowY[r] + k * 0.42, rw = rowW[r];
            var gsl = jzGrp(RB, 'slot ' + (r + 1)); ld1_rr(gsl, rw, k * 0.14, 0, 0, ryy - k * 0.05); jzAddFill(gsl, ld1_shade(sc, rackC, -0.3));
            var gsh = jzGrp(RB, 'shadow ' + (r + 1)); jzAddPath(gsh, [[-rw / 2 + k * 0.06, ryy + k * 0.34], [rw / 2 + k * 0.06, ryy + k * 0.34], [rw / 2 + k * 0.2, ryy + k * 0.42], [-rw / 2 + k * 0.2, ryy + k * 0.42]], true);
            jzAddFill(gsh, DK); ld1_gOp(gsh, '25');
        }
        jzXf(RB, 'ADBE Position').setValue([W / 2, 0]);
        jzSetExpr(jzXf(RB, 'ADBE Scale'), TH + '[value[0]*(0.3+0.7*ra),value[1]]'); jzSetExpr(jzXf(RB, 'ADBE Opacity'), TH + 'value*ra');
        // tiles (one shape layer, a group per tile)
        var TS = jzShapeLayer(ctx, 'tiles', 0, 0);
        for (i = 0; i < nT; i++) {
            var g = jzGrp(TS, 'tile ' + (i + 1));
            var gl2 = ld1_sub(g, 'shine'); jzAddPath(gl2, [[-k / 2 + k * 0.12, -k / 2 + k * 0.05], [k / 2 - k * 0.12, -k / 2 + k * 0.05]], false); jzAddStroke(gl2, hi, Math.max(1 * ctx.u, k * 0.025)); ld1_gOp(gl2, '70');
            var gt = ld1_sub(g, 'tile'); ld1_rr(gt, k, k, k * 0.1); jzAddFill(gt, tileC);
            var ge = ld1_sub(g, 'edge'); ld1_rr(ge, k, k, k * 0.1, k * 0.03, k * 0.06); jzAddFill(ge, edge);
            ld1_gPos(g, MH + 'var t=tile(' + i + ');[t[0],t[1]]'); ld1_gRot(g, MH + 'tile(' + i + ')[2]'); ld1_gOp(g, MH + 'tile(' + i + ')[3]*100');
        }
        ld1_opx(ctx, TS, 'K');
        // point values (one glyph per character of every value, riding on its tile)
        var pg = [], PI_ = [], PO_ = [];
        for (i = 0; i < nT; i++) { if (PT[i] <= 0) continue; var ps = jzChars(String(PT[i])); for (j = 0; j < ps.length; j++) { pg.push(ps[j]); PI_.push(i); PO_.push((j - (ps.length - 1) / 2) * k * 0.105); } }
        if (pg.length) {
            var PG = ld1_G(ctx, pg, { font: jzMonoF(ctx), size: k * 0.17, color: tileT, name: 'tile points', cx: W / 2, cy: H / 2 });
            ld1_GX(ctx, PG, 'JZ Points', MH + 'var PI_=' + ld1_arr(PI_) + ',PO_=' + ld1_arr(PO_) + ';',
                'var t=tile(PI_[i]),an=t[2]*Math.PI/180,ox=KK*0.35+PO_[i],oy=KK*0.33;x=t[0]+ox*Math.cos(an)-oy*Math.sin(an);y=t[1]+ox*Math.sin(an)+oy*Math.cos(an);r=t[2];a=t[3]*0.85', 'pra');
            ld1_opx(ctx, PG.L, 'K');
        }
        // the lyric glyphs ride on their tiles
        var G = ld1_G(ctx, tl, { font: font, size: k * 0.6, color: tileT, name: jzFlat(c.text), cx: W / 2, cy: H / 2 });
        ld1_GX(ctx, G, 'JZ Tile', MH, 'var t=tile(i),an=t[2]*Math.PI/180,ox=-KK*0.06,oy=-KK*0.05;x=t[0]+ox*Math.cos(an)-oy*Math.sin(an);y=t[1]+ox*Math.sin(an)+oy*Math.cos(an);r=t[2];a=t[3]', 'pra');
        ld1_anim(ctx, G.L, { mi: ld1_mi(ctx, 0.12), noHold: ld1_plateHold(ctx) });
        // rack front lip (over the tile bottoms)
        var RF = jzShapeLayer(ctx, 'rack front', 0, 0);
        for (r = 0; r < rows; r++) {
            var ry2 = rowY[r] + k * 0.42, rw2 = rowW[r];
            var glp = jzGrp(RF, 'lip ' + (r + 1));
            var gln = ld1_sub(glp, 'shine'); jzAddPath(gln, [[-rw2 / 2, ry2 + k * 0.02], [rw2 / 2, ry2 + k * 0.02]], false); jzAddStroke(gln, ld1_shade(sc, rackC, 0.3), Math.max(1 * ctx.u, k * 0.02));
            var glf = ld1_sub(glp, 'lip'); jzAddPath(glf, [[-rw2 / 2, ry2 + k * 0.02], [rw2 / 2, ry2 + k * 0.02], [rw2 / 2 - k * 0.06, ry2 + k * 0.34], [-rw2 / 2 + k * 0.06, ry2 + k * 0.34]], true); jzAddFill(glf, rackC);
        }
        jzXf(RF, 'ADBE Position').setValue([W / 2, 0]);
        jzSetExpr(jzXf(RF, 'ADBE Scale'), TH + '[value[0]*(0.3+0.7*ra),value[1]]'); jzSetExpr(jzXf(RF, 'ADBE Opacity'), TH + 'value*ra');
        // score: counts the values of the tiles that have landed
        if (jzP(ctx, 'score', true)) {
            var fs = Math.max(jzSmallSize(ctx), k * 0.16), lastY = rowY[rows - 1] + k * 0.76, hw = Math.min(per, n) * k * 0.54 + k * 0.25, ly = lastY + fs * 1.6;
            var SL = jzText(ctx, 'SCORE', { font: jzMonoF(ctx), size: fs, track: 0.3, align: 'left', x: W / 2 - hw, y: ly, color: sc.sub, name: 'tiles score label' });
            ld1_opx(ctx, SL, 'oc((time-0.2)/0.3)*K');
            var SV = jzText(ctx, '000', { font: jzMonoF(ctx), size: fs * 1.5, track: 0.1, align: 'right', x: W / 2 + hw, y: ly, color: sc.fg, name: 'tiles score' });
            var sh = MH + 'var PT=' + ld1_arr(PT) + ';var sc_=0;for(var i=0;i<PT.length;i++)if(time>=TT[i]+' + fd + ')sc_+=PT[i];';
            try { SV.property('ADBE Text Properties').property('ADBE Text Document').expression = sh + 'var s=String(sc_);while(s.length<3)s="0"+s;s'; } catch (e0) { jzWarn('score: ' + e0.toString()); }
            jzAnimator(SV, 'JZ Score Done', [['ADBE Text Fill Color', jzHex(sc.accent)]], sh + (total > 0 ? '(sc_>=' + total + ')?100:0' : '0'));
            ld1_opx(ctx, SV, 'oc((time-0.2)/0.3)*K');
        }
        return bb;
    }
});

// shape repeater: copies of everything above it in the group, offset by [dx, dy]
function ld1_rep(g, copies, dx, dy) {
    var r = jzVecs(g).addProperty('ADBE Vector Filter - Repeater');
    r.property('ADBE Vector Repeater Copies').setValue(copies);
    r.property('ADBE Vector Repeater Transform').property('ADBE Vector Repeater Position').setValue([dx, dy]);
    return r;
}
// alpha track matte: L shows only inside M (M directly above L). AE 23+ links mattes with setTrackMatte
function ld1_matte(L, M) {
    M.moveBefore(L);
    var ok = false;
    if (typeof L.setTrackMatte === 'function') { try { L.setTrackMatte(M, TrackMatteType.ALPHA); ok = true; } catch (e) { ok = false; } }
    if (!ok) { try { L.trackMatteType = TrackMatteType.ALPHA; } catch (e2) { jzWarn('matte: ' + e2.toString()); } }
}
function ld1_perim(pts) { var L = 0; for (var i = 0; i < pts.length; i++) { var a = pts[i], b = pts[(i + 1) % pts.length]; L += Math.sqrt((b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1])); } return L; }

/* ================================================================== 13 bulbs \u2014 \u96FB\u7403\u30B5\u30A4\u30F3 */
// the bulbs are dashed strokes (zero-length dashes with round caps = dots) along the sign's inset outline:
// sockets, dim-lit and lit patterns are separate strokes; the chase moves the lit stroke's dash offset
ld1_reg('bulbs', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display'])), shape: rng.pick(['board', 'board', 'arrow', 'double']), chase: rng.pick(['chase', 'chase', 'alt', 'wave']), dir: rng.pick([1, -1]), sub: rng.chance(0.6) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), i;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), shp = jzP(ctx, 'shape', 'board'), chase = jzP(ctx, 'chase', 'chase'), dir = jzP(ctx, 'dir', 1) < 0 ? -1 : 1;
        var text = ld1_brk(c.text, port ? 5 : 8), arrow = shp === 'arrow' && !port;
        var maxW = W * (arrow ? 0.62 : 0.72), maxH = H * (port ? 0.36 : 0.42), m1 = ld1_meas(text, 0.06, 1.12), subT = jzP(ctx, 'sub', false) ? ld1_alt(ctx) : null;
        var size = Math.min(ld1_fit(text, maxW, maxH, 0.06, 1.12), u * 0.24, W * (arrow ? 0.8 : 0.9) / (m1.w + 1.5), H * 0.8 / (m1.h + 1.24 + (subT ? 0.3 : 0)));
        var mw = m1.w * size, mh = m1.h * size, ss = Math.max(jzSmallSize(ctx), size * 0.16), padX = size * 0.75, padY = size * 0.62;
        var bw = mw + padX * 2, bh = mh + padY * 2 + (subT ? ss * 1.8 : 0), ax = arrow ? bh * 0.42 * dir : 0, cx = W / 2 - ax / 2, cy = H / 2, x0 = cx - bw / 2, y0 = cy - bh / 2;
        var DK = ld1_dark(sc), dark = jzLum(sc.bg) < 0.4, board = dark ? jzMixHex(sc.bg, sc.fg, 0.1) : DK, bulbC = null, textC = null, cand = [sc.accent, ld1_light(sc), sc.accent2];
        for (i = 0; i < cand.length && !bulbC; i++) if (cand[i] && jzContrast(cand[i], board) >= 2) bulbC = cand[i];
        bulbC = bulbC || ld1_light(sc);
        cand = [ld1_light(sc), sc.fg, sc.accent];
        for (i = 0; i < cand.length && !textC; i++) if (cand[i] && jzContrast(cand[i], board) >= 3) textC = cand[i];
        textC = textC || ld1_onCol(sc, board);
        var TH = ld1_H(ctx) + 'var qq=ob(cl(time/0.35),1.4)*(1-0.1*ic(PO));';
        var shape = null, tip;
        if (arrow) {
            tip = dir > 0 ? [x0 + bw + bh * 0.5, cy] : [x0 - bh * 0.5, cy];
            shape = dir > 0 ? [[x0, y0], [x0 + bw, y0], [x0 + bw, y0 - bh * 0.12], tip, [x0 + bw, y0 + bh * 1.12], [x0 + bw, y0 + bh], [x0, y0 + bh]]
                : [[x0 + bw, y0], [x0, y0], [x0, y0 - bh * 0.12], tip, [x0, y0 + bh * 1.12], [x0, y0 + bh], [x0 + bw, y0 + bh]];
        }
        // the sign (scaled from its centre with the pop)
        var S = jzShapeLayer(ctx, 'bulb sign', cx, cy);
        jzXf(S, 'ADBE Anchor Point').setValue([cx, cy]);
        jzSetExpr(jzXf(S, 'ADBE Scale'), TH + '[value[0]*qq,value[1]*qq]');
        jzSetExpr(jzXf(S, 'ADBE Opacity'), TH + '(qq<=0.01?0:100)*K');
        var r = jzClamp(size * 0.075, 4 * ctx.u, u * 0.022), rings = shp === 'double' && !arrow ? [0.95, 0.86] : [arrow ? 0.93 : 0.95];
        var lw0 = Math.max(1.5 * ctx.u, u * 0.003);
        for (var ri = 0; ri < rings.length; ri++) {
            var k = rings[ri], col = ri ? (jzContrast(sc.accent2 || bulbC, board) > 2 ? (sc.accent2 || bulbC) : bulbC) : bulbC, path = null, per;
            var gR = jzGrp(S, 'bulbs ' + (ri + 1));
            var addRing = function (gg) {
                if (arrow) { var pp = []; for (var z = 0; z < shape.length; z++) pp.push([cx + (shape[z][0] - cx) * k, cy + (shape[z][1] - cy) * k]); jzAddPath(gg, pp, true); return ld1_perim(pp); }
                var d = size * 0.3 * (1 - k), ww = bw - d * 2, hh2 = bh - d * 2 * (bh / bw), rr = Math.min(size * 0.3 * k, ww / 2, hh2 / 2);
                jzAddRect(gg, ww, hh2, rr, cx, cy);
                return 2 * (ww + hh2) - 8 * rr + 2 * Math.PI * rr;
            };
            var N = 0, sp = 1;
            // layers of the bulb ring, top first: highlights, lit bulbs, glow, dim-lit, sockets
            var parts = [['shine', '#FFFFFF', r * 0.7, 70, [-r * 0.25, -r * 0.3]], ['lit', col, r * 2, 100, null], ['glow', col, r * 4.2, 16, null], ['dim', jzMixHex(jzMixHex(board, col, 0.3), col, chase === 'alt' ? 0.2 : chase === 'wave' ? 0.35 : 0.18), r * 2, 100, null], ['sockets', jzMixHex(board, col, 0.22), r * 2, 100, null]];
            for (var pi = 0; pi < parts.length; pi++) {
                var gp = ld1_sub(gR, parts[pi][0]), L0 = addRing(gp);
                if (!N) { N = Math.max(4, Math.min(140, Math.round(L0 / (r * 3.3)))); sp = L0 / N; }
                var st = jzAddStroke(gp, parts[pi][1], parts[pi][2], parts[pi][3]);
                ld1_cap(st, 2);
                var lit = pi < 3, period = chase === 'alt' ? 2 : 3;
                var offE = null;
                if (lit) {
                    if (chase === 'alt') offE = ld1_H(ctx) + '((Math.floor(time*3)+' + ri + ')%2)*' + jzN(sp);
                    else offE = ld1_H(ctx) + 'var st=Math.floor(time*' + (chase === 'wave' ? 5 : 9) + ')*' + dir + '+' + (ri ? 0 : 1) + ';(((st%3)+3)%3)*' + jzN(sp);
                }
                ld1_dash(st, 0.01, (lit ? period * sp : sp) - 0.01, offE);
                if (parts[pi][4]) jzGX(gp).property('ADBE Vector Position').setValue(parts[pi][4]);
                if (pi < 4) jzAddTrimPaths(gp, ld1_H(ctx) + 'iocu((time-' + jzN(0.1 + ri * 0.1) + ')/0.6)*100', ld1_H(ctx) + 'Math.min(ic(PO),iocu((time-' + jzN(0.1 + ri * 0.1) + ')/0.6))*100');
            }
        }
        var gO = jzGrp(S, 'board');
        if (arrow) jzAddPath(gO, shape, true); else jzAddRect(gO, bw, bh, size * 0.3, cx, cy);
        jzAddStroke(gO, jzMixHex(board, bulbC, 0.35), lw0); jzAddFill(gO, board);
        var gSh = jzGrp(S, 'shadow');
        if (arrow) jzAddPath(gSh, shape, true); else jzAddRect(gSh, bw, bh, size * 0.3, cx, cy);
        jzAddFill(gSh, DK); ld1_gOp(gSh, '30'); jzGX(gSh).property('ADBE Vector Position').setValue([u * 0.012, u * 0.016]);
        // lyric + sub copy ride on the sign
        var ty = cy - (subT ? ss * 0.9 : 0);
        var L = ld1_T(ctx, text, { font: font, size: size, color: textC, x: cx, y: ty, track: 0.06, lead: 1.12, name: jzFlat(c.text) });
        ld1_parent(L, S);
        ld1_anim(ctx, L, { mi: 0, noHold: ld1_plateHold(ctx) });
        if (subT) {
            var t = jzChars(subT).length > 28 ? jzChars(subT).slice(0, 27).join('') + '\u2026' : subT;
            var sz = Math.min(ss, ld1_fit(t, bw - padX, ss * 1.2, 0.18, 1.2));
            var SB = jzText(ctx, t, { font: jzBodyF(ctx), size: sz, track: 0.18, color: bulbC, x: cx, y: cy + mh / 2 + padY * 0.1, name: 'bulb sign copy' });
            ld1_parent(SB, S); ld1_opx(ctx, SB, 'oe((time-0.3)/0.3)*K');
        }
        return ld1_box(cx - mw / 2, ty - mh / 2, cx + mw / 2, ty + mh / 2);
    }
});

/* ================================================================== 14 ledScroll \u2014 \u96FB\u5149\u63B2\u793A\u677F */
// LED dots = a dot grid (ellipse + two repeaters) used as the alpha matte of the scrolling text; a dim copy shows the off LEDs
ld1_reg('ledScroll', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(['dot', 'dot'].concat(jzFontsOf(st, ['display', 'body']))), col: rng.pick(['accent', 'accent', 'fg', 'accent2']), info: rng.pick(['top', 'bottom', 'none', 'bottom']), mods: rng.int(3, 6), y: rng.pick([0.5, 0.5, 0.42, 0.58]) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), i, j;
        var text = jzFlat(c.text), n = jzCount(text);
        if (!n) return null;
        var font = jzP(ctx, 'font', 'dot'), info = jzP(ctx, 'info', 'bottom'), mods = jzP(ctx, 'mods', 4), cl = jzP(ctx, 'col', 'accent');
        var fx0 = W * 0.03, fw = W * 0.94, adv0 = ld1_lineW(text, 0.08);
        var size = jzClamp((fw - W * 0.04) * 0.9 / Math.max(0.5, adv0), u * 0.1, Math.min(u * 0.2, H * 0.2)), p = size / 9.5;
        var ph = size * 1.36, infoH = info === 'none' ? 0 : size * 0.56, fr = size * 0.2, fh = ph + infoH + fr * 2 + (infoH ? fr * 0.6 : 0), fy0 = H * jzP(ctx, 'y', 0.5) - fh / 2;
        var DK = ld1_dark(sc), frameC = jzMixHex(DK, sc.sub, jzLum(sc.bg) < 0.4 ? 0.25 : 0.2), panel = jzMixHex(DK, '#000000', 0.5), ledC = null, infoC = null, i2;
        var cand = [cl === 'fg' ? ld1_light(sc) : cl === 'accent2' ? sc.accent2 : sc.accent, sc.accent, ld1_light(sc)];
        for (i = 0; i < cand.length && !ledC; i++) if (cand[i] && jzContrast(cand[i], panel) >= 3) ledC = cand[i];
        ledC = ledC || '#FFFFFF';
        cand = [sc.accent2, sc.accent, ld1_light(sc)];
        for (i = 0; i < cand.length && !infoC; i++) if (cand[i] && cand[i] !== ledC && jzContrast(cand[i], panel) >= 3) infoC = cand[i];
        infoC = infoC || ld1_light(sc);
        var TH = ld1_H(ctx) + 'var ee=oc(time/0.35),hw=' + jzN(fw / 2) + '*ee;';
        // frame + module seams + bolts
        var F = jzShapeLayer(ctx, 'led frame', 0, 0), mw = fw / mods;
        var gb = jzGrp(F, 'bolts');
        for (i = 0; i < mods; i++) for (j = 0; j < 4; j++) {
            var bx = j % 2 ? fx0 + (i + 1) * mw - fr * 0.6 : fx0 + i * mw + fr * 0.6, by = j < 2 ? fy0 + fr * 0.5 : fy0 + fh - fr * 0.5, gq = ld1_sub(gb, 'bolt');
            jzAddEllipse(gq, fr * 0.28, fr * 0.28, bx, by); ld1_gOp(gq, TH + '(Math.abs(' + jzN(bx - W / 2) + ')<hw)?100:0');
        }
        jzAddFill(gb, jzMixHex(frameC, ld1_light(sc), 0.3));
        var gs = jzGrp(F, 'seams');
        for (i = 1; i < mods; i++) { var sxp = fx0 + i * mw, gq2 = ld1_sub(gs, 'seam'); ld1_rr(gq2, 2 * ctx.u, fh, 0, sxp, fy0 + fh / 2); ld1_gOp(gq2, TH + '(Math.abs(' + jzN(sxp - W / 2) + ')<hw)?100:0'); }
        jzAddFill(gs, jzMixHex(frameC, '#000000', 0.35));
        var mainY = info === 'top' ? fy0 + fr + infoH + fr * 0.6 : fy0 + fr, panels = [{ y: mainY, h: ph, col: ledC }];
        if (infoH) panels.push({ y: info === 'top' ? fy0 + fr : mainY + ph + fr * 0.6, h: infoH, col: infoC });
        for (i = 0; i < panels.length; i++) { var gpn = jzGrp(F, 'panel ' + (i + 1)), rp = ld1_rr(gpn, fw - fr * 2, panels[i].h, 0, W / 2, panels[i].y + panels[i].h / 2); jzSetExpr(rp.property('ADBE Vector Rect Size'), TH + '[Math.max(0.5,hw*2-' + jzN(fr * 2) + '),' + jzN(panels[i].h) + ']'); jzAddFill(gpn, panel); }
        var gf = jzGrp(F, 'frame'), rf = ld1_rr(gf, fw, fh, fr * 0.6, W / 2, fy0 + fh / 2);
        jzSetExpr(rf.property('ADBE Vector Rect Size'), TH + '[Math.max(0.5,hw*2),' + jzN(fh) + ']');
        jzAddFill(gf, frameC);
        ld1_opx(ctx, F, 'K');
        // dot grids: cols x rows of LEDs over a panel (x-scaled with the frame as it opens)
        function dots(name, P0, col) {
            var D = jzShapeLayer(ctx, name, W / 2, 0), g = jzGrp(D, 'leds'), cols = Math.floor((fw - fr * 2) / p), rows = Math.max(1, Math.floor(P0.h / p));
            var gx0 = W / 2 - cols * p / 2, gy0 = P0.y + (P0.h - rows * p) / 2;
            jzXf(D, 'ADBE Anchor Point').setValue([W / 2, 0]);
            jzAddEllipse(g, p * 0.72, p * 0.72, gx0 + p / 2, gy0 + p / 2);
            ld1_rep(g, cols, p, 0); ld1_rep(g, rows, 0, p);
            jzAddFill(g, col);
            jzSetExpr(jzXf(D, 'ADBE Scale'), TH + '[value[0]*ee,value[1]]');
            return D;
        }
        for (i = 0; i < panels.length; i++) { var DO = dots('led off ' + (i + 1), panels[i], jzMixHex(panel, panels[i].col, 0.13)); ld1_opx(ctx, DO, 'K'); }
        // main text: slides in and stops (or scrolls as a marquee when too long); an empty shape layer carries the move
        var P0 = panels[0], m = ld1_lineW(text, 0.08) * size, pw0 = fw - fr * 2, fits = m <= pw0 * 0.94, cyy = P0.y + P0.h / 2;
        var MV = jzShapeLayer(ctx, 'led scroller', W / 2, cyy), mvH;
        jzXf(MV, 'ADBE Anchor Point').setValue([W / 2, cyy]);       // anchor = position: children keep comp coordinates
        if (fits) {
            var tIn = jzClamp(c.dur * 0.28, 0.35, 0.9), tOut = Math.max(0.2, c.outDur || 0.3);
            mvH = TH + 'var a=oc(time/' + jzN(tIn) + '),b=ic((time-' + jzN(c.dur - tOut) + ')/' + jzN(tOut) + ');var x=' + jzN(W / 2 + pw0 / 2 + m / 2) + '+(' + jzN(W / 2) + '-' + jzN(W / 2 + pw0 / 2 + m / 2) + ')*a-b*' + jzN(pw0 / 2 + m / 2) + ';';
        } else {
            var perS = m + size * 2, v = Math.max(W * 0.35, perS / Math.max(0.8, c.dur * 0.9));
            mvH = TH + 'var x=' + jzN(W / 2 + pw0 / 2 + m / 2) + '-((time*' + jzN(v) + ')%' + jzN(perS) + ');';
        }
        jzSetExpr(jzXf(MV, 'ADBE Position'), mvH + '[Math.round(x/' + jzN(p) + ')*' + jzN(p) + ',value[1]]');
        var str = fits ? text : text + '\u3000\u3000' + text + '\u3000\u3000' + text, off = fits ? 0 : m + size * 2;
        var LT = ld1_T(ctx, str, { font: font, size: size, color: ledC, x: W / 2 + off, y: cyy, track: 0.08, name: text });
        ld1_parent(LT, MV);
        ld1_anim(ctx, LT, { mi: 0, noHold: true, treat: false });
        var DM = dots('led matte 1', P0, '#FFFFFF');
        ld1_matte(LT, DM);
        // info line
        if (infoH) {
            var P1 = panels[1], ac = ld1_alt(ctx), inf = 'LINE ' + jzLineNo(ctx) + '  \u25C6  ' + jzFmtTime(c.start || 0) + (/^No\./.test(ac) ? '' : '  \u25C6  ' + ac) + '   \u25C6   ';
            var fs = P1.h * 0.72, im = ld1_lineW(inf, 0.1) * fs, reps = Math.ceil((fw + im) / Math.max(1, im)) + 1, s4 = '';
            for (i = 0; i < reps; i++) s4 += inf;
            var IT = jzText(ctx, s4, { font: font, size: fs, align: 'left', x: W / 2 - pw0 / 2, y: P1.y + P1.h / 2, track: 0.1, color: infoC, name: 'led info' });
            jzSetExpr(jzXf(IT, 'ADBE Position'), TH + 'var x=value[0]-((time*' + jzN(size * 2.2) + ')%' + jzN(im) + ');[Math.round(x/' + jzN(p) + ')*' + jzN(p) + ',value[1]]');
            ld1_opx(ctx, IT, 'oe((time-0.2)/0.3)*K');
            var DI = dots('led matte 2', P1, '#FFFFFF');
            ld1_matte(IT, DI);
        }
        return ld1_box(Math.max(W / 2 - pw0 / 2, W / 2 - m / 2), P0.y, Math.min(W / 2 + pw0 / 2, W / 2 + m / 2), P0.y + P0.h);
    }
});

/* ================================================================== 15 billboard \u2014 \u770B\u677F */
ld1_reg('billboard', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), face: rng.pick(['light', 'light', 'accent']), lamps: rng.int(3, 4), sweep: rng.range(8, 16), tag: rng.chance(0.7) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, s = c.seed, u = jzU(ctx), port = jzPortrait(ctx), i;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), text = ld1_brk(c.text, port ? 5 : 9), tag = !!jzP(ctx, 'tag', true), NL = jzP(ctx, 'lamps', 3), sweep = jzP(ctx, 'sweep', 12);
        var bw = W * (port ? 0.86 : 0.72), bh = Math.min(H * (port ? 0.3 : 0.42), bw * 0.5), bx = W / 2 - bw / 2, by = H * (port ? 0.3 : 0.16), groundY = H * 0.94;
        var DK = ld1_dark(sc), steel = jzMixHex(DK, sc.sub, 0.35), faceC;
        if (jzP(ctx, 'face', 'light') === 'accent' || jzLum(sc.bg) > 0.55) {
            if (jzLum(sc.bg) > 0.55) { var fc = [sc.accent, sc.ink, sc.fg]; faceC = sc.fg; for (i = 0; i < fc.length; i++) if (fc[i] && jzContrast(fc[i], sc.bg) >= 1.8) { faceC = fc[i]; break; } }
            else faceC = ld1_plateCol(sc, [sc.accent, sc.ink]);
        } else faceC = jzMixHex(ld1_light(sc), sc.accent, 0.06);
        var tc = ld1_onCol(sc, faceC), lw = Math.max(1.2 * ctx.u, u * 0.0025);
        var TH = ld1_H(ctx) + 'var rise=(1-oc(time/0.55))*' + jzN(H * 0.08) + '+ic(PO)*' + jzN(H * 0.05) + ',a0=oc(time/0.35)*K;' +
            'function on(i){var t1=0.15+i*0.12,o=time>t1+0.25?1:time>t1?(hh(Math.floor(time*24)*7+i*13+' + (s % 997) + ')<0.55?1:0.1):0;return o*(1-ic(cl(PO*1.6-i*0.12)));}';
        // ground + posts (their tops follow the board)
        var ST = jzShapeLayer(ctx, 'billboard stand', 0, 0), pw = bw * 0.035;
        var gg = jzGrp(ST, 'ground'); jzAddPath(gg, [[W * 0.03, groundY], [W * 0.97, groundY]], false); jzAddStroke(gg, sc.sub, lw); ld1_gOp(gg, '50');
        for (i = 0; i < 2; i++) {
            var px = bx + bw * (i ? 0.78 : 0.22), gp = jzGrp(ST, 'post ' + (i + 1)), rp = ld1_rr(gp, pw, 10, 0);
            jzSetExpr(rp.property('ADBE Vector Rect Size'), TH + '[' + jzN(pw) + ',Math.max(1,' + jzN(groundY - by - bh) + '-rise)]');
            jzSetExpr(rp.property('ADBE Vector Rect Position'), TH + '[' + jzN(px) + ',(' + jzN(by + bh) + '+rise+' + jzN(groundY) + ')/2]');
            jzAddFill(gp, steel);
        }
        var x1 = bx + bw * 0.22, x2 = bx + bw * 0.78, gx = jzGrp(ST, 'bracing');
        ld1_seg(gx, 'brace 1', TH, 'var yb=' + jzN(by + bh) + '+rise,Lp=' + jzN(groundY) + '-yb;var X0=' + jzN(x1) + ',Y0=yb+Lp*0.25,X1=' + jzN(x2) + ',Y1=' + jzN(groundY) + '-Lp*0.1', lw);
        ld1_seg(gx, 'brace 2', TH, 'var yb=' + jzN(by + bh) + '+rise,Lp=' + jzN(groundY) + '-yb;var X0=' + jzN(x2) + ',Y0=yb+Lp*0.25,X1=' + jzN(x1) + ',Y1=' + jzN(groundY) + '-Lp*0.1', lw);
        jzAddFill(gx, steel);
        jzSetExpr(jzXf(ST, 'ADBE Opacity'), TH + '100*a0');
        // the board rig (catwalk, frame, face) moves with "rise"
        var B = jzShapeLayer(ctx, 'billboard', 0, 0), cwY = by + bh + bh * 0.1;
        jzSetExpr(jzXf(B, 'ADBE Position'), TH + '[value[0],value[1]+rise]');
        var gcw = jzGrp(B, 'catwalk');
        ld1_rr(gcw, bw * 0.92, bh * 0.025, 0, W / 2, cwY + bh * 0.0125);
        for (i = 0; i <= 10; i++) ld1_rr(gcw, lw, bh * 0.07, 0, bx + bw * (0.04 + 0.92 * i / 10), cwY - bh * 0.035);
        ld1_rr(gcw, bw * 0.92, lw, 0, W / 2, cwY - bh * 0.07);
        jzAddFill(gcw, steel);
        var gfa = jzGrp(B, 'face'); ld1_rr(gfa, bw, bh, 0, W / 2, by + bh / 2); jzAddFill(gfa, jzMixHex(faceC, DK, 0.28));
        var gfr = jzGrp(B, 'frame'); ld1_rr(gfr, bw * 1.03, bh + bw * 0.03, 0, W / 2, by + bh / 2); jzAddFill(gfr, steel);
        jzSetExpr(jzXf(B, 'ADBE Opacity'), TH + '100*a0');
        // lamp light: soft cones (three stacked translucent lengths) sweeping over the face, clipped to it, + the lit face
        var CN = jzShapeLayer(ctx, 'billboard light', 0, 0);
        jzSetExpr(jzXf(CN, 'ADBE Position'), TH + '[value[0],value[1]+rise]');
        var gl = jzGrp(CN, 'lit face'); ld1_rr(gl, bw, bh, 0, W / 2, by + bh / 2); jzAddFill(gl, faceC);
        var litE = '(0'; for (i = 0; i < NL; i++) litE += '+on(' + i + ')'; litE += ')/' + NL;
        ld1_gOp(gl, TH + '55*' + litE);
        for (i = 0; i < NL; i++) {
            var lx = bx + bw * (i + 0.5) / NL, ly = by + bh + bh * 0.06, spread = bw / NL * 0.75, gc = jzGrp(CN, 'cone ' + (i + 1));
            for (var z = 0; z < 3; z++) {
                var len = bh * [0.5, 0.85, 1.25][z], sprd = spread * len / (bh * 1.25), gz = ld1_sub(gc, 'beam ' + (z + 1));
                jzAddPath(gz, [[-bw * 0.02, 0], [-sprd, -len], [sprd, -len], [bw * 0.02, 0]], true); jzAddFill(gz, faceC); ld1_gOp(gz, '32');
            }
            jzGX(gc).property('ADBE Vector Position').setValue([lx, ly]);
            ld1_gRot(gc, TH + 'Math.sin(time*0.8+' + jzN(i * 1.7) + ')*' + jzN(sweep));
            ld1_gOp(gc, TH + 'on(' + i + ')*100');
        }
        var mk = jzMaskRect(CN, bx, by, bx + bw, by + bh);
        jzSetExpr(jzXf(CN, 'ADBE Opacity'), TH + '100*a0');
        // lamp housings + arms
        var LH = jzShapeLayer(ctx, 'billboard lamps', 0, 0);
        jzSetExpr(jzXf(LH, 'ADBE Position'), TH + '[value[0],value[1]+rise]');
        for (i = 0; i < NL; i++) {
            var lx2 = bx + bw * (i + 0.5) / NL, ly2 = by + bh + bh * 0.06, gh = jzGrp(LH, 'lamp ' + (i + 1));
            var gbulb = ld1_sub(gh, 'bulb'); jzAddEllipse(gbulb, bh * 0.05, bh * 0.05, lx2, ly2 - bh * 0.03); jzAddFill(gbulb, ld1_light(sc)); ld1_gOp(gbulb, TH + '(on(' + i + ')>0.5)?100:0');
            var gho = ld1_sub(gh, 'housing'); jzAddPath(gho, [[lx2 - bh * 0.05, ly2 + bh * 0.03], [lx2 + bh * 0.05, ly2 + bh * 0.03], [lx2 + bh * 0.035, ly2 - bh * 0.03], [lx2 - bh * 0.035, ly2 - bh * 0.03]], true);
            ld1_rr(gho, lw * 1.5, cwY - ly2 + bh * 0.02, 0, lx2, (cwY + ly2 + bh * 0.02) / 2); jzAddFill(gho, steel);
        }
        jzSetExpr(jzXf(LH, 'ADBE Opacity'), TH + '100*a0');
        // lyric + tags ride on the board
        var size = Math.min(ld1_fit(text, bw * 0.84, bh * 0.66, 0.04, 1.1), bh * 0.6), ty = by + bh / 2 + (tag ? bh * 0.03 : 0);
        var L = ld1_T(ctx, text, { font: font, size: size, color: tc, x: W / 2, y: ty, track: 0.04, lead: 1.1, name: jzFlat(c.text) });
        ld1_parent(L, B);
        ld1_anim(ctx, L, { mi: ld1_mi(ctx, 0.3), noHold: ld1_plateHold(ctx) });
        if (tag) {
            var fs = Math.max(jzSmallSize(ctx) * 0.8, bh * 0.05);
            var T1 = jzText(ctx, 'No.' + jzLineNo(ctx), { font: jzMonoF(ctx), size: fs, align: 'left', track: 0.2, x: bx + bw * 0.025, y: by + fs * 1.1, color: tc, name: 'billboard no' });
            var T2 = jzText(ctx, jzFmtTime(c.start || 0), { font: jzMonoF(ctx), size: fs, align: 'right', track: 0.2, x: bx + bw * 0.975, y: by + fs * 1.1, color: tc, name: 'billboard time' });
            ld1_parent(T1, B); ld1_parent(T2, B);
            jzSetExpr(jzXf(T1, 'ADBE Opacity'), TH + '70*a0'); jzSetExpr(jzXf(T2, 'ADBE Opacity'), TH + '70*a0');
        }
        var m = ld1_meas(text, 0.04, 1.1);
        return ld1_box(W / 2 - m.w * size / 2, ty - m.h * size / 2, W / 2 + m.w * size / 2, ty + m.h * size / 2);
    }
});

/* ================================================================== 16 crowdBubbles \u2014 \u5439\u304D\u51FA\u3057\u306E\u7FA4\u308C */
var LD1_REACT = ['\u2026', '\uFF01\uFF1F', '\u266A', '\uFF1F', '\uFF01', '\u2026\uFF01', '\u2661'];
// bubble outline in local px: kind round / rect / shout, tail towards tailDir
function ld1_bubble(g, kind, w, h, tailDir, fill, stroke, lw) {
    var tx = tailDir * w * 0.18, ty = h * 0.42, tail = [[tx - w * 0.06 * tailDir, ty - h * 0.05], [tx + w * 0.02 * tailDir, ty - h * 0.05], [tx - w * 0.1 * tailDir, h * 0.5 + h * 0.3]];
    if (stroke) { var gl = ld1_sub(g, 'tail line'); jzAddPath(gl, [tail[0], tail[2], tail[1]], false); jzAddStroke(gl, stroke, lw); }
    var gt = ld1_sub(g, 'tail'); jzAddPath(gt, tail, true); jzAddFill(gt, fill);
    var gb = ld1_sub(g, 'body');
    if (kind === 'rect') jzAddRect(gb, w, h, h * 0.35);
    else if (kind === 'shout') { var pts = []; for (var i = 0; i < 22; i++) { var t = i / 22 * Math.PI * 2, rr = i % 2 ? 0.8 : 1.12; pts.push([Math.cos(t) * w / 2 * rr, Math.sin(t) * h / 2 * rr]); } jzAddPath(gb, pts, true); }
    else jzAddEllipse(gb, w, h);
    if (stroke) jzAddStroke(gb, stroke, lw);
    jzAddFill(gb, fill);
    return g;
}
ld1_reg('crowdBubbles', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'body'])), sf: rng.pick(jzFontsOf(st, ['body', 'display'])), big: rng.pick(['round', 'round', 'rect', 'shout']), style: rng.pick(['mixed', 'outline', 'filled']), count: rng.int(10, 16), side: rng.pick([1, -1]) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, s = c.seed, u = jzU(ctx), port = jzPortrait(ctx), i, j, g2;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), sf = jzP(ctx, 'sf', jzBodyF(ctx)), style = jzP(ctx, 'style', 'mixed'), bk = jzP(ctx, 'big', 'round'), side = jzP(ctx, 'side', 1) < 0 ? -1 : 1;
        var text = ld1_brk(c.text, port ? 5 : 8), size = Math.min(ld1_fit(text, W * (port ? 0.62 : 0.46), H * (port ? 0.22 : 0.3), 0.03, 1.15), u * 0.15), m = ld1_meas(text, 0.03, 1.15);
        var bw = m.w * size + size * 1.4, bh = m.h * size + size * 1.1, cx = W / 2, cy = H / 2 - bh * 0.05, bigC = null, cand = [sc.accent, ld1_light(sc)];
        for (i = 0; i < cand.length && !bigC; i++) if (cand[i] && jzContrast(cand[i], sc.bg) >= 1.8) bigC = cand[i];
        bigC = bigC || ld1_light(sc);
        var tc = ld1_onCol(sc, bigC), lw = Math.max(1.2 * ctx.u, u * 0.0025);
        // fragments for the crowd
        var words = [], src = jzChunk(jzFlat(c.lineText || c.text)).concat(jzChunk(jzFlat(c.text)));
        for (i = 0; i < src.length; i++) { var pp = jzSplitLines(src[i], 4).split('\r'); for (j = 0; j < pp.length; j++) if (pp[j] && jzIndexOf(words, pp[j]) < 0 && !/["\\]/.test(pp[j])) words.push(pp[j]); }
        function frag(q) { var r = jzHash(s, q, 41) % 10; if (r < 6 && words.length) return words[jzHash(s, q, 42) % words.length]; return LD1_REACT[jzHash(s, q, 43) % LD1_REACT.length]; }
        // slots around the big bubble
        var slots = [], gxN = port ? 3 : 6, gyN = port ? 8 : 5;
        for (var gy = 0; gy < gyN; gy++) for (var gx = 0; gx < gxN; gx++) {
            var x = W * (0.1 + 0.8 * (gx + 0.5) / gxN) + (jzR(s, gx, gy, 1) * 2 - 1) * W * 0.04, y = H * (0.1 + 0.8 * (gy + 0.5) / gyN) + (jzR(s, gx, gy, 2) * 2 - 1) * H * 0.03;
            if (Math.abs(x - cx) < bw * 0.5 + W * (port ? 0.12 : 0.07) && Math.abs(y - cy) < bh * 0.5 + H * (port ? 0.04 : 0.07)) continue;
            slots.push([x, y, jzR(s, gx, gy, 3)]);
        }
        slots.sort(function (a, b) { return a[2] - b[2]; });
        var cnt = Math.min(jzP(ctx, 'count', 12), slots.length), ss = Math.max(jzSmallSize(ctx) * 1.4, Math.min(size * 0.42, u * 0.05));
        var life = jzClamp(c.dur * 0.7, 1.2, 3), jx = W * 0.8 / gxN * 0.18, jy = H * 0.8 / gyN * 0.14, NG = Math.ceil((c.dur + 0.7) / life) + 1;
        var T0 = [], FR = [], LN = [], WS = [], JX = [], JY = [], KR = [], SX = [], SY = [], FI = [];
        for (i = 0; i < cnt; i++) {
            T0.push(0.05 + jzR(s, i, 5) * Math.min(0.6, c.dur * 0.25)); SX.push(slots[i][0]); SY.push(slots[i][1]);
            FI.push(style === 'filled' || (style === 'mixed' && jzR(s, i, 9) < 0.45));
            var fr = [], ln = [], ws = [], jxs = [], jys = [], kr = [];
            for (g2 = 0; g2 < NG; g2++) {
                var t = frag(i * 7 + g2); fr.push(t); ln.push(jzChars(t).length); ws.push(ld1_lineW(t, 0.04) * ss + ss * 1.4);
                jxs.push((jzR(s, i, g2, 44) * 2 - 1) * jx); jys.push((jzR(s, i, g2, 45) * 2 - 1) * jy); kr.push(jzR(s, i, g2, 8) < 0.5 ? 1 : 0);
            }
            FR.push(fr); LN.push(ln); WS.push(ws); JX.push(jxs); JY.push(jys); KR.push(kr);
        }
        function arr2(a) { var o = []; for (var z = 0; z < a.length; z++) o.push(ld1_arr(a[z])); return '[' + o.join(',') + ']'; }
        var CH = ld1_H(ctx) + 'var T0=' + ld1_arr(T0) + ',LF=' + jzN(life) + ',NG=' + NG + ',SX=' + ld1_arr(SX) + ',SY=' + ld1_arr(SY) + ',JX=' + arr2(JX) + ',JY=' + arr2(JY) + ',KR=' + arr2(KR) + ',WS=' + arr2(WS) + ',LN=' + arr2(LN) + ';' +
            'function gen(i){if(time<T0[i])return -1;return Math.min(NG-1,Math.floor((time-T0[i])/LF));}' +
            'function bub(i){var g=gen(i);if(g<0)return [0,0,0,0];var tl=time-T0[i]-g*LF,e=ob(cl(tl/0.22),2.2)*(1-ic(cl((tl-LF+0.2)/0.2)))*K;return [e,g,SX[i]+JX[i][g],SY[i]+JY[i][g]];}\n';
        // the crowd: one shape layer (a group per bubble; round / rect bodies swap per generation) + one text layer
        var CS = jzShapeLayer(ctx, 'crowd bubbles', 0, 0);
        for (i = 0; i < cnt; i++) {
            var gb = jzGrp(CS, 'bubble ' + (i + 1)), fcol = FI[i] ? jzMixHex(sc.bg, sc.fg, 0.14) : sc.bg, stc = FI[i] ? null : sc.sub, td = SX[i] < cx ? 1 : -1;
            for (var kd = 0; kd < 2; kd++) {
                var gk = ld1_bubble(ld1_sub(gb, kd ? 'rect' : 'round'), kd ? 'rect' : 'round', 100, ss * 2, td, fcol, stc, lw);
                ld1_gSc(gk, CH + 'var B=bub(' + i + '),w=WS[' + i + '][Math.max(0,B[1])];[w,100]');
                ld1_gOp(gk, CH + 'var B=bub(' + i + ');KR[' + i + '][Math.max(0,B[1])]===' + kd + '?100:0');
            }
            ld1_gPos(gb, CH + 'var B=bub(' + i + ');[B[2],B[3]]');
            ld1_gSc(gb, CH + 'var B=bub(' + i + ');[B[0]*100,B[0]*100]');
            ld1_gOp(gb, CH + 'var B=bub(' + i + ');B[0]>0.01?95:0');
        }
        // crowd texts: one line per bubble (the source text switches with the generations), each line moved onto its bubble
        var TX = jzText(ctx, ' ', { font: sf, size: ss, color: sc.sub, x: 0, y: 0, leading: ss, track: 0.04, name: 'crowd texts' });
        jzXf(TX, 'ADBE Anchor Point').setValue([0, 0]); jzXf(TX, 'ADBE Position').setValue([0, 0]);
        var CT = CH + 'var FR=[';
        for (i = 0; i < cnt; i++) CT += (i ? ',' : '') + ld1_strArr(FR[i]);
        CT += '];';
        try { TX.property('ADBE Text Properties').property('ADBE Text Document').expression = CT + 'var s="";for(var i=0;i<' + cnt + ';i++){if(i)s+="\\r";var g=gen(i);s+=g<0?" ":FR[i][g];}s'; } catch (eT) { jzWarn('crowd source: ' + eT.toString()); }
        var LNf = 'function lineOf(ti){var acc=0;for(var k=0;k<' + cnt + ';k++){var g=gen(k),n=g<0?1:LN[k][g];if(ti<=acc+n)return k;acc+=n;}return ' + (cnt - 1) + ';}var ln=lineOf(textIndex),B=bub(ln);';
        var KB = Math.max(W, H) * 3;
        jzAnimator(TX, 'JZ Crowd Place', [['ADBE Text Position 3D', [KB, KB, 0]]], CH + LNf + '[B[2]/' + jzN(KB) + '*100,(B[3]+' + jzN(LD1_CY * ss) + '-ln*' + jzN(ss) + ')/' + jzN(KB) + '*100,0]');
        jzAnimator(TX, 'JZ Crowd Show', [['ADBE Text Opacity', 0]], CH + LNf + '(1-cl((B[0]-0.5)*2))*100');
        var FIa = []; for (i = 0; i < cnt; i++) FIa.push(FI[i] ? 1 : 0);
        jzAnimator(TX, 'JZ Crowd Filled', [['ADBE Text Fill Color', jzHex(sc.fg)]], CH + 'var FI=' + ld1_arr(FIa) + ';' + LNf + 'FI[ln]*100');
        // the big bubble with the lyric (the lyric is parented to it)
        var BB = jzShapeLayer(ctx, 'big bubble', cx, cy);
        jzXf(BB, 'ADBE Anchor Point').setValue([cx, cy]);
        var gB = ld1_bubble(jzGrp(BB, 'bubble'), bk, bw * (bk === 'shout' ? 1.25 : 1), bh * (bk === 'shout' ? 1.3 : 1), side, bigC, null, lw);
        jzGX(gB).property('ADBE Vector Position').setValue([cx, cy]);
        var BQ = ld1_H(ctx) + 'var q=ob(cl((time-0.05)/0.3),1.8)*(1-0.2*ic(PO));';
        jzSetExpr(jzXf(BB, 'ADBE Scale'), BQ + '[value[0]*q,value[1]*q]');
        jzSetExpr(jzXf(BB, 'ADBE Opacity'), BQ + '(q<=0.01?0:100)*K');
        var L = ld1_T(ctx, text, { font: font, size: size, color: tc, x: cx, y: cy, track: 0.03, lead: 1.15, name: jzFlat(c.text) });
        ld1_parent(L, BB);
        ld1_anim(ctx, L, { mi: ld1_mi(ctx, 0.12), noHold: ld1_plateHold(ctx) });
        return ld1_box(cx - m.w * size / 2, cy - m.h * size / 2, cx + m.w * size / 2, cy + m.h * size / 2);
    }
});

/* ================================================================== 17 crossword \u2014 \u30AF\u30ED\u30B9\u30EF\u30FC\u30C9 */
ld1_reg('crossword', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), dens: rng.range(0.14, 0.22), fill: rng.int(2, 3), clue: rng.chance(0.8) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, s = c.seed, u = jzU(ctx), port = jzPortrait(ctx), i, j, r, cc;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), dens = jzP(ctx, 'dens', 0.18), fillN = jzP(ctx, 'fill', 2), clue = !!jzP(ctx, 'clue', true);
        var t0 = jzFlat(c.text), all = jzChars(t0), chs = [];
        for (i = 0; i < all.length; i++) if (all[i] !== ' ') chs.push(all[i]);
        var n = chs.length;
        if (!n) return null;
        var maxRow = port ? 7 : 12, words = [];
        if (n > maxRow) { var ws = ld1_chunksK(t0, Math.ceil(n / maxRow)); for (i = 0; i < ws.length; i++) { var wa = jzChars(ws[i]), wo = []; for (j = 0; j < wa.length; j++) if (wa[j] !== ' ') wo.push(wa[j]); if (wo.length) words.push(wo); } }
        else words = [chs];
        var LW = 1; for (i = 0; i < words.length; i++) LW = Math.max(LW, words[i].length);
        var C = Math.max(LW + 2, port ? 7 : 9), R = words.length > 1 ? 7 : port ? 7 : 5;
        var below = clue && H > W * 0.7, clueW = clue && !below ? 0.24 : 0;
        var cell = Math.min(W * (0.88 - clueW) / C, H * (below ? 0.64 : 0.8) / R, u * 0.14), gw = C * cell, gh = R * cell, fsC = Math.max(jzSmallSize(ctx) * 1.15, cell * 0.24);
        var gx = (W - gw - (clueW ? W * clueW : 0)) / 2, gy = (H - gh - (below ? fsC * 5 : 0)) / 2;
        var rowsW = words.length > 2 ? [1, 3, 5] : words.length > 1 ? [1, 5] : [Math.floor(R / 2)], c0 = [];
        for (i = 0; i < words.length; i++) c0.push(Math.floor((C - words[i].length) / 2) - (jzHash(s, words[i].length, 0, 0) % 2 && C - words[i].length > 2 ? 1 : 0));
        function key(a, b) { return a * 64 + b; }
        var lyr = {}, black = {};
        for (i = 0; i < words.length; i++) for (j = 0; j < words[i].length; j++) lyr[key(rowsW[i], c0[i] + j)] = { ch: words[i][j], wi: i, j: j };
        for (r = 0; r < R; r++) for (cc = 0; cc < C; cc++) {
            var r2 = R - 1 - r, c2 = C - 1 - cc;
            if (lyr[key(r, cc)] || lyr[key(r2, c2)]) continue;
            if (jzR(s, Math.min(key(r, cc), key(r2, c2)), 3) < dens) { black[key(r, cc)] = 1; black[key(r2, c2)] = 1; }
        }
        for (i = 0; i < words.length; i++) { if (c0[i] > 0) black[key(rowsW[i], c0[i] - 1)] = 1; if (c0[i] + words[i].length < C) black[key(rowsW[i], c0[i] + words[i].length)] = 1; }
        function isW(a, b) { return a >= 0 && b >= 0 && a < R && b < C && !black[key(a, b)]; }
        var num = {}, kn = 1, numCount = 0;
        for (r = 0; r < R; r++) for (cc = 0; cc < C; cc++) {
            if (!isW(r, cc)) continue;
            if ((!isW(r, cc - 1) && isW(r, cc + 1)) || (!isW(r - 1, cc) && isW(r + 1, cc))) { num[key(r, cc)] = kn++; numCount++; }
        }
        // pencilled crossing entries
        var pool = [], own = jzChars(jzStrip((c.lineText || '') + c.text)), KA = jzChars(jzPool('hira'));
        for (i = 0; i < own.length; i++) if (!jzIsPunct(own[i]) && !jzIsLatin(own[i]) && '\u3041\u3043\u3045\u3047\u3049\u3063\u3083\u3085\u3087\u308E\u30A1\u30A3\u30A5\u30A7\u30A9\u30C3\u30E3\u30E5\u30E7\u30EE\u30FC'.indexOf(own[i]) < 0) pool.push(own[i]);
        for (i = 0; i < KA.length; i += 3) pool.push(KA[i]);
        var pencil = {}, fills = 0;
        for (i = 0; i < words.length; i++) for (j = 0; j < words[i].length; j++) {
            if (fills >= fillN * words.length || jzR(s, i, j, 5) > 0.45) continue;
            var col = c0[i] + j, rw = rowsW[i], a = rw, b = rw;
            while (isW(a - 1, col)) a--;
            while (isW(b + 1, col)) b++;
            if (b - a < 2) continue;
            fills++;
            for (var rr = a; rr <= b; rr++) if (!lyr[key(rr, col)]) pencil[key(rr, col)] = pool[jzHash(s, rr, col, 6) % pool.length];
        }
        var paperC = ld1_light(sc), inkC = ld1_dark(sc), lw = Math.max(1 * ctx.u, cell * 0.035), T1 = 0.25, per = jzClamp(c.dur * 0.4 / n, 0.04, 0.11);
        var TH = ld1_H(ctx) + 'var ga=oc(time/0.35)*K,typed=Math.floor((time-' + T1 + ')/' + jzN(per) + ');function ra(r){return cl(ga*' + R + '-r);}';
        // grid: frame + paper (unrolls downward), then per-row black cells and rules
        var GS = jzShapeLayer(ctx, 'crossword grid', 0, 0);
        var order = [];
        for (i = 0; i < words.length; i++) for (j = 0; j < words[i].length; j++) order.push([i, j]);
        var WI = [], WJ = [];
        for (i = 0; i < order.length; i++) { WI.push(order[i][0]); WJ.push(order[i][1]); }
        var HL = TH + 'var WI=' + ld1_arr(WI) + ',RW=' + ld1_arr(rowsW) + ',C0=' + ld1_arr(c0) + ',WJ=' + ld1_arr(WJ) + ';var cur=(typed>=0&&typed<' + n + ')?typed:-1,cw=cur>=0?WI[cur]:-1;';
        var gc = jzGrp(GS, 'cursor'); ld1_rr(gc, cell, cell, 0); jzAddFill(gc, sc.accent);
        ld1_gPos(gc, HL + 'cur<0?[0,0]:[' + jzN(gx + cell / 2) + '+(C0[cw]+WJ[cur])*' + jzN(cell) + ',' + jzN(gy + cell / 2) + '+RW[cw]*' + jzN(cell) + ']');
        ld1_gOp(gc, HL + 'cur<0?0:50');
        for (i = 0; i < words.length; i++) {
            var gw2 = jzGrp(GS, 'word ' + (i + 1)); ld1_rr(gw2, words[i].length * cell, cell, 0, gx + (c0[i] + words[i].length / 2) * cell, gy + (rowsW[i] + 0.5) * cell); jzAddFill(gw2, sc.accent);
            ld1_gOp(gw2, HL + '(cw===' + i + ')?20:(typed>=' + n + '?12:0)');
        }
        for (r = 0; r < R; r++) {
            var gr = jzGrp(GS, 'row ' + (r + 1));
            ld1_sub(gr, 'rules'); ld1_sub(gr, 'black');
            var grl = jzVecs(gr).property('rules'), gbl = jzVecs(gr).property('black');     // fetched after both exist (AE rule)
            ld1_rr(grl, gw, lw * 0.6, 0, gx + gw / 2, gy + (r + 1) * cell);
            for (cc = 0; cc < C; cc++) ld1_rr(grl, lw * 0.6, cell, 0, gx + (cc + 1) * cell, gy + (r + 0.5) * cell);
            jzAddFill(grl, inkC); ld1_gOp(grl, '55');
            var anyB = false;
            for (cc = 0; cc < C; cc++) if (black[key(r, cc)]) { ld1_rr(gbl, cell + 0.5, cell + 0.5, 0, gx + (cc + 0.5) * cell, gy + (r + 0.5) * cell); anyB = true; }
            if (anyB) jzAddFill(gbl, inkC);
            ld1_gOp(gr, TH + 'ra(' + r + ')*100');
        }
        // paper, then frame: each group is finished before the next one is added (AE invalidates older sibling refs)
        var gp = jzGrp(GS, 'paper'); ld1_rr(gp, gw, gh, 0, gw / 2, gh / 2); jzAddFill(gp, paperC);
        jzGX(gp).property('ADBE Vector Position').setValue([gx, gy]);
        ld1_gSc(gp, TH + '[100,ga*100]');
        var gf = jzGrp(GS, 'frame'); ld1_rr(gf, gw + lw * 4, gh + lw * 4, 0, gw / 2, gh / 2); jzAddFill(gf, inkC);
        jzGX(gf).property('ADBE Vector Position').setValue([gx, gy]);
        jzGX(gf).property('ADBE Vector Anchor').setValue([0, -lw * 2]);
        ld1_gSc(gf, TH + 'var h=' + jzN(gh) + ';[100,(h*ga+' + jzN(lw * 4) + ')/(h+' + jzN(lw * 4) + ')*100]');
        ld1_opx(ctx, GS, 'K');
        // clue numbers (one glyph per digit) + pencilled entries
        var ng = [], NP = [], NR = [], pg = [], PP = [], PR = [];
        for (r = 0; r < R; r++) for (cc = 0; cc < C; cc++) {
            var nm = num[key(r, cc)];
            if (nm) { var ds = jzChars(String(nm)); for (j = 0; j < ds.length; j++) { ng.push(ds[j]); NP.push([gx + cc * cell + cell * 0.07 + cell * 0.2 * 0.62 * (j + 0.5), gy + r * cell + cell * 0.15]); NR.push(r); } }
            if (pencil[key(r, cc)]) { pg.push(pencil[key(r, cc)]); PP.push([gx + (cc + 0.5) * cell, gy + r * cell + cell * 0.56]); PR.push(r); }
        }
        if (ng.length) {
            var NG = ld1_G(ctx, ng, { font: jzMonoF(ctx), size: cell * 0.2, color: inkC, name: 'crossword numbers', cx: W / 2, cy: H / 2 });
            ld1_place(NG, NP); jzAnimator(NG.L, 'JZ Row Show', [['ADBE Text Opacity', 0]], TH + 'var NR=' + ld1_arr(NR) + ';(1-0.8*ra(NR[textIndex-1]))*100'); ld1_opx(ctx, NG.L, 'K');
        }
        if (pg.length) {
            var PGL = ld1_G(ctx, pg, { font: jzBodyF(ctx), size: cell * 0.46, color: jzMixHex(paperC, inkC, 0.38), name: 'crossword pencil', cx: W / 2, cy: H / 2 });
            ld1_place(PGL, PP); jzAnimator(PGL.L, 'JZ Pencil Show', [['ADBE Text Opacity', 0]], TH + 'var PR=' + ld1_arr(PR) + ',r=PR[textIndex-1];(1-oe((time-0.3-r*0.03)/0.3)*ra(r))*100'); ld1_opx(ctx, PGL.L, 'K');
        }
        // the lyric, typed into its row(s) one cell after another
        var gl = [], LP = [], bb = null;
        for (i = 0; i < order.length; i++) {
            var wi = order[i][0], jj = order[i][1], lx = gx + (c0[wi] + jj + 0.5) * cell, ly = gy + (rowsW[wi] + 0.56) * cell;
            gl.push(words[wi][jj]); LP.push([lx, ly]);
            bb = jzUnion(bb, ld1_box(lx - cell / 2, ly - cell * 0.56, lx + cell / 2, ly + cell * 0.44));
        }
        var G = ld1_G(ctx, gl, { font: font, size: cell * 0.7, color: inkC, name: t0, cx: W / 2, cy: H / 2 });
        ld1_place(G, LP);
        jzAnimator(G.L, 'JZ Typed', [['ADBE Text Opacity', 0]], TH + '(time<' + T1 + '+(textIndex-1)*' + jzN(per) + ')?100:0');
        ld1_anim(ctx, G.L, { mi: ld1_mi(ctx, T1), noHold: ld1_plateHold(ctx) });
        // clues
        if (clue) {
            var fs = fsC, cxl = below ? gx : gx + gw + W * 0.035, cyl = below ? gy + gh + fs * 1.6 : gy + fs;
            var n1 = num[key(rowsW[0], c0[0])] || 1, rom = jzRomajiOf(ctx);
            var lines = [['\u30E8\u30B3\u306E\u30AB\u30AE', sc.accent, jzMonoF(ctx)], [n1 + '  ' + (c.note || rom || '(' + n + ')'), sc.fg, jzBodyF(ctx)], ['\u30BF\u30C6\u306E\u30AB\u30AE', sc.accent, jzMonoF(ctx)], [(numCount > 3 ? 3 : 2) + '  \u2500', sc.sub, jzBodyF(ctx)]];
            for (i = 0; i < lines.length; i++) {
                var tt = jzChars(lines[i][0]).length > 22 ? jzChars(lines[i][0]).slice(0, 21).join('') + '\u2026' : lines[i][0];
                var yy = below ? cyl + (i % 2) * fs * 1.7 : cyl + i * fs * 1.9 + (i >= 2 ? fs * 0.8 : 0), xx = below && i >= 2 ? cxl + gw * 0.55 : cxl;
                var CL = jzText(ctx, tt, { font: lines[i][2], size: fs, align: 'left', x: xx, y: yy, track: 0.08, color: lines[i][1], name: 'crossword clue ' + (i + 1) });
                ld1_opx(ctx, CL, 'oc((time-0.35)/0.4)*K');
            }
        }
        return bb;
    }
});

// ================================================================ pack layoutsD part 2 (AE port of src/11p_layoutsD.js, entries 18-34)
// wordSearch, puzzle, shadowPlay, kaleido, dominoes, burst, fisheye, wall, origami, zipper, sliceStack, glitchGrid,
// mosaicTiles, maskReveal, contour, halftoneBig, stencil
//
// Techniques used here:
// - clipped copies of the lyric (puzzle pieces, kaleidoscope wedges, blind slats, stacked slices, mosaic) are ONE precomp that
//   holds the plate + the lyric (still one editable text layer, with its enter / hold / exit), used several times with masks.
// - projected / folding polygons (walls, paper flaps) are fixed unit shapes mapped by the shape group transform
//   (position + rotation + skew + scale = any 2D affine map), driven by expressions: no animated paths needed.
// - pattern-filled lyrics (maskReveal, halftoneBig) use the lyric as an alpha track matte for a pattern layer.

var LD2_CY = 0.38;     // glyph centre above the baseline (em)
// extra easing / hash for expressions (append after jzTH)
var LD2_FNS = 'function ioc(x){x=cl(x);return x<0.5?4*x*x*x:1-Math.pow(-2*x+2,3)/2;}function ios(x){x=cl(x);return -(Math.cos(Math.PI*x)-1)/2;}' +
    'function hr(a,b){var x=Math.sin(a*12.9898+b*78.233)*43758.5453;return x-Math.floor(x);}\n';
var LD2_KANA = '\u3042\u3044\u3046\u3048\u304A\u304B\u304D\u304F\u3051\u3053\u3055\u3057\u3059\u305B\u305D\u305F\u3061\u3064\u3066\u3068\u306A\u306B\u306C\u306D\u306E\u306F\u3072\u3075\u3078\u307B\u307E\u307F\u3080\u3081\u3082\u3084\u3086\u3088\u3089\u308A\u308B\u308C\u308D\u308F\u3092\u3093';
// affine map of a unit shape ((0,0) (100,0) (0,100)) onto A, B, C: rotation th, scale sx / sy, skew kk (group transform)
var LD2_AFF = 'var m11=(B[0]-A[0])/100,m21=(B[1]-A[1])/100,m12=(C[0]-A[0])/100,m22=(C[1]-A[1])/100,th=Math.atan2(m21,m11),cs=Math.cos(th),sn=Math.sin(th),' +
    'sx=Math.sqrt(m11*m11+m21*m21),sy=-m12*sn+m22*cs,kk=Math.abs(sy)<1e-6?0:(m12*cs+m22*sn)/sy;';

/* ---------------------------------------------------------------- small helpers */
function ld2_TH(ctx) { return jzTH(ctx) + LD2_FNS; }
function ld2_box(x0, y0, x1, y1) { return { x0: x0, y0: y0, x1: x1, y1: y1, cx: (x0 + x1) / 2, cy: (y0 + y1) / 2 }; }
function ld2_miAt(ctx, t) { return Math.max(0, t) / Math.max(0.005, ctx.cut.stagger || 0.04); }
function ld2_plateHold(ctx) { return jzIndexOf(['still', 'jitter', 'breathe', 'glitchtick'], ctx.cut.hold || 'still') < 0; }
function ld2_isSmall(ch) { return '\u3041\u3043\u3045\u3047\u3049\u3063\u3083\u3085\u3087\u308E\u30A1\u30A3\u30A5\u30A7\u30A9\u30C3\u30E3\u30E5\u30E7\u30EE\u30F5\u30F6'.indexOf(ch) >= 0; }
function ld2_light(sc) { return jzLum(sc.fg) > jzLum(sc.bg) ? sc.fg : sc.bg; }
function ld2_dark(sc) { return jzLum(sc.fg) > jzLum(sc.bg) ? sc.bg : sc.fg; }
function ld2_onCol(sc, fill) {
    var c = [sc.bg, sc.fg, sc.ink, sc.accent, sc.sub], best = null, bv = 0;
    for (var i = 0; i < c.length; i++) { if (!c[i] || String(c[i]).toUpperCase() === String(fill).toUpperCase()) continue; var k = jzContrast(c[i], fill); if (k > bv) { bv = k; best = c[i]; } }
    return bv >= 2.4 ? best : (jzLum(fill) > 0.5 ? '#111111' : '#FFFFFF');
}
function ld2_plateCol(sc, pref) { for (var i = 0; i < pref.length; i++) if (pref[i] && jzContrast(pref[i], sc.bg) >= 1.6) return pref[i]; return sc.fg; }
function ld2_shade(sc, c, s) { return s >= 0 ? jzMixHex(c, ld2_light(sc), Math.min(0.9, s)) : jzMixHex(c, ld2_dark(sc), Math.min(0.9, -s)); }
function ld2_rgb(hex) { var c = jzHex(hex); return '[' + jzN(c[0]) + ',' + jzN(c[1]) + ',' + jzN(c[2]) + ',1]'; }
// glyph pool for fillers: the line's own glyphs + every third hiragana
function ld2_pool(c) {
    var own = jzChars(jzStrip((c.lineText || '') + c.text)), out = [], i;
    for (i = 0; i < own.length; i++) if (!jzIsPunct(own[i]) && !ld2_isSmall(own[i]) && own[i] !== '\u30FC' && !jzIsLatin(own[i]) && !/["'\\\s]/.test(own[i])) out.push(own[i]);
    var ka = jzChars(jzPool('hira'));
    for (i = 0; i < ka.length; i += 3) out.push(ka[i]);
    return out;
}
// advance width in em (full-width glyphs are exactly 1 em)
function ld2_adv(ch) {
    var c = ch.charCodeAt(0);
    if (ch === ' ') return 0.34;
    if (c < 0x80) {
        if (/[A-Z]/.test(ch)) return /[MW]/.test(ch) ? 0.9 : (ch === 'I' ? 0.32 : 0.7);
        if (/[a-z]/.test(ch)) return /[mw]/.test(ch) ? 0.88 : /[ijl]/.test(ch) ? 0.28 : /[frt]/.test(ch) ? 0.42 : 0.61;
        if (/[0-9]/.test(ch)) return 0.62;
        return /[.,:;!'|]/.test(ch) ? 0.28 : 0.42;
    }
    if (c < 0x250) return 0.56;
    if (c >= 0xFF61 && c <= 0xFF9F) return 0.5;
    return 1;
}

/* ---- chunking (browser chunksK): k balanced groups of words, long words split at natural points */
function ld2_splitWord(w) {
    var ch = jzChars(w), n = ch.length, i;
    if (/[A-Za-z]/.test(w)) {
        if (/[^\x00-\x7F]/.test(w)) { var cw = jzChunk(w); if (cw.length > 1) return cw; }
        return n > 10 ? [w.substr(0, Math.ceil(w.length / 2)), w.substr(Math.ceil(w.length / 2))] : [w];
    }
    if (n < 2) return [w];
    var sp = jzSplitLines(w, Math.ceil(n / 2)).split('\r'), o = [];
    for (i = 0; i < sp.length; i++) if (sp[i]) o.push(sp[i]);
    return o;
}
function ld2_chunksK(text, k) {
    var t = jzTrim(String(text || '')), words = [], i, j, g;
    if (!t) return [''];
    if (/\s/.test(t) && jzHasLatin(t)) {
        var ws = t.split(/\s+/), tmp = [];
        for (i = 0; i < ws.length; i++) if (ws[i]) tmp.push(ws[i]);
        for (i = 0; i < tmp.length; i++) words.push({ t: tmp[i], sp: i < tmp.length - 1 });
    } else {
        var cs = jzChunk(jzStrip(t));
        for (i = 0; i < cs.length; i++) { var w0 = jzTrim(cs[i]); if (w0) words.push({ t: w0, sp: false }); }
    }
    if (!words.length) words = [{ t: t, sp: false }];
    k = Math.max(1, Math.min(k, jzCount(t)));
    var hard = {};
    var gl = function (w) { return jzCount(w.t); };
    var splitAt = function (idx) {
        var w = words[idx], parts = ld2_splitWord(w.t), rep = [];
        if (parts.length < 2) { hard[w.t] = 1; return false; }
        for (var q = 0; q < parts.length; q++) rep.push({ t: parts[q], sp: q === parts.length - 1 ? w.sp : false });
        words = words.slice(0, idx).concat(rep, words.slice(idx + 1));
        return true;
    };
    for (var guard = 0; words.length < k && guard < 20; guard++) {
        var bi = -1, bl = 1;
        for (i = 0; i < words.length; i++) if (!hard[words[i].t] && gl(words[i]) > bl) { bl = gl(words[i]); bi = i; }
        if (bi < 0) break;
        splitAt(bi);
    }
    var part = function () {
        var n = words.length, kk = Math.min(k, n), pre = [0], dp = [], by = [], a, b;
        for (a = 0; a < n; a++) pre.push(pre[a] + gl(words[a]) + 0.4);
        var tgt = pre[n] / kk;
        for (g = 0; g <= kk; g++) { dp.push([]); by.push([]); for (a = 0; a <= n; a++) { dp[g].push(1e18); by[g].push(0); } }
        dp[0][0] = 0;
        for (g = 1; g <= kk; g++) for (a = g; a <= n; a++) for (b = g - 1; b < a; b++) { var d = pre[a] - pre[b] - tgt, v = dp[g - 1][b] + d * d; if (v < dp[g][a]) { dp[g][a] = v; by[g][a] = b; } }
        var cuts = [], p = n;
        for (g = kk; g >= 1; g--) { cuts.unshift(p); p = by[g][p]; }
        var out = [], prev = 0;
        for (a = 0; a < cuts.length; a++) { out.push(words.slice(prev, cuts[a])); prev = cuts[a]; }
        return out;
    };
    var groups = part();
    for (var it = 0; it < 3 && groups.length > 1; it++) {
        var mx = -1, mn = 1e9, gi = 0;
        for (i = 0; i < groups.length; i++) { var sum = 0; for (j = 0; j < groups[i].length; j++) sum += gl(groups[i][j]); if (sum > mx) { mx = sum; gi = i; } if (sum < mn) mn = sum; }
        if (mx <= mn * 1.7 + 1) break;
        var bw = null, big = groups[gi];
        for (j = 0; j < big.length; j++) if (!hard[big[j].t] && gl(big[j]) >= 4 && (!bw || gl(big[j]) > gl(bw))) bw = big[j];
        if (!bw || !splitAt(jzIndexOf(words, bw))) break;
        groups = part();
    }
    var res = [];
    for (i = 0; i < groups.length; i++) { var s = ''; for (j = 0; j < groups[i].length; j++) s += groups[i][j].t + (j < groups[i].length - 1 && groups[i][j].sp ? ' ' : ''); if (s) res.push(s); }
    return res.length ? res : [t];
}
// balanced display line breaks at chunk boundaries (AE line break = \r)
function ld2_brk(text, maxPer) { var t = jzFlat(text), n = jzCount(t); return n <= maxPer ? t : ld2_chunksK(t, Math.ceil(n / maxPer)).join('\r'); }
// rows of glyph slots (a ' ' slot is a word gap)
function ld2_rowsOf(text, maxPer) {
    var t = jzFlat(text), n = jzCount(t), rs = n > maxPer ? ld2_chunksK(t, Math.ceil(n / maxPer)) : [t], out = [];
    for (var i = 0; i < rs.length; i++) out.push(jzChars(jzTrim(rs[i])));
    return out;
}

/* ---- text: measure / fit / place */
function ld2_meas(ctx, str, font, track, lead) {
    var L = jzText(ctx, str, { font: font, size: 100, color: '#FFFFFF', x: -9999, y: -9999, track: track || 0, leading: /\r/.test(str) ? 100 * (lead || 1.2) : null });
    var r = jzRect(L); L.remove();
    return { w: Math.max(1, r.width), h: Math.max(1, r.height) };
}
// browser J.fitSize: the size at which the block fits maxW x maxH
function ld2_fit(ctx, str, font, maxW, maxH, track, lead) { var m = ld2_meas(ctx, str, font, track, lead); return 100 * Math.min(maxW / m.w, maxH / m.h); }
function ld2_measAt(ctx, str, font, size, track, lead) { var m = ld2_meas(ctx, str, font, track, lead); return { w: m.w * size / 100, h: m.h * size / 100 }; }
// text block of a given size centred at (o.x, o.y); o { color, track, lead, fill, stroke, strokeColor, strokeOver, name, align }
function ld2_T(ctx, str, font, size, o) {
    return jzText(ctx, str, { font: font, size: size, color: o.color || ctx.sc.fg, x: o.x || 0, y: o.y || 0, align: o.align, track: o.track || 0,
        leading: /\r/.test(str) ? size * (o.lead || 1.2) : null, fill: o.fill, stroke: o.stroke, strokeColor: o.strokeColor, strokeOver: o.strokeOver, name: o.name });
}
// one glyph whose em centre sits at (x, y)
function ld2_G(ctx, ch, font, size, x, y, col, name) {
    var L = jzText(ctx, ch, { font: font, size: size, color: col, x: x, y: y, name: name || ch });
    jzXf(L, 'ADBE Anchor Point').setValue([0, -LD2_CY * size]);
    jzXf(L, 'ADBE Position').setValue([x, y]);
    return L;
}
// "one glyph per line" layer: glyph i rests with its em centre at (0, i*LD - CY*fs) in layer space; the layer's anchor and
// position are both (ax, ay) so layer space = comp space and layer-level motion pivots round (ax, ay)
function ld2_glyphs(ctx, chars, o) {
    var t = [], i;
    for (i = 0; i < chars.length; i++) t.push(chars[i] === '' ? ' ' : chars[i]);
    var L = jzText(ctx, t.join('\r'), { font: o.font, size: o.size, color: o.color || '#FFFFFF', x: 0, y: 0, track: 0, leading: o.size, fill: o.fill, stroke: o.stroke, strokeColor: o.strokeColor, name: o.name || 'glyphs' });
    jzXf(L, 'ADBE Anchor Point').setValue([o.ax || 0, o.ay || 0]); jzXf(L, 'ADBE Position').setValue([o.ax || 0, o.ay || 0]);
    return L;
}

/* ---- text animators (expression selectors): body sets a (alpha) / sx, sy (scale) / dx, dy (px) / r (deg) / m (colour mix) / k (skew deg) */
function ld2_opAnim(L, name, head, body) { return jzAnimator(L, name, [['ADBE Text Opacity', 0]], head + 'var a=1;' + body + ';(1-Math.max(0,Math.min(1,a)))*100'); }
function ld2_scAnim(L, name, head, body) { return jzAnimator(L, name, [['ADBE Text Scale 3D', [300, 300, 100]]], head + 'var sx=1,sy=1;' + body + ';[Math.max(-1,Math.min(1,(sx-1)/2))*100,Math.max(-1,Math.min(1,(sy-1)/2))*100,0]'); }
function ld2_posAnim(L, name, KB, head, body) { return jzAnimator(L, name, [['ADBE Text Position 3D', [KB, KB, 0]]], head + 'var dx=0,dy=0;' + body + ';[dx/' + jzN(KB) + '*100,dy/' + jzN(KB) + '*100,0]'); }
function ld2_rotAnim(L, name, R, head, body) { return jzAnimator(L, name, [['ADBE Text Rotation', R]], head + 'var r=0;' + body + ';Math.max(-1,Math.min(1,r/' + jzN(R) + '))*100'); }
function ld2_colAnim(L, name, hex, head, body) { return jzAnimator(L, name, [['ADBE Text Fill Color', jzHex(hex)]], head + 'var m=0;' + body + ';Math.max(0,Math.min(1,m))*100'); }
function ld2_skAnim(L, name, head, body) { return jzAnimator(L, name, [['ADBE Text Skew', 85]], head + 'var k=0;' + body + ';Math.max(-85,Math.min(85,k))/85*100'); }

/* ---- shapes / layers */
function ld2_gX(g, mn, ex) { jzSetExpr(jzGX(g).property(mn), ex); }
function ld2_path(g, sh) { var p = jzVecs(g).addProperty('ADBE Vector Shape - Group'); p.property('ADBE Vector Shape').setValue(sh); return p; }
function ld2_sub(g, name) { var n = jzVecs(g).addProperty('ADBE Vector Group'); if (name) n.name = name; return n; }
// group holding a unit triangle ('tri': A, B, C) or parallelogram ('par': A, B, A+C-A) mapped by expressions; abc defines var A, B, C
function ld2_affGroup(S, name, kind, col, head, abc) {
    var g = jzGrp(S, name);
    jzAddPath(g, kind === 'tri' ? [[0, 0], [100, 0], [0, 100]] : [[0, 0], [100, 0], [100, 100], [0, 100]], true);
    jzAddFill(g, col);
    var e = head + abc + LD2_AFF;
    ld2_gX(g, 'ADBE Vector Position', e + '[A[0],A[1]]');
    ld2_gX(g, 'ADBE Vector Rotation', e + 'th*180/Math.PI');
    ld2_gX(g, 'ADBE Vector Scale', e + '[sx*100,sy*100]');
    ld2_gX(g, 'ADBE Vector Skew', e + 'Math.max(-85,Math.min(85,-Math.atan(kk)*180/Math.PI))');
    return g;
}
// planar quad Q[0..3] (expression 'var Q=[[x,y],...]') as a parallelogram + a triangle that overlap (no seam)
function ld2_quad(S, name, col, head, qExpr) {
    ld2_affGroup(S, name + ' b', 'tri', col, head, qExpr + 'var A=Q[0],B=Q[2],C=Q[3];');
    ld2_affGroup(S, name + ' a', 'par', col, head, qExpr + 'var A=Q[0],B=Q[1],C=[Q[0][0]+Q[2][0]-Q[1][0],Q[0][1]+Q[2][1]-Q[1][1]];');
}
function ld2_null(ctx, name, x, y) {
    var N = ctx.comp.layers.addNull(ctx.comp.duration); N.name = name;
    jzXf(N, 'ADBE Anchor Point').setValue([0, 0]); jzXf(N, 'ADBE Position').setValue([x || 0, y || 0]);
    return N;
}
// parent a layer without moving it, then place it in the parent's space
function ld2_ng(list) { for (var i = 0; i < list.length; i++) if (list[i]) jzNoGhost(list[i]); }   // browser: drawn with ghost off
function ld2_parent(L, P, x, y) { L.setParentWithJump(P); jzXf(L, 'ADBE Position').setValue([x, y]); }
// alpha track matte: L shows only inside M (M directly above L)
function ld2_matte(L, M, inverted) {
    var tt = inverted ? TrackMatteType.ALPHA_INVERTED : TrackMatteType.ALPHA, ok = false;
    if (typeof L.setTrackMatte === 'function') { try { L.setTrackMatte(M, tt); ok = true; } catch (e) { ok = false; } }
    if (!ok) { try { L.trackMatteType = tt; } catch (e2) { jzWarn('matte: ' + e2.toString()); } }
}
// precomp of the content comp's size / length; its ctx builds layers inside it (the lyric stays one editable text layer)
function ld2_precomp(ctx, name, w, h) {
    var pc = app.project.items.addComp(name, Math.round(w || ctx.W), Math.round(h || ctx.H), 1, ctx.comp.duration, ctx.comp.frameRate);
    try { pc.parentFolder = ctx.comp.parentFolder; } catch (e) {}
    var pctx = jzCopy(ctx); pctx.comp = pc;
    if (w) { pctx.W = pc.width; pctx.H = pc.height; }
    return { comp: pc, ctx: pctx };
}
function ld2_use(ctx, pc, name) { var L = ctx.comp.layers.add(pc); L.name = name; L.startTime = 0; return L; }
function ld2_mask(L, sh, mode) {
    var m = L.property('ADBE Mask Parade').addProperty('ADBE Mask Atom');
    m.property('ADBE Mask Shape').setValue(sh);
    if (mode) { try { m.maskMode = mode; } catch (e) {} }
    return m;
}
function ld2_shape(pts, closed) { var sh = new Shape(); sh.vertices = pts; sh.closed = closed !== false; return sh; }
function ld2_rectShape(x0, y0, x1, y1) { return ld2_shape([[x0, y0], [x1, y0], [x1, y1], [x0, y1]], true); }

/* ================================================================== 18 wordSearch \u2014 \u6587\u5B57\u63A2\u3057 */
jzReg('layout', 'wordSearch', {
    plan: function (rng, cut, st) {
        var port = cut.H > cut.W * 1.08, n = cut.n, dirs = ['h', 'h'];
        if (port && n <= 14) dirs.push('v', 'v', 'v');
        if (n <= 8) dirs.push('d');
        return { font: rng.pick(jzFontsOf(st, ['display', 'body'])), gf: rng.pick(jzFontsOf(st, ['body', 'display'])), dir: rng.pick(dirs), decoys: rng.int(1, 3), pad: rng.int(1, 3) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, s = c.seed, u = jzU(ctx), port = jzPortrait(ctx), i, r, q;
        var all = jzChars(jzFlat(c.text)), chs = [];
        for (i = 0; i < all.length; i++) if (all[i] !== ' ') chs.push(all[i]);
        var n = chs.length;
        if (!n) return null;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), gf = jzP(ctx, 'gf', jzBodyF(ctx));
        var dir = jzP(ctx, 'dir', 'h'), pad = jzP(ctx, 'pad', 2), decoys = jzP(ctx, 'decoys', 2), C, R;
        if (dir === 'v' && n > 14) dir = 'h';
        if (dir === 'd' && n > 9) dir = 'h';
        if (dir === 'h') { C = n + pad * 2; R = port ? Math.max(7, Math.round(C * 1.4)) : Math.max(5, Math.round(C * 0.5)); if (port && C > 9) C = n + 1; }
        else if (dir === 'v') { R = n + pad * 2; C = Math.max(5, Math.round(R * (W / H) * 1.1)); }
        else { C = n + 2; R = n + 2; }
        C = Math.min(C, 18); R = Math.min(R, 18, Math.max(dir === 'v' ? n + 1 : dir === 'd' ? n : 5, Math.floor(150 / C)));
        if (dir === 'v') C = Math.min(C, Math.max(5, Math.floor(150 / R)));
        var cell = Math.min(W * 0.86 / C, H * 0.8 / R, u * 0.16), gx = W / 2 - C * cell / 2, gy = H / 2 - R * cell / 2;
        var r0, c0, dr = 0, dc = 1;
        if (dir === 'h') { r0 = Math.floor(R / 2) + (jzHash(s, 1) % 3) - 1; c0 = Math.floor((C - n) / 2); }
        else if (dir === 'v') { dr = 1; dc = 0; c0 = Math.floor(C / 2) + (jzHash(s, 1) % 3) - 1; r0 = Math.floor((R - n) / 2); }
        else { dr = 1; dc = 1; r0 = Math.floor((R - n) / 2); c0 = Math.floor((C - n) / 2); }
        r0 = jzClamp(r0, 0, Math.max(0, R - 1 - dr * (n - 1))); c0 = jzClamp(c0, 0, Math.max(0, C - 1 - dc * (n - 1)));
        var onPath = {};
        for (i = 0; i < n; i++) onPath[(r0 + dr * i) * 64 + (c0 + dc * i)] = i;
        var pool = ld2_pool(c), NP = pool.length, TH = ld2_TH(ctx), GA = 'var GA=oc(time/0.4)*K;';
        // panel
        var PN = jzRectLayer(ctx, 'grid panel', W / 2, H / 2, C * cell + cell * 0.7, R * cell + cell * 0.7, jzMixHex(sc.bg, sc.fg, jzLum(sc.bg) < 0.5 ? 0.06 : 0.05),
            { round: cell * 0.3, stroke: jzMixHex(sc.bg, sc.fg, 0.2), strokeW: Math.max(1, u * 0.0018) });
        jzSetExpr(jzXf(PN, 'ADBE Opacity'), TH + GA + 'value*GA'); jzNoGhost(PN);
        // decoy capsules (words "found" earlier)
        var rr = cell * 0.42, lwC = Math.max(1.5, cell * 0.06), DS = jzShapeLayer(ctx, 'found words', 0, 0), nd = 0;
        for (var d = 0; d < decoys; d++) {
            var ra = jzHash(s, d, 21) % R, len = 3 + jzHash(s, d, 22) % 2, ca = jzHash(s, d, 23) % Math.max(1, C - len), clash = false;
            for (q = 0; q < len; q++) if (onPath[ra * 64 + ca + q] != null) clash = true;
            if (clash) continue;
            var gd = jzGrp(DS, 'word ' + (d + 1)), L0 = (len - 1) * cell;
            jzAddRect(gd, L0 + rr * 2, rr * 2, rr, L0 / 2, 0.001); jzAddStroke(gd, sc.sub, lwC);
            jzGX(gd).property('ADBE Vector Position').setValue([gx + (ca + 0.5) * cell, gy + (ra + 0.5) * cell]);
            nd++;
        }
        if (nd) { jzSetExpr(jzXf(DS, 'ADBE Opacity'), TH + GA + '50*GA'); jzNoGhost(DS); } else DS.remove();
        // the letter grid: one text, rows = lines, cell pitch = glyph advance + tracking; rows fade in top to bottom
        var gs = cell * 0.56, rows = [];
        for (r = 0; r < R; r++) { var row = ''; for (q = 0; q < C; q++) row += onPath[r * 64 + q] != null ? '\u3000' : pool[jzHash(s, r, q, 9) % NP]; rows.push(row); }
        var GL = jzText(ctx, rows.join('\r'), { font: gf, size: gs, color: sc.sub, x: 0, y: 0, align: 'left', track: (cell - gs) / gs, leading: cell, name: 'letter grid' });
        jzXf(GL, 'ADBE Anchor Point').setValue([0, 0]);
        jzXf(GL, 'ADBE Position').setValue([gx + (cell - gs) / 2, gy + 0.5 * cell + LD2_CY * gs]);
        jzSetExpr(jzXf(GL, 'ADBE Opacity'), TH + '75*K'); jzNoGhost(GL);
        ld2_opAnim(GL, 'JZ Rows In', TH + GA, 'var rw=Math.floor((textIndex-1)/' + C + ');a=cl(GA*' + (R + 2) + '-rw)');
        // the find: a capsule sweeps along the lyric
        var sweep = jzClamp(c.dur * 0.3, 0.35, 0.8), stepL = cell * Math.sqrt(dr * dr + dc * dc);
        var FF = TH + 'var f=ioc((time-0.35)/' + jzN(sweep) + ')*(1-ic(PO*1.3)),LN=' + (n - 1) + '*f*' + jzN(stepL) + ';';
        var CP = jzShapeLayer(ctx, 'find', gx + (c0 + 0.5) * cell, gy + (r0 + 0.5) * cell), gc = jzGrp(CP, 'capsule');
        var rc = jzAddRect(gc, rr * 2, rr * 2, rr, 0, 0);
        jzSetExpr(rc.property('ADBE Vector Rect Size'), FF + '[LN+' + jzN(rr * 2) + ',' + jzN(rr * 2) + ']');
        jzSetExpr(rc.property('ADBE Vector Rect Position'), FF + '[LN/2,0]');
        jzAddStroke(gc, sc.accent, lwC); jzAddFill(gc, sc.accent, 30);
        jzXf(CP, 'ADBE Rotate Z').setValue(Math.atan2(dr, dc) * 180 / Math.PI);
        jzSetExpr(jzXf(CP, 'ADBE Opacity'), FF + '(f>0?100:0)*K'); jzNoGhost(CP);
        // the lyric glyphs light up as the capsule passes
        var bb = null, lit = FF + 'var LT=cl(((' + (n > 1 ? 'f*' + (n - 1) : 'f') + ')-IX+0.6)/0.6);';
        for (i = 0; i < n; i++) {
            var x = gx + (c0 + dc * i + 0.5) * cell, y = gy + (r0 + dr * i + 0.5) * cell;
            var G = ld2_G(ctx, chs[i], font, gs, x, y, sc.sub), hd = 'var IX=' + i + ';' + lit;
            ld2_scAnim(G, 'JZ Lit Size', hd, 'sx=1+LT*' + jzN(0.08 / 0.56) + ';sy=sx');
            ld2_colAnim(G, 'JZ Lit Colour', sc.fg, hd, 'm=LT>0.5?1:0');
            ld2_opAnim(G, 'JZ Lit Alpha', hd, 'a=0.75+0.25*LT');
            jzAnimate(ctx, G, { mi: ld2_miAt(ctx, 0.1 + i * 0.02), noHold: ld2_plateHold(ctx) });
            bb = jzUnion(bb, ld2_box(x - cell / 2, y - cell / 2, x + cell / 2, y + cell / 2));
        }
        return bb;
    }
});

/* ================================================================== 19 puzzle \u2014 \u30D1\u30BA\u30EB */
// jigsaw piece outline (x, y, w, h); e = [top, right, bottom, left] knob sides (+-1, 0 = straight)
function ld2_jigShape(x, y, w, h, e) {
    var V = [], I = [], O = [];
    var add = function (p, i, o) { V.push(p); I.push(i || [0, 0]); O.push(o || [0, 0]); };
    var edge = function (xa, ya, xb, yb, sg, lastE) {
        if (sg) {
            var dx = xb - xa, dy = yb - ya, L = Math.sqrt(dx * dx + dy * dy), ux = dx / L, uy = dy / L, nx = uy * sg, ny = -ux * sg;
            var P = function (t, k) { return [xa + ux * t * L + nx * k * L, ya + uy * t * L + ny * k * L]; };
            var a = P(0.37, 0), b1 = P(0.42, 0.1), b2 = P(0.3, 0.26), cc = P(0.5, 0.27), d1 = P(0.7, 0.26), d2 = P(0.58, 0.1), ee = P(0.63, 0);
            add(a, null, [b1[0] - a[0], b1[1] - a[1]]);
            add(cc, [b2[0] - cc[0], b2[1] - cc[1]], [d1[0] - cc[0], d1[1] - cc[1]]);
            add(ee, [d2[0] - ee[0], d2[1] - ee[1]], null);
        }
        if (!lastE) add([xb, yb]);
    };
    add([x, y]);
    edge(x, y, x + w, y, e[0]); edge(x + w, y, x + w, y + h, e[1]); edge(x + w, y + h, x, y + h, e[2]); edge(x, y + h, x, y, e[3], true);
    var sh = new Shape(); sh.vertices = V; sh.inTangents = I; sh.outTangents = O; sh.closed = true;
    return sh;
}
jzReg('layout', 'puzzle', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), plate: rng.pick(['accent', 'light', 'ink']), rows: rng.pick([2, 2, 3]), last: rng.chance(0.65), spread: rng.range(0.6, 1) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, s = c.seed, u = jzU(ctx), port = jzPortrait(ctx), i, k;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), text = ld2_brk(c.text, port ? 5 : 8), tr = 0.04, lead = 1.12;
        var size = Math.min(ld2_fit(ctx, text, font, W * 0.7, H * (port ? 0.3 : 0.4), tr, lead), u * 0.22), m = ld2_measAt(ctx, text, font, size, tr, lead);
        var bw = Math.min(W * 0.92, m.w + size * 1.3), bh = Math.min(H * 0.8, m.h + size * 1.1), x0 = W / 2 - bw / 2, y0 = H / 2 - bh / 2;
        var R = jzP(ctx, 'rows', 2), C = Math.max(2, Math.min(Math.round(bw / bh * R), Math.floor(28 / R))), pw = bw / C, ph = bh / R, last = !!jzP(ctx, 'last', true), spread = jzP(ctx, 'spread', 0.8);
        var pl = jzP(ctx, 'plate', 'accent');
        var plate = pl === 'light' ? jzMixHex(ld2_light(sc), sc.accent, 0.08) : ld2_plateCol(sc, pl === 'accent' ? [sc.accent, sc.ink] : [sc.ink, sc.accent]), tc = ld2_onCol(sc, plate);
        var N = R * C, ord = [], rank = [];
        for (i = 0; i < N; i++) ord.push(i);
        ord.sort(function (a, b) { return jzR(s, a, 3) - jzR(s, b, 3); });
        for (i = 0; i < N; i++) rank[ord[i]] = i;
        var fd = 0.42, span = jzClamp(c.dur * 0.3, 0.3, 0.8);
        var tOf = function (p) { var q = rank[p]; if (last && q === N - 1) return 0.1 + span + 0.25; return 0.06 + (N > 1 ? q / (N - 1) : 0) * span * (last ? 0.85 : 1); };
        var hS = function (r2, c2) { return jzHash(s, r2, c2, 1) & 1 ? 1 : -1; };
        var vS = function (r2, c2) { return jzHash(s, r2, c2, 2) & 1 ? 1 : -1; };
        var edges = function (r2, c2) { return [r2 === 0 ? 0 : -hS(r2 - 1, c2), c2 === C - 1 ? 0 : vS(r2, c2), r2 === R - 1 ? 0 : hS(r2, c2), c2 === 0 ? 0 : -vS(r2, c2 - 1)]; };
        // the picture: plate + lyric in one precomp
        var PC = ld2_precomp(ctx, 'JZ puzzle art'), pctx = PC.ctx;
        jzRectLayer(pctx, 'plate', W / 2, H / 2, bw + pw * 0.8, bh + ph * 0.8, plate);
        var L = ld2_T(pctx, text, font, size, { color: tc, x: W / 2, y: H / 2, track: tr, lead: lead });
        jzAnimate(pctx, L, { mi: 0, noHold: ld2_plateHold(ctx) });
        var bb = jzBB(L);
        // pieces: the precomp masked by a jigsaw outline, flying in (in rank order, later ones on top) and apart at the end
        var TH = ld2_TH(ctx), dia = Math.sqrt(W * W + H * H), heads = [], shapes = [], homes = [];
        for (k = 0; k < N; k++) {
            var p = ord[k], r = Math.floor(p / C), cc = p % C, hx = x0 + (cc + 0.5) * pw, hy = y0 + (r + 0.5) * ph;
            var ang = jzR(s, p, 4) * Math.PI * 2, dist = dia * 0.45 * spread * (0.6 + 0.4 * jzR(s, p, 5));
            var hd = TH + 'var HX=' + jzN(hx) + ',HY=' + jzN(hy) + ',CA=' + jzN(Math.cos(ang)) + ',SA=' + jzN(Math.sin(ang)) + ',DI=' + jzN(dist) + ',T0=' + jzN(tOf(p)) +
                ',RK=' + jzN(k / N) + ',R6=' + jzN(jzR(s, p, 6) * 2 - 1) + ',R7=' + jzN(jzR(s, p, 7) * 2 - 1) + ';' +
                'var f=cl((time-T0)/' + jzN(fd) + '),fo=cl(PO*1.5-RK*0.5),e=f<1?oc(f):1,eo=ic(fo),SX=HX+CA*DI,SY=HY+SA*DI*0.7;';
            var sh = ld2_jigShape(x0 + cc * pw, y0 + r * ph, pw, ph, edges(r, cc));
            heads.push(hd); shapes.push(sh); homes.push([hx, hy]);
            var PL = jzNoGhost(ld2_use(ctx, PC.comp, 'piece ' + (r + 1) + '-' + (cc + 1)));
            ld2_mask(PL, sh);
            jzXf(PL, 'ADBE Anchor Point').setValue([hx, hy]); jzXf(PL, 'ADBE Position').setValue([hx, hy]);
            jzSetExpr(jzXf(PL, 'ADBE Position'), hd + '[SX+(HX-SX)*e+(HX-' + jzN(W / 2) + ')*eo*1.4+CA*eo*DI*0.5,SY+(HY-SY)*e+(HY-' + jzN(H / 2) + ')*eo*1.4+SA*eo*DI*0.4]');
            jzSetExpr(jzXf(PL, 'ADBE Rotate Z'), hd + 'R6*70*(1-e)+R7*50*eo');
            jzSetExpr(jzXf(PL, 'ADBE Scale'), hd + 'var lf=1+0.08*(1-e)+0.06*eo;[100*lf,100*lf]');
            jzSetExpr(jzXf(PL, 'ADBE Opacity'), hd + 'f<=0?0:(1-eo)*(fo>0?K:1)*100');
            var DSh = jzEffect(PL, 'ADBE Drop Shadow', 'JZ Lift');
            jzEP(DSh, 1, jzHex(ld2_dark(sc))); jzEP(DSh, 3, 146); jzEP(DSh, 4, u * 0.0216); jzEP(DSh, 5, 0);
            jzEX(DSh, 2, hd + '(f<1||fo>0)?76.5:0');
        }
        // seams (all pieces, moving with them) + the flash round the last piece
        var SM = jzNoGhost(jzShapeLayer(ctx, 'seams', 0, 0)), lw = Math.max(1, u * 0.0018), sCol = jzMixHex(plate, ld2_dark(sc), 0.45);
        if (last && N > 1) {
            var gF = jzGrp(SM, 'last piece flash'), tl = TH + 'var tl=time-' + jzN(tOf(ord[N - 1]) + fd) + ',k2=(tl>0&&tl<0.35)?1-tl/0.35:0;';
            ld2_path(gF, shapes[N - 1]);
            var stF = jzAddStroke(gF, sc.accent, Math.max(2, u * 0.006));
            jzSetExpr(stF.property('ADBE Vector Stroke Width'), tl + 'value*k2');
            ld2_gX(gF, 'ADBE Vector Group Opacity', tl + 'k2*K*100');
        }
        for (k = N - 1; k >= 0; k--) {
            var g = jzGrp(SM, 'seam ' + (k + 1));
            ld2_path(g, shapes[k]); jzAddStroke(g, sCol, lw);
            jzGX(g).property('ADBE Vector Anchor').setValue(homes[k]);
            ld2_gX(g, 'ADBE Vector Position', heads[k] + '[SX+(HX-SX)*e+(HX-' + jzN(W / 2) + ')*eo*1.4+CA*eo*DI*0.5,SY+(HY-SY)*e+(HY-' + jzN(H / 2) + ')*eo*1.4+SA*eo*DI*0.4]');
            ld2_gX(g, 'ADBE Vector Rotation', heads[k] + 'R6*70*(1-e)+R7*50*eo');
            ld2_gX(g, 'ADBE Vector Scale', heads[k] + 'var lf=1+0.08*(1-e)+0.06*eo;[100*lf,100*lf]');
            ld2_gX(g, 'ADBE Vector Group Opacity', heads[k] + 'f<=0?0:((f>=1&&fo<=0)?55:100)*(1-eo)*(fo>0?K:1)');
        }
        return bb || ld2_box(x0, y0, x0 + bw, y0 + bh);
    }
});

/* ================================================================== 20 shadowPlay \u2014 \u5F71\u7D75 */
jzReg('layout', 'shadowPlay', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'display', 'serif'])), mode: rng.pick(['floor', 'floor', 'wall']), dir: rng.pick([1, -1]), sweep: rng.range(0.7, 1), sun: rng.chance(0.8) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), i;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), dark = jzLum(sc.bg) < 0.45, dir = jzP(ctx, 'dir', 1) < 0 ? -1 : 1;
        var text = ld2_brk(c.text, port ? 5 : 9), tr = 0.04, lead = 1.08, TH = ld2_TH(ctx);
        var shC = dark ? jzMixHex(sc.bg, '#000000', 0.55) : jzMixHex(ld2_dark(sc), sc.bg, 0.25), size, m, L, S;
        if (jzP(ctx, 'mode', 'floor') !== 'wall') {
            size = Math.min(ld2_fit(ctx, text, font, W * 0.8, H * (port ? 0.22 : 0.3), tr, lead), u * 0.22); m = ld2_measAt(ctx, text, font, size, tr, lead);
            var yb = H * (port ? 0.56 : 0.6), ty = yb - m.h / 2 - size * 0.04, floorC = jzMixHex(sc.bg, sc.fg, dark ? 0.13 : 0.07), FA = 'var FA=oc(time/0.5)*K;';
            // floor plane (grows down from the horizon) + horizon line
            var FL = jzShapeLayer(ctx, 'floor', 0, yb), gf = jzGrp(FL, 'floor');
            jzAddRect(gf, W, H - yb + 2, 0, W / 2, (H - yb + 2) / 2); jzAddFill(gf, floorC);
            jzSetExpr(jzXf(FL, 'ADBE Scale'), TH + FA + '[100,100*FA]');
            jzSetExpr(jzXf(FL, 'ADBE Opacity'), TH + FA + '100*FA');
            var HZ = jzRectLayer(ctx, 'horizon', W / 2, yb, W, Math.max(1, u * 0.002), sc.sub, { opacity: 0.6 }); ld2_ng([FL, HZ]);
            jzSetExpr(jzXf(HZ, 'ADBE Scale'), TH + FA + '[100*FA,100]');
            // the sun crosses the sky; the shadow swings like a sundial
            var PHI = TH + 'var PHI=(74+(38-74)*ios(time/' + jzN(Math.max(0.5, c.dur)) + '))*' + jzN((0.85 + 0.15 * jzP(ctx, 'sweep', 0.85)) * dir) + '*Math.PI/180;';
            if (jzP(ctx, 'sun', true)) {
                // rays, disc, glow (same stacking order); each group is finished before the next is added (AE invalidates older sibling refs)
                var sr0 = u * 0.045, SU = jzShapeLayer(ctx, 'sun', W / 2, yb), g1 = jzGrp(SU, 'rays');
                for (i = 0; i < 10; i++) { var a = i / 10 * Math.PI * 2; jzAddPath(g1, [[Math.cos(a) * sr0 * 1.35, Math.sin(a) * sr0 * 1.35], [Math.cos(a) * sr0 * 1.75, Math.sin(a) * sr0 * 1.75]], false); }
                jzAddStroke(g1, sc.accent, Math.max(1.5, sr0 * 0.1), 80);
                ld2_gX(g1, 'ADBE Vector Rotation', 'time*0.4*180/Math.PI');
                var g2 = jzGrp(SU, 'disc'); jzAddEllipse(g2, sr0 * 2, sr0 * 2); jzAddFill(g2, sc.accent);
                var g3 = jzGrp(SU, 'glow'); jzAddEllipse(g3, sr0 * 3.8, sr0 * 3.8); jzAddFill(g3, sc.accent, 12);
                var mw2 = m.w / 2 + sr0 * 2.2, capY = ty - m.h / 2 - sr0 * 2.4;
                jzSetExpr(jzXf(SU, 'ADBE Position'), PHI + 'var sx=' + jzN(W / 2) + '-Math.sin(PHI)*' + jzN(W * 0.4) + ',sy=' + jzN(yb - sr0 * 1.6) + '-' + jzN(yb - H * 0.1) + '*Math.cos(PHI)*1.1;' +
                    'if(Math.abs(sx-' + jzN(W / 2) + ')<' + jzN(mw2) + ')sy=Math.min(sy,' + jzN(capY) + ');[sx,sy]');
                jzSetExpr(jzXf(SU, 'ADBE Scale'), TH + 'var q=ob(cl((time-0.1)/0.5),1.70158)*100;[q,q]');
                jzSetExpr(jzXf(SU, 'ADBE Opacity'), TH + '100*K'); jzNoGhost(SU);
            }
            // cast shadow: the lyric reflected about the floor line and sheared, M = [[1, sh], [0, -d]] = R(ph) S(s1, s2) R(th)
            var SVD = PHI + 'var sh=Math.max(-1.9,Math.min(1.9,Math.tan(PHI)*0.75)),d=0.3+0.3*Math.abs(Math.sin(PHI));' +
                'var E=(1-d)/2,F=(1+d)/2,G=sh/2,Hh=-sh/2,Q=Math.sqrt(E*E+Hh*Hh),Rr=Math.sqrt(F*F+G*G),s1=Q+Rr,s2=Q-Rr,a1=Math.atan2(G,F),a2=Math.atan2(Hh,E);';
            var NA = ld2_null(ctx, 'shadow skew', W / 2, yb), NB = ld2_null(ctx, 'shadow turn', 0, 0);
            ld2_parent(NB, NA, 0, 0);
            jzSetExpr(jzXf(NA, 'ADBE Rotate Z'), SVD + '(a2+a1)/2*180/Math.PI');
            jzSetExpr(jzXf(NA, 'ADBE Scale'), SVD + '[s1*100,s2*100]');
            jzSetExpr(jzXf(NB, 'ADBE Rotate Z'), SVD + '(a2-a1)/2*180/Math.PI');
            S = ld2_T(ctx, text, font, size, { color: shC, x: W / 2, y: ty, track: tr, lead: lead, name: 'cast shadow' });
            jzXf(S, 'ADBE Opacity').setValue(85); jzNoGhost(S);
            jzAnimate(ctx, S, { mi: 0, treat: false });
            ld2_parent(S, NB, 0, ty - yb);
            // the far end of the shadow fades into the floor
            var Lsh = m.h * 0.45 * 1.15 + size * 0.1, FO = jzShapeLayer(ctx, 'shadow fade', 0, 0), gF = jzGrp(FO, 'fade');
            jzAddRect(gF, W, H - yb, 0, W / 2, yb + (H - yb) / 2); jzAddFill(gF, floorC);
            var mk = ld2_mask(FO, ld2_rectShape(-W, yb + Lsh * 0.675, W * 2, H * 2));
            mk.property('ADBE Mask Feather').setValue([Lsh * 0.65, Lsh * 0.65]);
            jzSetExpr(jzXf(FO, 'ADBE Opacity'), TH + FA + '70*FA'); jzNoGhost(FO);
            L = ld2_T(ctx, text, font, size, { color: sc.fg, x: W / 2, y: ty, track: tr, lead: lead });
            jzAnimate(ctx, L, { mi: 0 });
            return jzBB(L);
        }
        // wall: a small lamp in front throws a big soft shadow on the wall behind
        size = Math.min(ld2_fit(ctx, text, font, W * 0.6, H * (port ? 0.2 : 0.26), tr, lead), u * 0.17);
        var tx = W / 2, tyW = H * 0.5, glowC = dark ? sc.accent : ld2_light(sc);
        var LMP = TH + 'var LX=' + jzN(W / 2) + '+Math.sin(time*0.45+' + (dir > 0 ? 0 : 2) + ')*' + jzN(W * 0.16) + ',LY=' + jzN(H * 0.86) + '+Math.sin(time*1.7)*' + jzN(H * 0.006) +
            ',KS=1.42+0.1*Math.sin(time*0.6),LA=oc(time/0.5)*K,FL=0.92+0.08*hh(SD+Math.floor(time*24)*3.1);';
        // light pool round the lamp (screen-blended radial ramp)
        var GL = jzShapeLayer(ctx, 'lamp light', 0, 0), gg = jzGrp(GL, 'light');
        jzAddRect(gg, W, H, 0, W / 2, H / 2); jzAddFill(gg, '#000000');
        var rp = jzEffect(GL, 'ADBE Ramp', 'JZ Lamp Light');
        jzEP(rp, 2, jzHex(jzMixHex('#000000', glowC, dark ? 0.18 : 0.35))); jzEP(rp, 4, [0, 0, 0]); jzEP(rp, 5, 2);
        jzEX(rp, 1, LMP + '[LX,LY]');
        jzEX(rp, 3, LMP + '[LX+' + jzN(Math.max(W, H) * 0.75) + ',LY]');
        GL.blendingMode = BlendingMode.SCREEN;
        jzSetExpr(jzXf(GL, 'ADBE Opacity'), LMP + '100*LA*FL'); jzNoGhost(GL);
        // the shadow: the lyric scaled up about the lamp (a parent that follows the lamp), blurred
        var NS = ld2_null(ctx, 'lamp pivot', 0, 0);
        jzSetExpr(jzXf(NS, 'ADBE Anchor Point'), LMP + '[LX,LY]');
        jzSetExpr(jzXf(NS, 'ADBE Position'), LMP + '[LX,LY]');
        jzSetExpr(jzXf(NS, 'ADBE Scale'), LMP + '[KS*100,KS*100]');
        S = ld2_T(ctx, text, font, size, { color: shC, x: tx, y: tyW, track: tr, lead: lead, name: 'wall shadow' });
        jzAnimate(ctx, S, { mi: 0, treat: false });
        ld2_parent(S, NS, tx, tyW);
        ld2_opAnim(S, 'JZ Shadow Alpha', LMP, 'a=' + (dark ? 0.9 : 0.55) + '*LA');
        var bl = jzEffect(S, 'ADBE Gaussian Blur 2', 'JZ Soft Shadow'); jzEP(bl, 1, size * 0.04 * 1.42); jzNoGhost(S);
        // the lamp
        // base, flame, halo (same stacking order), each finished before the next group is added
        var fr = u * 0.02, LP = jzShapeLayer(ctx, 'lamp', 0, 0), gb = jzGrp(LP, 'base');
        jzAddRect(gb, fr * 1.8, fr * 1.6, 0, 0, fr * 1.4); jzAddFill(gb, sc.sub);
        var gfl = jzGrp(LP, 'flame');
        jzAddPath(gfl, [[0, -fr * 2.2], [fr * 0.8, -fr * 0.2], [0, fr * 0.6], [-fr * 0.8, -fr * 0.2]], true); jzAddFill(gfl, sc.accent);
        ld2_gX(gfl, 'ADBE Vector Scale', LMP + '[100,100*FL]');
        var gh = jzGrp(LP, 'halo');
        jzAddEllipse(gh, fr * 7, fr * 7); jzAddFill(gh, sc.accent, 15);
        ld2_gX(gh, 'ADBE Vector Group Opacity', LMP + '100*FL');
        jzSetExpr(jzXf(LP, 'ADBE Position'), LMP + '[LX,LY]');
        jzSetExpr(jzXf(LP, 'ADBE Opacity'), LMP + '100*LA'); jzNoGhost(LP);
        L = ld2_T(ctx, text, font, size, { color: sc.fg, x: tx, y: tyW, track: tr, lead: lead });
        jzAnimate(ctx, L, { mi: 0 });
        return jzBB(L);
    }
});

/* ================================================================== 21 kaleido \u2014 \u4E07\u83EF\u93E1 */
jzReg('layout', 'kaleido', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), N: rng.pick([6, 8, 8, 10, 12]), speed: rng.range(4, 10) * rng.pick([1, -1]), flow: rng.range(0.15, 0.3), center: rng.pick(['disc', 'disc', 'band']), tint: rng.pick(['sub', 'accent', 'mixed']) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), i, j;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), t0 = jzFlat(c.text), n = jzCount(t0);
        var unit = n > 5 ? ld2_chunksK(t0, Math.ceil(n / 4))[0] : t0;
        var cx = W / 2, cy = H / 2, N = Math.max(4, Math.round(jzP(ctx, 'N', 8))), al = Math.PI * 2 / N, Rr = Math.sqrt(W * W + H * H) * 0.56;
        var speed = jzP(ctx, 'speed', 6), flow = jzP(ctx, 'flow', 0.2), tint = jzP(ctx, 'tint', 'sub'), TH = ld2_TH(ctx);
        var cols = tint === 'accent' ? [sc.accent, sc.sub, sc.sub] : tint === 'mixed' ? [sc.sub, sc.accent, sc.sub, sc.accent2 || sc.sub] : [sc.sub, jzMixHex(sc.sub, sc.bg, 0.4)];
        // one wedge (precomp): unit copies flowing outward along the bisector (+x), turned 90 degrees
        var S2 = Math.ceil(Rr * 1.1) * 2, pcx = S2 / 2, pcy = S2 / 2;
        var PC = ld2_precomp(ctx, 'JZ kaleido wedge', S2, S2), pctx = PC.ctx, EW = 'var EW=oc(time/0.6);';
        var LNm = jzShapeLayer(pctx, 'axis', 0, 0), gl = jzGrp(LNm, 'axis');
        jzAddPath(gl, [[pcx + Rr * 0.1, pcy], [pcx + Rr, pcy]], false); jzAddStroke(gl, sc.dim || sc.sub, Math.max(1, u * 0.001));
        jzSetExpr(jzXf(LNm, 'ADBE Opacity'), TH + EW + '50*K*EW');
        var M = 4, tn = Math.tan(al / 2);
        for (j = 0; j <= M; j++) {
            var T = ld2_T(pctx, unit, font, 100, { color: cols[j % cols.length], x: pcx, y: pcy, name: 'unit ' + (j + 1) });
            jzXf(T, 'ADBE Rotate Z').setValue(90);
            var FW = TH + EW + 'var ph=((time*' + jzN(flow) + ')%0.25+0.25)%0.25,f=' + jzN(j / M) + '+ph,r=' + jzN(Rr) + '*(0.12+f*0.95),sz=Math.min(r*' + jzN(tn * 1.2) + ',r*0.5),al=Math.min(1,f*4)*(1-f*0.55)*K*EW;';
            jzSetExpr(jzXf(T, 'ADBE Position'), FW + '[' + jzN(pcx) + '+r,' + jzN(pcy) + ']');
            jzSetExpr(jzXf(T, 'ADBE Scale'), FW + '[sz,sz]');
            jzSetExpr(jzXf(T, 'ADBE Opacity'), FW + '(al<=0.02||sz<4)?0:al*50');
        }
        // N wedges round the centre, every other one mirrored
        var wsh = ld2_shape([[pcx, pcy], [pcx + Rr * Math.cos(-al / 2) * 1.05, pcy + Rr * Math.sin(-al / 2) * 1.05], [pcx + Rr * Math.cos(al / 2) * 1.05, pcy + Rr * Math.sin(al / 2) * 1.05]], true);
        for (i = 0; i < N; i++) {
            var WL = jzNoGhost(ld2_use(ctx, PC.comp, 'wedge ' + (i + 1)));
            ld2_mask(WL, wsh);
            jzXf(WL, 'ADBE Anchor Point').setValue([pcx, pcy]); jzXf(WL, 'ADBE Position').setValue([cx, cy]);
            if (i % 2) jzXf(WL, 'ADBE Scale').setValue([100, -100]);
            jzSetExpr(jzXf(WL, 'ADBE Rotate Z'), TH + EW + 'time*' + jzN(speed) + '+(1-EW)*60+' + jzN((i + 0.5) * 360 / N));
        }
        // centre: the lyric on a disc or a band
        var disc = jzP(ctx, 'center', 'disc') === 'disc' && n <= (port ? 8 : 9), text, size, tr = 0.03, lead = 1.1;
        if (disc) {
            text = ld2_brk(t0, n <= 4 ? 4 : Math.ceil(n / 2));
            var Rc = Math.min(W, H) * (n > 5 ? 0.36 : 0.3);
            size = Math.min(ld2_fit(ctx, text, font, Rc * 1.55, Rc * 1.2, tr, lead), u * 0.16);
            var D = jzShapeLayer(ctx, 'disc', cx, cy), g1 = jzGrp(D, 'ring');
            jzAddEllipse(g1, Rc * 2.12, Rc * 2.12); jzAddStroke(g1, sc.sub, Math.max(1, u * 0.001), 60);
            var g2 = jzGrp(D, 'disc');      // added only after 'ring' is finished (AE invalidates older sibling refs)
            jzAddEllipse(g2, Rc * 2, Rc * 2); jzAddStroke(g2, sc.accent, Math.max(2, u * 0.004)); jzAddFill(g2, sc.bg);
            jzSetExpr(jzXf(D, 'ADBE Scale'), TH + 'var q=ob(cl(time/0.4),1.5)*(1-0.3*ic(PO))*100;[q,q]');
            jzSetExpr(jzXf(D, 'ADBE Opacity'), TH + '100*K'); jzNoGhost(D);
        } else {
            text = ld2_brk(t0, port ? 5 : 8);
            size = Math.min(ld2_fit(ctx, text, font, W * 0.84, H * 0.26, tr, lead), u * 0.18);
            var mb = ld2_measAt(ctx, text, font, size, tr, lead), bh0 = mb.h + size * 0.6, lwB = Math.max(2, u * 0.003);
            // band, top, bottom (same stacking order); each group is finished before the next is added (AE invalidates older sibling refs)
            var BD = jzNoGhost(jzShapeLayer(ctx, 'band', cx, cy));
            var BH = TH + 'var bh=' + jzN(bh0) + '*oe(time/0.4)*(1-ic(PO));';
            var gb = jzGrp(BD, 'band');
            jzAddRect(gb, W, bh0, 0, 0, 0); jzAddFill(gb, sc.bg); ld2_gX(gb, 'ADBE Vector Scale', BH + '[100,bh/' + jzN(bh0) + '*100]');
            var gt = jzGrp(BD, 'top');
            jzAddRect(gt, W, lwB, 0, 0, 0); jzAddFill(gt, sc.accent); ld2_gX(gt, 'ADBE Vector Position', BH + '[0,-bh/2]'); ld2_gX(gt, 'ADBE Vector Group Opacity', BH + '100*K');
            var gbt = jzGrp(BD, 'bottom');
            jzAddRect(gbt, W, lwB, 0, 0, 0); jzAddFill(gbt, sc.accent); ld2_gX(gbt, 'ADBE Vector Position', BH + '[0,bh/2]'); ld2_gX(gbt, 'ADBE Vector Group Opacity', BH + '100*K');
        }
        var L = ld2_T(ctx, text, font, size, { color: sc.fg, x: cx, y: cy, track: tr, lead: lead });
        jzAnimate(ctx, L, { mi: 0 });
        return jzBB(L);
    }
});

/* ================================================================== 22 dominoes \u2014 \u30C9\u30DF\u30CE */
var LD2_PIPS = [[], [[0, 0]], [[-1, -1], [1, 1]], [[-1, -1], [0, 0], [1, 1]], [[-1, -1], [1, -1], [-1, 1], [1, 1]], [[-1, -1], [1, -1], [0, 0], [-1, 1], [1, 1]], [[-1, -1], [1, -1], [-1, 0], [1, 0], [-1, 1], [1, 1]]];
jzReg('layout', 'dominoes', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), face: rng.pick(['light', 'light', 'ink']), pips: rng.chance(0.8) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, s = c.seed, u = jzU(ctx), port = jzPortrait(ctx), r, j, q;
        var n = jzCount(c.text);
        if (n < 1) return null;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), pips = !!jzP(ctx, 'pips', true);
        var rowsD = ld2_rowsOf(c.text, port ? 5 : n <= 7 ? 7 : Math.ceil(n / 2)), rows = rowsD.length, per = 1;
        for (r = 0; r < rows; r++) per = Math.max(per, rowsD[r].length);
        var asp = pips ? 1.9 : 1.45, gapK = 0.5;
        var w = Math.min(W * 0.84 / (per + (per - 1) * gapK), H * 0.72 / (rows * asp + (rows - 1) * 0.35), u * 0.2), h = w * asp, g = w * gapK;
        var faceC = jzP(ctx, 'face', 'light') === 'ink' ? ld2_plateCol(sc, [sc.ink, sc.fg]) : jzMixHex(ld2_light(sc), sc.accent, 0.06);
        var tc = ld2_onCol(sc, faceC), sideC = ld2_shade(sc, faceC, -0.35), pipC = jzMixHex(faceC, tc, 0.7), thick = w * 0.12;
        var lean = Math.asin(jzClamp((g - thick * 0.2) / h, 0, 0.95)) * 180 / Math.PI;
        var T0 = 0.1, dT = jzClamp(c.dur * 0.3 / n, 0.04, 0.09), rise = 0.26;
        var outDur = Math.max(0.3, Math.min(c.dur * 0.35, (c.outDur || 0.3) + 0.25)), oStart = c.dur - outDur, TH = ld2_TH(ctx);
        var FLN = jzNoGhost(jzShapeLayer(ctx, 'floor lines', 0, 0)), bb = null, gi = 0;
        jzSetExpr(jzXf(FLN, 'ADBE Opacity'), TH + '50*oe(time/0.3)*K');
        for (r = 0; r < rows; r++) {
            var row = rowsD[r], cnt = row.length, yF = H / 2 + (r - (rows - 1) / 2) * (h + w * 0.35) + h / 2, half = cnt * (w + g) / 2 + w * 0.4;
            var gfl = jzGrp(FLN, 'floor ' + (r + 1));
            jzAddPath(gfl, [[W / 2 - half, yF], [W / 2 + half, yF]], false); jzAddStroke(gfl, sc.sub, Math.max(1, u * 0.0018));
            for (j = 0; j < cnt; j++) {
                if (row[j] === ' ') continue;
                var i = gi++, xc = W / 2 + (j - (cnt - 1) / 2) * (w + g), ti = T0 + i * dT;
                // stand up in a wave from the left (pivot = bottom-left corner), fall in a chain to the right (bottom-right corner)
                var hd = TH + 'var fu=cl((time-' + jzN(ti) + ')/' + jzN(rise) + '),th=0,pv=0;if(fu<1){th=-' + jzN(j === 0 ? 88 : lean) + '*(1-ob(fu,1.6));pv=-1;}' +
                    'var fo=(time-(' + jzN(oStart + j * 0.05) + '))/0.3;if(fo>0){th=' + jzN(j === cnt - 1 ? 88 : lean) + '*iq(Math.min(1,fo));pv=1;}' +
                    'var vis=cl((time-' + jzN(ti - 0.08) + ')/0.1)*(1-ic(cl((PO-0.7)/0.3)));';
                var TL = jzNoGhost(jzShapeLayer(ctx, 'tile ' + (i + 1), xc, yF));
                if (pips) {
                    var gp = jzGrp(TL, 'pips'), pn = jzHash(s, i, 11) % 7;
                    for (q = 0; q < LD2_PIPS[pn].length; q++) jzAddEllipse(gp, w * 0.14, w * 0.14, LD2_PIPS[pn][q][0] * w * 0.22, -h * 0.235 + LD2_PIPS[pn][q][1] * w * 0.22);
                    jzAddFill(gp, pipC);
                    var gln = jzGrp(TL, 'half line'); jzAddPath(gln, [[-w * 0.36, -h * 0.47], [w * 0.36, -h * 0.47]], false); jzAddStroke(gln, jzMixHex(faceC, tc, 0.45), Math.max(1, w * 0.03));
                }
                var gfc = jzGrp(TL, 'face'); jzAddRect(gfc, w, h, w * 0.1, 0, -h / 2); jzAddFill(gfc, faceC);
                var gsd = jzGrp(TL, 'side'); jzAddPath(gsd, [[w / 2, -h + thick * 0.4], [w / 2 + thick, -h + thick * 0.7], [w / 2 + thick, thick * 0.3], [w / 2, 0]], true); jzAddFill(gsd, sideC);
                var gsh = jzGrp(TL, 'shadow'); jzAddPath(gsh, [[-w / 2, 0], [w / 2, 0], [w * 0.6, w * 0.04], [-w * 0.4, w * 0.04]], true); jzAddFill(gsh, ld2_dark(sc), 25);
                jzSetExpr(jzXf(TL, 'ADBE Anchor Point'), hd + '[pv*' + jzN(w / 2) + ',0]');
                jzSetExpr(jzXf(TL, 'ADBE Position'), hd + '[' + jzN(xc) + '+pv*' + jzN(w / 2) + ',' + jzN(yF) + ']');
                jzSetExpr(jzXf(TL, 'ADBE Rotate Z'), hd + 'th');
                jzSetExpr(jzXf(TL, 'ADBE Opacity'), hd + '100*vis');
                // the glyph rides on the tile
                var cyT = pips ? -h * 0.735 : -h / 2;
                var G = ld2_G(ctx, row[j], font, w * 0.7, xc, yF + cyT, tc);
                ld2_opAnim(G, 'JZ Tile Alpha', hd, 'a=vis');
                jzAnimate(ctx, G, { mi: ld2_miAt(ctx, ti), noHold: ld2_plateHold(ctx) });
                ld2_parent(G, TL, 0, cyT);
                bb = jzUnion(bb, ld2_box(xc - w * 0.4, yF + cyT - w * 0.4, xc + w * 0.4, yF + cyT + w * 0.4));
            }
        }
        return bb;
    }
});

/* ================================================================== 23 burst \u2014 \u30C9\u30AB\u30F3 */
jzReg('layout', 'burst', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display'])), spikes: rng.int(13, 19), sharp: rng.range(0.66, 0.78), tilt: rng.range(-9, 9), lines: rng.chance(0.75), debris: rng.chance(0.8), order: rng.pick(['accent', 'ink']) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, s = c.seed, u = jzU(ctx), port = jzPortrait(ctx), i, v;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), text = ld2_brk(c.text, port ? 4 : 6), tr = 0.02, lead = 1.05;
        var rxM = W * 0.355, ryM = Math.min(H * 0.355, rxM * (port ? 1.5 : 1.1));
        var size = Math.min(ld2_fit(ctx, text, font, rxM * 1.08, ryM, tr, lead), u * 0.24), m = ld2_measAt(ctx, text, font, size, tr, lead);
        var rx = Math.min(rxM, m.w / 2 / 0.54 + size * 0.15), ry = Math.min(ryM, Math.max(m.h / 2 / 0.54 + size * 0.15, rx * 0.55)), cx = W / 2, cy = H / 2;
        var N = Math.max(5, Math.round(jzP(ctx, 'spikes', 16))), sharp = jzP(ctx, 'sharp', 0.72), tilt = jzP(ctx, 'tilt', 0);
        var A = ld2_plateCol(sc, jzP(ctx, 'order', 'accent') === 'accent' ? [sc.accent, sc.ink] : [sc.ink, sc.accent]), B = null, cand = [ld2_dark(sc), ld2_light(sc), sc.bg, sc.fg];
        for (i = 0; i < cand.length; i++) if (!B && jzContrast(cand[i], A) >= 2.2 && cand[i] !== A) B = cand[i];
        B = B || ld2_onCol(sc, A);
        var TH = ld2_TH(ctx), QR = TH + 'var q=ob(cl(time/0.3),2.2)*(1-0.6*ic(PO))*(1+0.015*Math.sin(time*7)*cl(time-0.3)),ROT=' + jzN(tilt) + '+(1-oc(time/0.3))*-25+ic(PO)*20;';
        // shock ring
        var RG = jzNoGhost(jzShapeLayer(ctx, 'shock ring', cx, cy)), gr = jzGrp(RG, 'ring'), R0 = Math.max(rx, ry) * 2;
        var RN = TH + 'var rg=cl(time/0.45);', el = jzAddEllipse(gr, R0, R0);
        jzSetExpr(el.property('ADBE Vector Ellipse Size'), RN + 'var d=' + jzN(R0) + '*(0.6+rg*1.1);[d,d]');     // before the stroke is added (AE rule)
        var stR = jzAddStroke(gr, A, Math.max(2, u * 0.012));
        jzSetExpr(stR.property('ADBE Vector Stroke Width'), RN + 'value*(1-rg)');
        jzSetExpr(jzXf(RG, 'ADBE Opacity'), RN + 'rg>=1?0:(1-rg)*K*100');
        // speed lines (flicker per frame)
        if (jzP(ctx, 'lines', true)) {
            var SL = jzNoGhost(jzShapeLayer(ctx, 'speed lines', cx, cy));
            for (i = 0; i < 28; i++) {
                var a = i / 28 * Math.PI * 2 + (jzR(s, i, 8) * 2 - 1) * 0.08, r0 = 1.325, r1 = r0 + 0.2 + jzR(s, i, 11) * 0.25, gl = jzGrp(SL, 'line ' + (i + 1));
                jzAddPath(gl, [[Math.cos(a) * rx * r0, Math.sin(a) * ry * r0], [Math.cos(a) * rx * r1, Math.sin(a) * ry * r1]], false); jzAddStroke(gl, sc.sub, Math.max(1.5, u * 0.004));
                var FK = TH + 'var st=Math.floor(time*24);';
                ld2_gX(gl, 'ADBE Vector Group Opacity', FK + 'hh(SD+' + (i * 7) + '+st*1.37)<0.3?0:100');
                ld2_gX(gl, 'ADBE Vector Scale', FK + 'var k=100+(hh(SD+' + (i * 11) + '+st*2.11)-0.5)*12;[k,k]');
            }
            jzSetExpr(jzXf(SL, 'ADBE Scale'), QR + '[q*100,q*100]');
            jzSetExpr(jzXf(SL, 'ADBE Opacity'), TH + '70*K');
        }
        // debris flying out
        if (jzP(ctx, 'debris', true)) {
            var DB = jzNoGhost(jzShapeLayer(ctx, 'debris', cx, cy)), sz0 = u * 0.018;
            for (i = 0; i < 12; i++) {
                var a2 = jzR(s, i, 21) * Math.PI * 2, sp = 0.5 + 0.6 * jzR(s, i, 22), rr = jzR(s, i, 23) * Math.PI * 2, gd = jzGrp(DB, 'bit ' + (i + 1));
                jzAddPath(gd, [[sz0, 0], [Math.cos(2.2) * sz0, Math.sin(2.2) * sz0], [Math.cos(4.1) * sz0 * 0.7, Math.sin(4.1) * sz0 * 0.7]], true); jzAddFill(gd, i % 2 ? A : B);
                var DF = TH + 'var f=oc(time/0.8),d=1.05+f*0.6*' + jzN(sp) + ';';
                ld2_gX(gd, 'ADBE Vector Position', DF + '[' + jzN(Math.cos(a2) * rx) + '*d,' + jzN(Math.sin(a2) * ry) + '*d+f*f*' + jzN(H * 0.05) + ']');
                ld2_gX(gd, 'ADBE Vector Scale', DF + '[(1-f)*100,(1-f)*100]');
                ld2_gX(gd, 'ADBE Vector Rotation', DF + '(' + jzN(rr) + '+f*6)*180/Math.PI');
            }
            jzSetExpr(jzXf(DB, 'ADBE Opacity'), TH + 'time<0.9?100*K:0');
        }
        // the plate: 3 stacked jagged stars + shadow, each boiling between 3 drawings
        var ST = jzNoGhost(jzShapeLayer(ctx, 'burst', cx, cy)), defs = [[3, 0.86, 0.08, A], [2, 0.93, 0.12, B], [1, 1.12, 0.22, A], [1, 1.12, 0.22, ld2_dark(sc)]];
        for (var k = 0; k < defs.length; k++) {
            var df = defs[k], gk = jzGrp(ST, k === 3 ? 'shadow' : 'star ' + (k + 1));
            for (v = 0; v < 3; v++) {
                var pts = [], gv = ld2_sub(gk, 'boil ' + (v + 1));
                for (i = 0; i < N * 2; i++) {
                    var an = i / (N * 2) * Math.PI * 2, rq = i % 2 ? sharp + (jzR(s, i, df[0], v) * 2 - 1) * 0.04 : 1 + jzR(s, i, df[0], v) * df[2];
                    pts.push([Math.cos(an) * rx * rq * df[1], Math.sin(an) * ry * rq * df[1]]);
                }
                jzAddPath(gv, pts, true); jzAddFill(gv, df[3], k === 3 ? 30 : 100);
                ld2_gX(gv, 'ADBE Vector Group Opacity', 'Math.floor(time*8)%3==' + v + '?100:0');
            }
            if (k === 3) ld2_gX(gk, 'ADBE Vector Position', QR + 'var qq=Math.max(q,0.01),aa=-ROT*Math.PI/180,ox=' + jzN(u * 0.012) + ',oy=' + jzN(u * 0.016) + ';[(ox*Math.cos(aa)-oy*Math.sin(aa))/qq,(ox*Math.sin(aa)+oy*Math.cos(aa))/qq]');
        }
        jzSetExpr(jzXf(ST, 'ADBE Scale'), QR + '[q*100,q*100]');
        jzSetExpr(jzXf(ST, 'ADBE Rotate Z'), QR + 'ROT');
        jzSetExpr(jzXf(ST, 'ADBE Opacity'), QR + 'q<=0.01?0:100*K');
        // the lyric rides a pivot that follows the plate (0.6 of its turn, never larger than 1)
        var NP = ld2_null(ctx, 'burst pivot', cx, cy);
        jzSetExpr(jzXf(NP, 'ADBE Rotate Z'), QR + 'ROT*0.6');
        jzSetExpr(jzXf(NP, 'ADBE Scale'), QR + 'var k=Math.min(1,q)*100;[k,k]');
        var L = ld2_T(ctx, text, font, size, { color: ld2_onCol(sc, A), x: cx, y: cy, track: tr, lead: lead });
        var bb = jzBB(L);
        jzAnimate(ctx, L, { mi: 0 });
        ld2_parent(L, NP, 0, 0);
        return bb;
    }
});

/* ================================================================== 24 fisheye \u2014 \u9B5A\u773C\u30EC\u30F3\u30BA */
// circular arc (degrees, screen angles) as an open bezier Shape
function ld2_arc(cx, cy, r, a0, a1) {
    var segs = Math.max(1, Math.ceil(Math.abs(a1 - a0) / 90)), da = (a1 - a0) / segs, k = 4 / 3 * Math.tan(da * Math.PI / 720) * r, V = [], I = [], O = [];
    for (var i = 0; i <= segs; i++) {
        var a = (a0 + da * i) * Math.PI / 180, c = Math.cos(a), s = Math.sin(a);
        V.push([cx + c * r, cy + s * r]); I.push(i ? [s * k, -c * k] : [0, 0]); O.push(i < segs ? [-s * k, c * k] : [0, 0]);
    }
    var sh = new Shape(); sh.vertices = V; sh.inTangents = I; sh.outTangents = O; sh.closed = false;
    return sh;
}
jzReg('layout', 'fisheye', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), mode: rng.pick(['sweep', 'sweep', 'pingpong']), glass: rng.pick(['lens', 'lens', 'grid', 'none']), amp: rng.range(0.7, 1.1) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), i, j, ri;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), t0 = jzFlat(c.text), n = jzCount(t0);
        if (!n) return null;
        var A = jzP(ctx, 'amp', 0.9), mode = jzP(ctx, 'mode', 'sweep') === 'pingpong' ? 1 : 0, glass = jzP(ctx, 'glass', 'lens');
        var rowsT = n > (port ? 6 : 10) ? ld2_chunksK(t0, port ? Math.ceil(n / 6) : 2) : [t0], nR = rowsT.length, rowsC = [], s0 = u * 0.16;
        for (ri = 0; ri < nR; ri++) { var ch = jzChars(jzTrim(rowsT[ri])), a = 0; for (j = 0; j < ch.length; j++) a += ld2_adv(ch[j]); s0 = Math.min(s0, W * 0.84 / (a + A * 1.6)); rowsC.push(ch); }
        s0 = Math.min(s0, H * 0.6 / (nR * (1.4 + A * 0.5)));
        var sig = s0 * 1.25, RB = [], RY = [], XL = [], TW = [], bb = null;
        for (ri = 0; ri < nR; ri++) {
            var ws = [], tot = 0;
            for (j = 0; j < rowsC[ri].length; j++) { ws.push(ld2_adv(rowsC[ri][j]) * s0); tot += ws[j]; }
            RB.push(ws); TW.push(tot); XL.push(W / 2 - tot / 2); RY.push(H / 2 + (ri - (nR - 1) / 2) * s0 * (1.35 + A * 0.55));
        }
        var rb = [];
        for (ri = 0; ri < nR; ri++) rb.push(jzArrExpr(RB[ri]));
        // row(ri): bulge factor per glyph (lens at lx, gaussian), re-flowed round the centre
        var FE = ld2_TH(ctx) + 'var RB=[' + rb.join(',') + '],RY=' + jzArrExpr(RY) + ',XL=' + jzArrExpr(XL) + ',TW=' + jzArrExpr(TW) + ',SIG=' + jzN(sig) + ',AMP=' + jzN(A) + ',NR=' + nR + ',W2=' + jzN(W / 2) + ',MODE=' + mode + ';' +
            'var LI=oc((time-0.1)/0.4)*(1-ic(PO)),TT=cl((time-0.2)/' + jzN(Math.max(0.4, c.dur - 0.5)) + ');' +
            'function lensX(r){if(MODE==0){var f=TT*NR-r;return (f<0||f>1)?null:XL[r]-SIG+(TW[r]+SIG*2)*ios(f);}return XL[r]+TW[r]*(0.5-0.5*Math.cos(time*1.6+r*1.3));}' +
            'function row(r){var B=RB[r],lx=lensX(r),ks=[],acc=0,tot=0;for(var j=0;j<B.length;j++){var x=XL[r]+acc+B[j]/2;acc+=B[j];var k=lx==null?1:1+AMP*LI*Math.exp(-Math.pow((x-lx)/SIG,2));ks.push(k);tot+=B[j]*k;}return {ks:ks,tot:tot,lx:lx};}\n';
        for (ri = 0; ri < nR; ri++) {
            var L = ld2_G(ctx, rowsC[ri].join(''), font, s0, W / 2, RY[ri], sc.fg, rowsT[ri]);
            var hd = FE + 'var R=row(' + ri + '),B=RB[' + ri + '],i=Math.min(textIndex-1,B.length-1),k=R.ks[i],xr=XL[' + ri + '],xn=W2-R.tot/2;for(var j=0;j<i;j++){xr+=B[j];xn+=B[j]*R.ks[j];}xr+=B[i]/2;xn+=B[i]*k/2;';
            ld2_posAnim(L, 'JZ Lens Flow', W, hd, 'dx=xn-xr');
            ld2_scAnim(L, 'JZ Lens Bulge', hd, 'sx=k;sy=k');
            ld2_colAnim(L, 'JZ Lens Tint', sc.accent, hd, 'm=k>1.35?1:0');
            jzAnimate(ctx, L, { mi: ri * 3 });
            bb = jzUnion(bb, ld2_box(W / 2 - TW[ri] / 2 - s0 * 0.4, RY[ri] - s0 * 0.7, W / 2 + TW[ri] / 2 + s0 * 0.4, RY[ri] + s0 * 0.7));
            if (glass === 'grid') {
                // warped grid round the lens (drawn at full bulge, faded with it)
                var GR = jzNoGhost(jzShapeLayer(ctx, 'lens grid ' + (ri + 1), 0, 0)), gg = jzGrp(GR, 'grid'), gq = s0 * 0.5, q, kq;
                var warp = function (dx, dy) { var rr = Math.sqrt(dx * dx + dy * dy), f = 1 + 0.9 * A * Math.exp(-Math.pow(rr / (sig * 1.4), 2)); return [dx * f, dy * f]; };
                for (kq = -3; kq <= 3; kq++) { var p1 = []; for (q = -12; q <= 12; q++) p1.push(warp(q * gq * 0.5, kq * gq)); jzAddPath(gg, p1, false); }
                for (kq = -6; kq <= 6; kq++) { var p2 = []; for (q = -6; q <= 6; q++) p2.push(warp(kq * gq, q * gq * 0.5)); jzAddPath(gg, p2, false); }
                jzAddStroke(gg, sc.sub, Math.max(1, u * 0.0012));
                jzSetExpr(jzXf(GR, 'ADBE Position'), FE + 'var lx=lensX(' + ri + ');[lx==null?-9999:lx,RY[' + ri + ']]');
                jzSetExpr(jzXf(GR, 'ADBE Opacity'), FE + 'lensX(' + ri + ')==null?0:35*K*LI');
            }
        }
        if (glass === 'lens') {
            // magnifier over the most bulged glyph (the active row when sweeping, the last row in ping-pong)
            var R0 = s0 * 1.05, lw = Math.max(2, u * 0.005), LS = jzNoGhost(jzShapeLayer(ctx, 'lens', 0, 0)), lc = ld2_light(sc);
            var gh = jzGrp(LS, 'handle'), a50 = 50 * Math.PI / 180;
            jzAddPath(gh, [[Math.cos(a50) * R0, Math.sin(a50) * R0], [Math.cos(a50) * R0 * 1.75, Math.sin(a50) * R0 * 1.75]], false); jzAddStroke(gh, sc.sub, lw * 2.6);
            var ga = jzGrp(LS, 'glint'); ld2_path(ga, ld2_arc(0, 0, R0 * 0.8, 200, 250)); jzAddStroke(ga, lc, lw * 0.8, 70);
            var gr = jzGrp(LS, 'rim'); jzAddEllipse(gr, R0 * 2, R0 * 2); jzAddStroke(gr, sc.sub, lw); jzAddFill(gr, lc, 6);
            var LH = FE + 'var ri=MODE==0?Math.max(0,Math.min(NR-1,Math.floor(TT*NR))):NR-1,R=row(ri),B=RB[ri],bi=0,bw=0,x=W2-R.tot/2;for(var j=0;j<B.length;j++)if(R.ks[j]>bw){bw=R.ks[j];bi=j;}' +
                'for(j=0;j<bi;j++)x+=B[j]*R.ks[j];x+=B[bi]*bw/2;var RL=Math.max(' + jzN(s0) + '*(0.5+0.5*bw)*0.95*1.25,' + jzN(R0) + ');';
            jzSetExpr(jzXf(LS, 'ADBE Position'), LH + '[x,RY[ri]]');
            jzSetExpr(jzXf(LS, 'ADBE Scale'), LH + '[RL/' + jzN(R0) + '*100,RL/' + jzN(R0) + '*100]');
            jzSetExpr(jzXf(LS, 'ADBE Opacity'), LH + 'R.lx==null?0:100*LI');
        }
        return bb;
    }
});

/* ================================================================== 25 wall \u2014 \u58C1\u9762\u30D1\u30FC\u30B9 */
jzReg('layout', 'wall', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'display', 'serif'])), side: rng.pick([1, -1]), yaw: rng.range(36, 46), orbit: rng.range(8, 14), stripe: rng.chance(0.7), eye: rng.range(-0.06, 0.06), tone: rng.pick(['plate', 'plain']) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), i, j, r;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), side = jzP(ctx, 'side', 1) < 0 ? -1 : 1, yaw0 = jzP(ctx, 'yaw', 40), orbit = jzP(ctx, 'orbit', 10);
        var eye = jzP(ctx, 'eye', 0), tone = jzP(ctx, 'tone', 'plate'), f = Math.max(W, H) * 1.05, D = f;
        var ccx = W / 2 - side * W * (port ? 0.3 : 0.22), ccy = H * (0.5 + eye), Ht = H * (port ? 0.26 : 0.4), Hb = H * (port ? 0.2 : 0.34), LL = D * (port ? 1.5 : 1.15), LO = D * 1.2;
        var plate = tone === 'plate' ? ld2_plateCol(sc, [sc.ink, sc.fg]) : jzMixHex(sc.bg, sc.fg, jzLum(sc.bg) < 0.5 ? 0.1 : 0.07);
        var litW = ld2_shade(sc, plate, 0.06), dimW = ld2_shade(sc, plate, -0.3), tc = tone === 'plate' ? ld2_onCol(sc, plate) : sc.fg;
        // projection (expressions): a corner at depth D, the lyric wall along dL, the other wall along dO, yaw orbiting in / out
        var WH = ld2_TH(ctx) + 'var F=' + jzN(f) + ',D=' + jzN(D) + ',SIDE=' + side + ',CX=' + jzN(ccx) + ',CY=' + jzN(ccy) + ',HT=' + jzN(Ht) + ',HB=' + jzN(Hb) + ',LL=' + jzN(LL) + ',LO=' + jzN(LO) + ';' +
            'var a0=oc(time/0.9),YAW=(' + jzN(yaw0) + '+(1-a0)*' + jzN(orbit * 2.2) + '+Math.sin(time*0.35)*' + jzN(orbit * 0.15) + '+ic(PO)*' + jzN(orbit * 1.5) + ')*Math.PI/180;' +
            'var dL=[SIDE*Math.sin(YAW),Math.cos(YAW)],dO=[-SIDE*Math.cos(YAW),Math.sin(YAW)],WA=oc(time/0.5)*K;' +
            'function PJ(d,s,Y){var Z=D+d[1]*s;return [CX+F*d[0]*s/Z,CY+F*Y/Z];}function FACE(d,L,y0,y1){return [PJ(d,0,y0),PJ(d,L,y0),PJ(d,L,y1),PJ(d,0,y1)];}\n';
        var pj = function (d, s, Y) { var Z = D + d[1] * s; return [ccx + f * d[0] * s / Z, ccy + f * Y / Z]; };
        var yr = yaw0 * Math.PI / 180, dLr = [side * Math.sin(yr), Math.cos(yr)];
        // ground + horizon
        var GD = jzRectLayer(ctx, 'ground', W / 2, (ccy + H) / 2, W, H - ccy, jzMixHex(sc.bg, ld2_dark(sc), 0.35));
        jzSetExpr(jzXf(GD, 'ADBE Opacity'), WH + '50*WA');
        var HZ = jzRectLayer(ctx, 'horizon', W / 2, ccy, W, Math.max(1, u * 0.001), sc.sub);
        jzSetExpr(jzXf(HZ, 'ADBE Opacity'), WH + '25*WA'); ld2_ng([GD, HZ]);
        // walls (quads mapped by group transforms), stripes on top
        var WS = jzShapeLayer(ctx, 'walls', 0, 0);
        if (jzP(ctx, 'stripe', true)) {
            ld2_quad(WS, 'stripe lyric wall', sc.accent, WH, 'var Q=FACE(dL,LL*WA,HB*0.55,HB*0.72);');
            ld2_quad(WS, 'stripe other wall', sc.accent, WH, 'var Q=FACE(dO,LO*WA,HB*0.55,HB*0.72);');
        }
        var gcn = jzGrp(WS, 'corner');
        jzAddPath(gcn, [[ccx, ccy - f * Ht / D], [ccx, ccy + f * Hb / D]], false); jzAddStroke(gcn, jzMixHex(litW, ld2_dark(sc), 0.3), Math.max(1, u * 0.0016) * 1.5);
        ld2_quad(WS, 'lyric wall', litW, WH, 'var Q=FACE(dL,LL*WA,-HT,HB);');
        ld2_quad(WS, 'other wall', dimW, WH, 'var Q=FACE(dO,LO*WA,-HT,HB);');
        jzSetExpr(jzXf(WS, 'ADBE Opacity'), WH + '100*K'); jzNoGhost(WS);
        // per-glyph perspective: glyph centre p0, local x along the wall, local y world-vertical -> rotation + skew + scale
        var GLY = 'var p0=PJ(DV,sp,Y),p1=PJ(DV,sp+FS,Y),p2=PJ(DV,sp,Y+1),m11=p1[0]-p0[0],m21=p1[1]-p0[1],m12=p2[0]-p0[0],m22=p2[1]-p0[1],th=Math.atan2(m21,m11),cs=Math.cos(th),sn=Math.sin(th),' +
            'jx=Math.sqrt(m11*m11+m21*m21),jy=-m12*sn+m22*cs,kk=Math.abs(jy)<1e-6?0:(m12*cs+m22*sn)/jy;';
        var KB = Math.max(W, H) * 3;
        var persp = function (L, hd, fs, cnt) {
            ld2_posAnim(L, 'JZ Wall Place', KB + cnt * fs, hd, 'dx=p0[0];dy=p0[1]-(i*' + jzN(fs) + '-' + jzN(LD2_CY * fs) + ')');
            ld2_rotAnim(L, 'JZ Wall Turn', 180, hd, 'r=th*180/Math.PI');
            ld2_skAnim(L, 'JZ Wall Skew', hd, 'k=Math.atan(kk)*180/Math.PI');
            ld2_scAnim(L, 'JZ Wall Scale', hd, 'sx=jx;sy=jy');
        };
        // other wall: rows of the line in small type
        var oc = jzFlat(c.lineText || c.text), rowsO = port ? 3 : 4, fsO = Ht * 0.12, fo = jzBodyF(ctx);
        var ochs = jzChars(oc + '\u3000\u30FB\u3000' + oc + '\u3000\u30FB\u3000' + oc).slice(0, 40), ads = [], tot = 0, cnt = 0;
        for (i = 0; i < ochs.length; i++) { var ad = ld2_adv(ochs[i]) * fsO * 1.1; if (tot + ad > LO * 0.86) break; ads.push(ad); tot += ad; cnt++; }
        var OG = [], OSP = [], OY = [], ORW = [];
        for (r = 0; r < rowsO; r++) {
            var uu = 0;
            for (i = 0; i < cnt; i++) {
                var mid = uu + ads[i] / 2; uu += ads[i];
                if (ochs[i] === ' ' || ochs[i] === '\u3000') continue;
                OG.push(ochs[i]); OSP.push(-side >= 0 ? LO * 0.07 + mid : LO * 0.07 + tot - mid); OY.push(-Ht * 0.72 + r * fsO * 1.9); ORW.push(r);
            }
        }
        if (OG.length) {
            var OL = jzNoGhost(ld2_glyphs(ctx, OG, { font: fo, size: fsO, color: jzMixHex(dimW, ld2_onCol(sc, dimW), 0.55), name: 'other wall copy' }));
            var oh = WH + 'var SPA=' + jzArrExpr(OSP) + ',YA=' + jzArrExpr(OY) + ',RWA=' + jzArrExpr(ORW) + ',i=textIndex-1,sp=SPA[i],Y=YA[i],DV=dO,FS=' + (side > 0 ? -1 : 1) + ';' + GLY;
            persp(OL, oh, fsO, OG.length);
            ld2_opAnim(OL, 'JZ Wall Copy In', oh, 'a=WA*cl((time-0.2-RWA[i]*0.08)/0.3)');
        }
        // the lyric along the main wall (one layer per line, pivot = its settled centre)
        var t0 = jzFlat(c.text), n = jzCount(t0), lines = n > (port ? 5 : 8) ? ld2_chunksK(t0, port ? Math.ceil(n / 5) : 2) : [t0], nl = lines.length, bb = null;
        var g = (Ht + Hb * 0.5) * 0.62 / (nl * 1.15);
        for (j = 0; j < nl; j++) { var aa = 0, lc = jzChars(lines[j]); for (i = 0; i < lc.length; i++) aa += ld2_adv(lc[i]) * 1.04; g = Math.min(g, LL * 0.86 / Math.max(1, aa)); }
        for (j = 0; j < nl; j++) {
            var chs = jzChars(jzTrim(lines[j])), Y = -Ht * 0.25 + (j - (nl - 1) / 2) * g * 1.15, SP = [], tt = 0, u2 = 0, x0 = 1e9, x1 = -1e9, y0 = 1e9, y1 = -1e9;
            for (i = 0; i < chs.length; i++) tt += ld2_adv(chs[i]) * g * 1.04;
            for (i = 0; i < chs.length; i++) {
                var a2 = ld2_adv(chs[i]) * g * 1.04, md = u2 + a2 / 2; u2 += a2;
                var sp = side >= 0 ? LL * 0.07 + md : LL * 0.07 + tt - md; SP.push(sp);
                var q0 = pj(dLr, sp, Y - g / 2), q1 = pj(dLr, sp, Y + g / 2);
                x0 = Math.min(x0, q0[0] - g / 2); x1 = Math.max(x1, q0[0] + g / 2); y0 = Math.min(y0, q0[1]); y1 = Math.max(y1, q1[1]);
            }
            var box = ld2_box(x0, y0, x1, y1);
            bb = jzUnion(bb, box);
            var ML = ld2_glyphs(ctx, chs, { font: font, size: g, color: tc, name: lines[j], ax: box.cx, ay: box.cy });
            var mh = WH + 'var SPA=' + jzArrExpr(SP) + ',i=Math.min(textIndex-1,SPA.length-1),sp=SPA[i],Y=' + jzN(Y) + ',DV=dL,FS=' + (side < 0 ? -1 : 1) + ';' + GLY;
            persp(ML, mh, g, chs.length);
            ld2_opAnim(ML, 'JZ Wall Reach', mh, 'a=sp<=LL*WA?1:0');
            jzAnimate(ctx, ML, { mi: j * 3, noHold: true });
        }
        return bb;
    }
});

/* ================================================================== 26 origami \u2014 \u6298\u308A\u7D19 */
jzReg('layout', 'origami', {
    plan: function (rng, cut, st) {
        var port = cut.H > cut.W * 1.08;
        return { font: rng.pick(jzFontsOf(st, ['serif', 'display'])), mode: !port && cut.n > 6 && rng.chance(0.6) ? 'gate' : rng.pick(['blintz', 'blintz', 'gate']), col: rng.pick(['accent', 'accent', 'ink']), order: rng.int(0, 3) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), i, q;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'serif')), paper0 = ld2_light(sc), dk = ld2_dark(sc), lt = ld2_light(sc), back = null;
        var cand = jzP(ctx, 'col', 'accent') === 'ink' ? [sc.accent2, sc.accent, sc.ink] : [sc.accent, sc.accent2, sc.ink];
        for (i = 0; i < cand.length; i++) if (!back && cand[i] && jzContrast(cand[i], paper0) >= 1.5 && jzContrast(cand[i], sc.bg) >= 1.3) back = cand[i];
        back = back || dk;
        var paper = jzMixHex(paper0, back, 0.05), tc = ld2_onCol(sc, paper) === paper ? dk : (jzContrast(dk, paper) > 3 ? dk : ld2_onCol(sc, paper));
        var crease = jzMixHex(paper, dk, 0.22), cx = W / 2, cy = H / 2, TH = ld2_TH(ctx), lw = Math.max(1, u * 0.001);
        var t0 = jzFlat(c.text), n = jzCount(t0), order = jzP(ctx, 'order', 0);
        // flap angle (deg) of flap q: opens in turn, closes again on the way out; c = cos
        var FA = function (qq) { return TH + 'var p=ioc((time-' + jzN(0.1 + qq * 0.13) + ')/0.36)*166,oF=cl(PO*1.3);if(oF>0)p=Math.min(p,166*(1-ioc((oF-' + jzN(qq * 0.12 * 0.8) + ')/0.6)));var c=Math.cos(p*Math.PI/180);' +
            'function mx(a,b,t){t=Math.max(0,Math.min(0.9,t));return [a[0]+(b[0]-a[0])*t,a[1]+(b[1]-a[1])*t,a[2]+(b[2]-a[2])*t,1];}' +
            'function sh(col,s){return s>=0?mx(col,' + ld2_rgb(lt) + ',s):mx(col,' + ld2_rgb(dk) + ',-s);}'; };
        // everything shrinks into the centre at the very end: one pivot
        var PV = ld2_null(ctx, 'paper pivot', cx, cy);
        jzXf(PV, 'ADBE Anchor Point').setValue([cx, cy]);
        jzSetExpr(jzXf(PV, 'ADBE Scale'), TH + 'var k=(1-ic(cl((PO-0.75)/0.25)))*100;[k,k]');
        var sheet = jzShapeLayer(ctx, 'paper', 0, 0), under = null, over = null, text, size, tr, lead, mi, bbox;
        if (jzP(ctx, 'mode', 'blintz') === 'gate' && !port) {
            text = ld2_brk(t0, 9); tr = 0.04; lead = 1.12; mi = 0.15;
            size = Math.min(ld2_fit(ctx, text, font, W * 0.66, H * 0.4, tr, lead), u * 0.2);
            var m = ld2_measAt(ctx, text, font, size, tr, lead), sw = Math.min(W * 0.86, m.w + size * 1.6), shh = Math.min(H * 0.72, m.h + size * 1.4), x0 = cx - sw / 2, y0 = cy - shh / 2;
            var g1 = jzGrp(sheet, 'creases');
            jzAddPath(g1, [[x0 + sw / 4, y0], [x0 + sw / 4, y0 + shh]], false); jzAddPath(g1, [[x0 + sw * 3 / 4, y0], [x0 + sw * 3 / 4, y0 + shh]], false); jzAddStroke(g1, crease, lw, 80);
            var g2 = jzGrp(sheet, 'sheet'); jzAddRect(g2, sw, shh, 0, cx, cy); jzAddFill(g2, paper);
            var g3 = jzGrp(sheet, 'shadow'); jzAddRect(g3, sw, shh, 0, cx + u * 0.012, cy + u * 0.018); jzAddFill(g3, dk, 30);
            bbox = ld2_box(x0, y0, x0 + sw, y0 + shh);
            var gate = function (S, isUnder) {
                for (var k = 0; k < 2; k++) {
                    var hx = k ? x0 + sw * 3 / 4 : x0 + sw / 4, dir = k ? -1 : 1, hd = FA(k), gd = jzGrp(S, 'door ' + (k + 1));
                    var ge = ld2_sub(gd, 'edge'); jzAddRect(ge, Math.max(1.5, u * 0.0015), shh, 0, dir * sw / 4, 0); var fe = jzAddFill(ge, ld2_shade(sc, back, 0.25));
                    ld2_gX(ge, 'ADBE Vector Group Opacity', hd + 'c>0?100:0');
                    var gp = ld2_sub(gd, 'leaf'); jzAddRect(gp, sw / 4, shh, 0, dir * sw / 8, 0); var fp = jzAddFill(gp, back);
                    jzSetExpr(fp.property('ADBE Vector Fill Color'), hd + 'c>0?sh(' + ld2_rgb(back) + ',-0.25*(1-c)):sh(' + ld2_rgb(paper) + ',' + jzN(k ? -0.09 : 0.03) + '*-c-0.35*(1+c))');
                    jzGX(gd).property('ADBE Vector Position').setValue([hx, cy]);
                    ld2_gX(gd, 'ADBE Vector Scale', hd + '[c*100,100]');
                    ld2_gX(gd, 'ADBE Vector Group Opacity', hd + ((isUnder ? 'c<0' : 'c>=0') + '?100:0'));
                }
            };
            under = jzShapeLayer(ctx, 'doors (open)', 0, 0); gate(under, true);
            var L = ld2_T(ctx, text, font, size, { color: tc, x: cx, y: cy, track: tr, lead: lead });
            jzAnimate(ctx, L, { mi: ld2_miAt(ctx, mi), noHold: ld2_plateHold(ctx) });
            over = jzShapeLayer(ctx, 'doors (closed)', 0, 0); gate(over, false);
        } else {
            // blintz: a diamond sheet, four corners folded to the centre and opened one by one
            var S = Math.min(W * 0.84, H * 0.84, u * 0.95), R = S / 2;
            var C = [[cx, cy - R], [cx + R, cy], [cx, cy + R], [cx - R, cy]], M = [];
            for (i = 0; i < 4; i++) M.push([(C[i][0] + C[(i + 1) % 4][0]) / 2, (C[i][1] + C[(i + 1) % 4][1]) / 2]);
            var gc = jzGrp(sheet, 'creases');
            for (i = 0; i < 4; i++) jzAddPath(gc, [M[i], M[(i + 1) % 4]], false);
            jzAddStroke(gc, crease, lw, 80);
            var gd2 = jzGrp(sheet, 'diagonals'); jzAddPath(gd2, [C[0], C[2]], false); jzAddPath(gd2, [C[1], C[3]], false); jzAddStroke(gd2, crease, lw, 35);
            var gs = jzGrp(sheet, 'sheet'); jzAddPath(gs, C, true); jzAddFill(gs, paper);
            var gsh = jzGrp(sheet, 'shadow'); jzAddPath(gsh, [[C[0][0] + u * 0.012, C[0][1] + u * 0.018], [C[1][0] + u * 0.012, C[1][1] + u * 0.018], [C[2][0] + u * 0.012, C[2][1] + u * 0.018], [C[3][0] + u * 0.012, C[3][1] + u * 0.018]], true); jzAddFill(gsh, dk, 30);
            text = n <= 4 ? t0 : ld2_brk(t0, n <= 9 ? Math.ceil(n / 2) : Math.ceil(n / 3)); tr = 0.02; lead = 1.08; mi = 0.12;
            size = Math.min(ld2_fit(ctx, text, font, R * 0.94 * 0.84, R * 0.94 * 0.84, tr, lead), u * 0.2);
            bbox = ld2_box(cx - R, cy - R, cx + R, cy + R);
            var flaps = function (Sx, isUnder) {
                for (var qq = 0; qq < 4; qq++) {
                    var k = (qq + order) % 4, a = M[(k + 3) % 4], b = M[k], hm = [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2];
                    var ux = b[0] - a[0], uy = b[1] - a[1], Lh = Math.sqrt(ux * ux + uy * uy); ux /= Lh; uy /= Lh;
                    var dx = cx - hm[0], dy = cy - hm[1], ly = dx * -uy + dy * ux;       // apex (the centre) in the hinge frame
                    var lk = [0.04, -0.1, -0.05, 0.02][k], hd = FA(qq), gf = jzGrp(Sx, 'flap ' + (k + 1));
                    var ge = ld2_sub(gf, 'edge'); jzAddPath(ge, [[-Lh / 2, 0], [0, ly], [Lh / 2, 0]], false);
                    var st = jzAddStroke(ge, crease, lw);
                    jzSetExpr(st.property('ADBE Vector Stroke Color'), hd + 'c>0?' + ld2_rgb(ld2_shade(sc, back, 0.2)) + ':' + ld2_rgb(crease));
                    var gp = ld2_sub(gf, 'leaf'); jzAddPath(gp, [[-Lh / 2, 0], [Lh / 2, 0], [0, ly]], true); var fp = jzAddFill(gp, back);
                    jzSetExpr(fp.property('ADBE Vector Fill Color'), hd + 'c>0?sh(' + ld2_rgb(back) + ',-0.3*(1-c)):sh(' + ld2_rgb(paper) + ',' + jzN(lk) + '*-c-0.32*(1+c))');
                    jzGX(gf).property('ADBE Vector Position').setValue(hm);
                    jzGX(gf).property('ADBE Vector Rotation').setValue(Math.atan2(uy, ux) * 180 / Math.PI);
                    ld2_gX(gf, 'ADBE Vector Scale', hd + '[100,c*100]');
                    ld2_gX(gf, 'ADBE Vector Group Opacity', hd + ((isUnder ? 'c<0' : 'c>=0') + '?100:0'));
                }
            };
            under = jzShapeLayer(ctx, 'flaps (open)', 0, 0); flaps(under, true);
            var L2 = ld2_T(ctx, text, font, size, { color: tc, x: cx, y: cy, track: tr, lead: lead });
            jzAnimate(ctx, L2, { mi: ld2_miAt(ctx, mi), noHold: ld2_plateHold(ctx) });
            over = jzShapeLayer(ctx, 'flaps (closed)', 0, 0); flaps(over, false);
            L = L2;
        }
        jzSetExpr(jzXf(sheet, 'ADBE Opacity'), TH + '100*K');
        jzSetExpr(jzXf(under, 'ADBE Opacity'), TH + '100*K');
        jzSetExpr(jzXf(over, 'ADBE Opacity'), TH + '100*K');
        ld2_ng([sheet, under, over]);
        var bbL = jzBB(L), list = [sheet, under, L, over];
        for (i = 0; i < list.length; i++) ld2_parent(list[i], PV, jzXf(list[i], 'ADBE Position').value[0], jzXf(list[i], 'ADBE Position').value[1]);
        return bbL || bbox;
    }
});

/* ================================================================== 27 zipper \u2014 \u30B8\u30C3\u30D1\u30FC */
jzReg('layout', 'zipper', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), cloth: rng.pick(['ink', 'accent', 'ink']), dir: rng.pick([1, -1]), stitch: rng.chance(0.8) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), i, k;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), t0 = jzFlat(c.text), n = jzCount(t0), vert = port && n <= 10 && !jzHasLatin(t0);
        // work in a frame where the seam runs along +x (vertical zip: a parent null swaps x and y)
        var Lw = vert ? H : W, Lh = vert ? W : H, x0 = Lw * 0.05, x1 = Lw * 0.95, tr = 0.05, lead = vert ? 1.05 : 1.1;
        var text = vert ? jzVertical(t0) : ld2_brk(t0, port ? 5 : 9);
        var size = Math.min(ld2_fit(ctx, text, font, vert ? Lh * 0.44 : Lw * 0.66, vert ? Lw * 0.64 : Lh * 0.36, vert ? 0 : tr, lead), u * 0.2), m = ld2_measAt(ctx, text, font, size, vert ? 0 : tr, lead);
        var G = (vert ? m.w : m.h) / 2 + size * 0.55, tIn = jzClamp(c.dur * 0.3, 0.45, 1), tOut = Math.max(0.3, (c.outDur || 0.3) + 0.2), dir = jzP(ctx, 'dir', 1) < 0 ? -1 : 1;
        var cy = Lh / 2, cloth = ld2_plateCol(sc, jzP(ctx, 'cloth', 'ink') === 'accent' ? [sc.accent, sc.ink] : [sc.ink, sc.accent]);
        var tape = ld2_shade(sc, cloth, -0.25), toothC = jzMixHex(sc.sub, ld2_light(sc), 0.4), tw = u * 0.022, pitch = u * 0.02;
        var ZH = ld2_TH(ctx) + 'var X0=' + jzN(x0) + ',X1=' + jzN(x1) + ',GG=' + jzN(G) + ',CYZ=' + jzN(cy) + ',DZ=' + dir + ';' +
            'var sp=ioc((time-0.22)/' + jzN(tIn) + ')*(1-ioc((time-(' + jzN(c.dur - tOut - 0.22) + '))/' + jzN(tOut) + ')),xs=DZ>0?X0+(X1-X0)*sp:X1+(X0-X1)*sp,za=DZ>0?X0:xs,zb=DZ>0?xs:X1,wb=1+0.02*Math.sin(time*2);' +
            'function gp(x){return (x<=za||x>=zb||zb-za<1)?0:GG*Math.pow(Math.sin(Math.PI*(x-za)/(zb-za)),0.75)*wb;}' +
            'var off=(1-oc(time/0.28)+ic((time-(' + jzN(c.dur - 0.22) + '))/0.22))*' + jzN(Lh * 0.62) + ';\n';
        // the lyric sits under the cloth
        var L = ld2_T(ctx, text, font, size, { color: sc.fg, x: W / 2, y: H / 2, track: vert ? 0 : tr, lead: lead });
        jzAnimate(ctx, L, { mi: ld2_miAt(ctx, 0.15) });
        var bb = jzBB(L);
        var FR = null;
        if (vert) { FR = ld2_null(ctx, 'zip frame', 0, 0); jzXf(FR, 'ADBE Rotate Z').setValue(90); jzXf(FR, 'ADBE Scale').setValue([100, -100]); }
        // lens outline (unit width 100): upper edge left->right, lower edge back
        var lens = function (hh) {
            var pts = [], q, N2 = 36;
            for (q = 0; q <= N2; q++) pts.push([q / N2 * 100, -hh * Math.pow(Math.sin(Math.PI * q / N2), 0.75)]);
            for (q = N2 - 1; q > 0; q--) pts.push([q / N2 * 100, hh * Math.pow(Math.sin(Math.PI * q / N2), 0.75)]);
            return pts;
        };
        var lensXf = function (g) {
            ld2_gX(g, 'ADBE Vector Position', ZH + '[za,CYZ]');
            ld2_gX(g, 'ADBE Vector Scale', ZH + 'var w=zb-za;w<1?[0,0]:[w,100*wb]');
        };
        // cloth: two halves (slide in from the edges, out at the very end) + tape; the gap lens cuts through them (inverted matte)
        var CL = jzShapeLayer(ctx, 'zip cloth', 0, 0);
        var gT = jzGrp(CL, 'teeth');
        var nT = Math.floor((x1 - x0) / pitch + 1e-6);
        for (k = 0; k <= nT; k++) {
            var xk = x0 + k * pitch, up = k % 2 ? -1 : 1, gk = ld2_sub(gT, 'tooth ' + (k + 1));
            jzAddRect(gk, pitch * 0.68, tw * 0.7, 0, 0, 0); jzAddFill(gk, toothC);
            var TK = ZH + 'var x=' + jzN(xk) + ',g=gp(x),d=(gp(x+1)-gp(x-1))/2;';
            ld2_gX(gk, 'ADBE Vector Position', TK + '[x,g<0.5?CYZ+' + jzN(up * tw * 0.7 * 0.28) + ':CYZ+' + up + '*(g+' + jzN(tw * 0.15) + ')]');
            ld2_gX(gk, 'ADBE Vector Rotation', TK + 'g<0.5?0:Math.atan(' + up + '*d)*180/Math.PI');
        }
        ld2_gX(gT, 'ADBE Vector Group Opacity', ZH + 'off<1?100:0');
        var stitch = !!jzP(ctx, 'stitch', true), stC = ld2_shade(sc, cloth, 0.25), sw = Math.max(1, u * 0.0022);
        if (stitch) {
            var gsc = jzGrp(CL, 'stitch lens'); jzAddPath(gsc, lens(G + tw * 1.6), true);
            var ss2 = jzAddStroke(gsc, stC, sw, 80);
            try { var ds2 = ss2.property('ADBE Vector Stroke Dashes'); ds2.addProperty('ADBE Vector Stroke Dash 1').setValue(u * 0.012); ds2.addProperty('ADBE Vector Stroke Gap 1').setValue(u * 0.01); } catch (eD2) {}
            lensXf(gsc);
        }
        var gTL = jzGrp(CL, 'tape lens'); jzAddPath(gTL, lens(G + tw), true); jzAddFill(gTL, tape); lensXf(gTL);
        for (var s2 = -1; s2 <= 1; s2 += 2) {
            var gh = jzGrp(CL, s2 < 0 ? 'top half' : 'bottom half');
            if (stitch) {
                // straight stitches where the zip is closed (trimmed to x < za and x > zb)
                for (var sd = 0; sd < 2; sd++) {
                    var gsl = ld2_sub(gh, 'stitch ' + (sd ? 'after' : 'before')), xa = -Lw * 0.02, xb = Lw * 1.02;
                    jzAddPath(gsl, [[xa, cy + s2 * tw * 1.6], [xb, cy + s2 * tw * 1.6]], false);
                    var ss = jzAddStroke(gsl, stC, sw, 80);
                    try { var dsh = ss.property('ADBE Vector Stroke Dashes'); dsh.addProperty('ADBE Vector Stroke Dash 1').setValue(u * 0.012); dsh.addProperty('ADBE Vector Stroke Gap 1').setValue(u * 0.01); } catch (eD) {}
                    if (sd) jzAddTrimPaths(gsl, null, ZH + 'cl((zb-(' + jzN(xa) + '))/' + jzN(xb - xa) + ')*100');
                    else jzAddTrimPaths(gsl, ZH + 'cl((za-(' + jzN(xa) + '))/' + jzN(xb - xa) + ')*100', null);
                }
            }
            var gtp = ld2_sub(gh, 'tape'); jzAddRect(gtp, Lw * 1.1, tw, 0, Lw / 2, cy + s2 * tw / 2); jzAddFill(gtp, tape);
            var gcl = ld2_sub(gh, 'cloth'); jzAddRect(gcl, Lw * 1.1, Lh * 0.6, 0, Lw / 2, cy + s2 * Lh * 0.3); jzAddFill(gcl, cloth);
            ld2_gX(gh, 'ADBE Vector Position', ZH + '[0,' + s2 + '*off]');
        }
        var MT = jzShapeLayer(ctx, 'zip gap (matte)', 0, 0), gm = jzGrp(MT, 'gap');
        jzAddPath(gm, lens(G), true); jzAddFill(gm, '#FFFFFF'); lensXf(gm);
        ld2_matte(CL, MT, true);
        // slider + swinging pull tab
        var SLd = jzShapeLayer(ctx, 'zip slider', 0, 0), bw = u * 0.05, bh = u * 0.035, tabL = u * 0.075;
        var gtb = jzGrp(SLd, 'tab');
        jzAddEllipse(gtb, bw * 0.24, bw * 0.24, 0, tabL * 0.75); jzAddFill(gtb, cloth);
        var gtt = ld2_sub(gtb, 'tab plate'); jzAddPath(gtt, [[-bw * 0.28, 0], [bw * 0.28, 0], [bw * 0.34, tabL], [-bw * 0.34, tabL]], true); jzAddFill(gtt, jzMixHex(toothC, ld2_dark(sc), 0.15));
        jzGX(gtb).property('ADBE Vector Position').setValue([dir * bw * 0.1, bh * 0.4]);
        ld2_gX(gtb, 'ADBE Vector Rotation', 'Math.sin(time*3)*0.15*180/Math.PI');
        var gsb = jzGrp(SLd, 'slider'); jzAddPath(gsb, [[-bw * 0.6 * dir, -bh], [bw * 0.5 * dir, -bh * 0.7], [bw * 0.5 * dir, bh * 0.7], [-bw * 0.6 * dir, bh]], true); jzAddFill(gsb, toothC);
        jzSetExpr(jzXf(SLd, 'ADBE Position'), ZH + '[xs,CYZ]');
        jzSetExpr(jzXf(SLd, 'ADBE Opacity'), ZH + 'off<1?100*K:0');
        if (FR) { ld2_parent(CL, FR, 0, 0); ld2_parent(MT, FR, 0, 0); SLd.setParentWithJump(FR); }
        ld2_ng([CL, MT, SLd]);
        return bb;
    }
});

/* ================================================================== 28 sliceStack \u2014 \u30B9\u30E9\u30A4\u30B9\u7A4D\u5C64 */
jzReg('layout', 'sliceStack', {
    plan: function (rng, cut, st) {
        var mode = rng.pick(['blinds', 'blinds', 'stack', 'wave']);
        return { font: rng.pick(jzFontsOf(st, ['display'])), mode: mode, slices: mode === 'blinds' ? rng.int(6, 9) : rng.int(6, 8), gap: rng.range(0.3, 0.55), side: rng.pick(['accent', 'accent', 'sub']), amp: rng.range(0.08, 0.16), dir: rng.pick([1, -1]), plate: rng.pick(['ink', 'accent']) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), i;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), text = ld2_brk(c.text, port ? 5 : 8), tr = 0.02, lead = 1.02, mode = jzP(ctx, 'mode', 'blinds'), blinds = mode === 'blinds';
        var size = Math.min(ld2_fit(ctx, text, font, W * (blinds ? 0.74 : 0.78), H * (blinds ? 0.46 : 0.42), tr, lead), u * 0.3), m = ld2_measAt(ctx, text, font, size, tr, lead);
        var Sn = Math.max(2, Math.round(jzP(ctx, 'slices', 7))), tbx = W / 2, tby = H / 2, TH = ld2_TH(ctx), mot = ctx.fx && ctx.fx.motion != null ? ctx.fx.motion : 0.7;
        var PC = ld2_precomp(ctx, 'JZ slice art'), pctx = PC.ctx, L, bb;
        if (blinds) {
            // venetian blind: slats carrying the lyric turn open, a ripple runs through them, they turn shut again
            var padX = size * 0.45, padY = size * 0.3, bh = m.h + padY * 2, hs = bh / Sn, gp = hs * 0.08, x0 = tbx - m.w / 2 - padX, w = m.w + padX * 2, yTop = tby - bh / 2;
            var pc = ld2_plateCol(sc, jzP(ctx, 'plate', 'ink') === 'accent' ? [sc.accent, sc.ink] : [sc.ink, sc.accent]), tc = ld2_onCol(sc, pc), backC = ld2_shade(sc, pc, -0.45);
            jzRectLayer(pctx, 'plate', tbx, tby, w, bh, pc);
            L = ld2_T(pctx, text, font, size, { color: tc, x: tbx, y: tby, track: tr, lead: lead });
            jzAnimate(pctx, L, { mi: 0, noHold: true });
            var SLn = jzShapeLayer(pctx, 'slat edges', 0, 0), gl = jzGrp(SLn, 'edges'), elw = Math.max(1, hs * 0.04);
            for (i = 0; i < Sn; i++) jzAddRect(gl, w, elw, 0, tbx, yTop + i * hs + hs - gp / 2 - elw / 2);
            jzAddFill(gl, ld2_dark(sc), 35);
            // rail + cords
            var RA = TH + 'var RA=oc(time/0.3)*K;', RL = jzShapeLayer(ctx, 'blind rail', 0, 0), gr = jzGrp(RL, 'rail');
            jzAddRect(gr, w + size * 0.16, hs * 0.3, 0, tbx, yTop - hs * 0.4); jzAddFill(gr, ld2_shade(sc, pc, -0.2));
            jzSetExpr(jzXf(RL, 'ADBE Opacity'), RA + '100*RA');
            var CD = jzShapeLayer(ctx, 'blind cords', 0, yTop - hs * 0.3), gcd = jzGrp(CD, 'cords');
            jzAddRect(gcd, Math.max(1, u * 0.0015), bh + hs * 0.3, 0, x0 + w * 0.2, (bh + hs * 0.3) / 2); jzAddRect(gcd, Math.max(1, u * 0.0015), bh + hs * 0.3, 0, x0 + w * 0.8, (bh + hs * 0.3) / 2); jzAddFill(gcd, sc.sub);
            jzSetExpr(jzXf(CD, 'ADBE Scale'), RA + '[100,100*RA]');
            jzSetExpr(jzXf(CD, 'ADBE Opacity'), RA + '60*RA'); ld2_ng([RL, CD]);
            for (i = 0; i < Sn; i++) {
                var f = Sn > 1 ? i / (Sn - 1) : 0, yc = yTop + i * hs + hs / 2;
                var hd = TH + 'var ph=(1-ob(cl((time-' + jzN(0.05 + f * 0.25) + ')/0.45),1.3))*90+Math.sin(time*2.4-' + jzN(i * 0.7) + ')*' + jzN(16 * mot) + '*cl((time-0.6)/0.4)-ic(cl(PO*1.3-' + jzN(f * 0.3) + '))*90,c=Math.cos(ph*Math.PI/180);';
                var SL = jzNoGhost(ld2_use(ctx, PC.comp, 'slat ' + (i + 1)));
                ld2_mask(SL, ld2_rectShape(x0, yc - (hs - gp) / 2, x0 + w, yc + (hs - gp) / 2));
                jzXf(SL, 'ADBE Anchor Point').setValue([tbx, yc]); jzXf(SL, 'ADBE Position').setValue([tbx, yc]);
                jzSetExpr(jzXf(SL, 'ADBE Scale'), hd + '[100,100*c]');
                jzSetExpr(jzXf(SL, 'ADBE Opacity'), hd + 'Math.abs(c)*' + jzN(hs - gp) + '<0.6?0:100*K');
                var fl = jzEffect(SL, 'ADBE Fill', 'JZ Slat Shade');
                jzEX(fl, 3, hd + 'c>0?' + ld2_rgb(ld2_dark(sc)) + ':' + ld2_rgb(backC));
                jzEX(fl, 7, hd + 'c>0?0.35*(1-c):1');
            }
            return ld2_box(x0, yTop, x0 + w, yTop + bh);
        }
        // stack / wave: horizontal slices of the lyric pulled apart, each with a coloured side face under it
        var hs2 = (m.h + size * 0.1) / Sn, gp2 = hs2 * jzP(ctx, 'gap', 0.4), totH = Sn * hs2 + (Sn - 1) * gp2, y0 = H / 2 - totH / 2;
        var sideC = jzP(ctx, 'side', 'accent') === 'accent' ? sc.accent : jzMixHex(sc.fg, sc.bg, 0.55), dep = Math.min(gp2 * 0.9, size * 0.08);
        var A = size * jzP(ctx, 'amp', 0.12) * (0.5 + 0.7 * mot), dr = jzP(ctx, 'dir', 1) < 0 ? -1 : 1;
        L = ld2_T(pctx, text, font, size, { color: sc.fg, x: tbx, y: tby, track: tr, lead: lead });
        jzAnimate(pctx, L, { mi: 0, noHold: true });
        for (i = 0; i < Sn; i++) {
            var f2 = Sn > 1 ? i / (Sn - 1) : 0.5, by0 = tby - Sn * hs2 / 2 + i * hs2, dy = y0 + i * (hs2 + gp2) - by0, alt = i % 2 ? 1 : -1;
            var hx = TH + 'var dx=' + (mode === 'wave' ? jzN(A) + '*Math.sin(' + jzN(f2 * Math.PI * 2 * 0.9) + '+time*' + jzN(2.2 * dr) + ')' : jzN(A * 1.4 * (f2 - 0.5) * 2 * dr) + '*(0.75+0.25*Math.cos(time*1.1))') +
                '+(1-oe((time-' + jzN(i * 0.025) + ')/0.45))*' + jzN(W * 0.7 * -alt) + '+ic(cl(PO*1.3-' + jzN(f2 * 0.3) + '))*' + jzN(W * 0.8 * alt) + ';';
            var SD2 = jzNoGhost(ld2_use(ctx, PC.comp, 'slice side ' + (i + 1)));
            ld2_mask(SD2, ld2_rectShape(-W, by0 + hs2 - dep - 0.5, W * 2, by0 + hs2));
            jzSetExpr(jzXf(SD2, 'ADBE Position'), hx + '[value[0]+dx+' + jzN(dep * 0.5) + ',value[1]+' + jzN(dy + dep) + ']');
            var fs = jzEffect(SD2, 'ADBE Fill', 'JZ Side'); jzEP(fs, 3, jzHex(sideC));
            var SF = ld2_use(ctx, PC.comp, 'slice ' + (i + 1));
            ld2_mask(SF, ld2_rectShape(-W, by0, W * 2, by0 + hs2 + 0.5));
            jzSetExpr(jzXf(SF, 'ADBE Position'), hx + '[value[0]+dx,value[1]+' + jzN(dy) + ']');
        }
        var w2 = m.w / 2 + A * 1.4;
        return ld2_box(tbx - w2, y0, tbx + w2, y0 + totH);
    }
});

/* ================================================================== 29 glitchGrid \u2014 \u30B0\u30EA\u30C3\u30C1\u683C\u5B50 */
jzReg('layout', 'glitchGrid', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display'])), rate: rng.int(3, 6), inv: rng.range(0.1, 0.22), labels: rng.chance(0.75), gut: rng.range(0.008, 0.016) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, s = c.seed, u = jzU(ctx), port = jzPortrait(ctx), sq = !port && W < H * 1.45, i, r, cc;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), rate = Math.max(1, Math.round(jzP(ctx, 'rate', 4))), invP = jzP(ctx, 'inv', 0.15), labels = !!jzP(ctx, 'labels', true);
        var C = port ? 3 : sq ? 3 : 4, R = port ? 5 : 3, g = u * jzP(ctx, 'gut', 0.012), mx = W * 0.04, my = H * 0.05;
        var cw = (W - mx * 2 - g * (C - 1)) / C, ch = (H - my * 2 - g * (R - 1)) / R, crow = Math.floor(R / 2);
        var cc0 = port ? 0 : C === 4 ? 1 : 0, cc1 = port ? C - 1 : C === 4 ? 2 : C - 1;
        var text = jzFlat(c.text), t2 = ld2_brk(c.text, port ? 5 : 8), lw = Math.max(1, u * 0.0015);
        var pal = [sc.fg, sc.accent, sc.sub, sc.ghostA, sc.ghostB], cols = [], onc = [];
        for (i = 0; i < pal.length; i++) if (pal[i] && jzContrast(pal[i], sc.bg) > 1.4) { cols.push(jzMixHex(pal[i], sc.bg, 0.25)); }
        if (!cols.length) cols.push(sc.fg);
        var CA = [], OA = [];
        for (i = 0; i < cols.length; i++) { CA.push(ld2_rgb(cols[i])); OA.push(ld2_rgb(ld2_onCol(sc, cols[i]))); }
        var TH = ld2_TH(ctx), base = TH + 'var st=Math.floor(time*24),COLS=[' + CA.join(',') + '],ONC=[' + OA.join(',') + '],NC=' + cols.length + ',RATE=' + rate + ',INV=' + jzN(invP) + ';';
        var cell = function (id) { return 'var ID=' + id + ',T1=' + jzN(0.03 + jzR(s, id, 1) * 0.35) + ',R3=' + jzN(jzR(s, id, 3)) + ',H4=' + (jzHash(s, id, 4) % 7) + ';' +
            'var on=time>T1&&(time-T1>0.12||hh(SD*0.37+ID*13+st*1.7)<0.6)&&!(PO>R3*0.8+0.1),kk=Math.floor((st+H4)/RATE),inv=hh(ID*7.3+kk*3.1+SD*0.11)<INV,' +
            'ci=Math.floor(hh(ID*5.9+kk*2.7+SD*0.23)*NC)%NC,zoom=1.4+2.2*hh(ID*3.7+kk*1.9+SD*0.31),fresh=(st+H4)%RATE==0;'; };
        var rect = function (r2, c0, c1) { return [mx + c0 * (cw + g), my + r2 * (ch + g), (c1 - c0 + 1) * cw + (c1 - c0) * g, ch]; };
        // cell plates, borders and fresh-frame slice bars (one shape layer)
        var CS = jzNoGhost(jzShapeLayer(ctx, 'cells', 0, 0)), ids = [];
        for (r = 0; r < R; r++) for (cc = 0; cc < C; cc++) {
            if (r === crow && cc >= cc0 && cc <= cc1) continue;
            var id = r * C + cc, q = rect(r, cc, cc), hd = base + cell(id);
            ids.push([id, q]);
            var gcl = jzGrp(CS, 'cell ' + (id + 1));
            var gb = ld2_sub(gcl, 'slice bar'); jzAddRect(gb, q[2], q[3] * 0.06, 0, 0, 0);
            var fb = jzAddFill(gb, sc.fg, 60);
            jzSetExpr(fb.property('ADBE Vector Fill Color'), hd + 'inv?ONC[ci]:COLS[ci]');
            ld2_gX(gb, 'ADBE Vector Position', hd + '[0,' + jzN(-q[3] / 2) + '+' + jzN(q[3]) + '*hh(ID*4.3+st*0.9+SD*0.71)+' + jzN(q[3] * 0.03) + ']');
            ld2_gX(gb, 'ADBE Vector Group Opacity', hd + 'fresh?100:0');
            var gp = ld2_sub(gcl, 'plate'); jzAddRect(gp, q[2], q[3], 0, 0, 0);
            jzAddStroke(gp, jzMixHex(sc.bg, sc.fg, 0.2), lw);
            var fp = jzAddFill(gp, sc.bg);
            jzSetExpr(fp.property('ADBE Vector Fill Color'), hd + 'inv?COLS[ci]:' + ld2_rgb(jzMixHex(sc.bg, sc.fg, 0.04)));
            jzSetExpr(fp.property('ADBE Vector Fill Opacity'), hd + 'inv?85:100');
            jzGX(gcl).property('ADBE Vector Position').setValue([q[0] + q[2] / 2, q[1] + q[3] / 2]);
            ld2_gX(gcl, 'ADBE Vector Group Opacity', hd + 'on?100*K:0');
        }
        // zoomed glitch copies of the line, one per cell, clipped by the cell (alpha matte)
        for (i = 0; i < ids.length; i++) {
            var id2 = ids[i][0], q2 = ids[i][1], hc = base + cell(id2), fs0 = Math.min(q2[3] * 0.9, q2[2] * 0.9) * 0.5;
            var T = ld2_T(ctx, text, font, fs0, { color: sc.fg, x: q2[0] + q2[2] / 2, y: q2[1] + q2[3] / 2, name: 'CH.' + jzPad(id2 + 1, 2) });
            jzSetExpr(jzXf(T, 'ADBE Scale'), hc + '[zoom*100,zoom*100]');
            jzSetExpr(jzXf(T, 'ADBE Position'), hc + 'var ox=(hh(ID*2.3+kk*4.1+SD*0.41)*2-1)*' + jzN(q2[2] * 0.5) + ',oy=(hh(ID*6.1+kk*1.3+SD*0.53)*2-1)*' + jzN(q2[3] * 0.3) +
                ',sl=fresh?(hh(ID*9.1+st*0.7+SD*0.61)*2-1)*' + jzN(q2[2] * 0.12) + ':0;[value[0]+ox+sl,value[1]+oy]');
            jzSetExpr(jzXf(T, 'ADBE Opacity'), hc + 'on?(inv?90:60)*K:0');
            var fe = jzEffect(T, 'ADBE Fill', 'JZ Channel Colour');
            jzEX(fe, 3, hc + 'inv?ONC[ci]:COLS[ci]');
            var MK = jzRectLayer(ctx, 'CH.' + jzPad(id2 + 1, 2) + ' window', q2[0] + q2[2] / 2, q2[1] + q2[3] / 2, q2[2], q2[3], '#FFFFFF');
            ld2_matte(T, MK); ld2_ng([T, MK]);
        }
        // channel labels: one left-aligned text, one label per line, placed on its cell
        if (labels && ids.length) {
            var ls = Math.max(10 * ctx.u, u * 0.013), LD = ls * 1.2, lines = [], LX = [], LY = [], LS = [];
            for (i = 0; i < ids.length; i++) { lines.push('CH.' + jzPad(ids[i][0] + 1, 2) + '  x2.0'); LX.push(ids[i][1][0] + u * 0.01); LY.push(ids[i][1][1] + u * 0.016); }
            var LB = jzText(ctx, lines.join('\r'), { font: jzMonoF(ctx), size: ls, color: sc.sub, x: 0, y: 0, align: 'left', leading: LD, name: 'channel labels' });
            jzXf(LB, 'ADBE Anchor Point').setValue([0, 0]); jzXf(LB, 'ADBE Position').setValue([0, 0]);
            var cells = [];
            for (i = 0; i < ids.length; i++) cells.push('(function(){' + cell(ids[i][0]) + 'return [on,zoom];})()');
            var LH = base + 'var CL=[' + cells.join(',') + '];';
            var NM = []; for (i = 0; i < ids.length; i++) NM.push('"' + jzPad(ids[i][0] + 1, 2) + '"');
            try {
                LB.property('ADBE Text Properties').property('ADBE Text Document').expression = LH + 'var NM=[' + NM.join(',') + '],o=[];for(var q=0;q<CL.length;q++){var z=Math.round(CL[q][1]*10)/10,zs=String(z);if(zs.indexOf(".")<0)zs+=".0";o.push("CH."+NM[q]+"  x"+zs);}o.join("\\r")';
            } catch (eL) { jzWarn('glitchGrid labels: ' + eL.toString()); }
            var lh2 = LH + 'var li=Math.floor((textIndex-1)/11),LX=' + jzArrExpr(LX) + ',LY=' + jzArrExpr(LY) + ';';
            ld2_posAnim(LB, 'JZ Label Place', Math.max(W, H) * 2, lh2, 'dx=LX[li];dy=LY[li]-li*' + jzN(LD) + '+' + jzN(LD2_CY * ls));
            ld2_opAnim(LB, 'JZ Label Show', lh2, 'a=CL[li]&&CL[li][0]?0.8*K:0'); jzNoGhost(LB);
        }
        // the clean cell
        var qc = rect(crow, cc0, cc1), CF = jzShapeLayer(ctx, 'clean cell', qc[0] + qc[2] / 2, qc[1] + qc[3] / 2), gcf = jzGrp(CF, 'frame');
        var rcf = jzAddRect(gcf, qc[2], qc[3], 0, 0, 0);
        jzSetExpr(rcf.property('ADBE Vector Rect Size'), TH + '[' + jzN(qc[2]) + '*oe(time/0.35),' + jzN(qc[3]) + ']');     // before the stroke is added (AE rule)
        jzAddStroke(gcf, sc.accent, Math.max(2, u * 0.003));
        jzSetExpr(jzXf(CF, 'ADBE Opacity'), TH + '100*K'); jzNoGhost(CF);
        if (labels) {
            var RC = jzText(ctx, 'REC \u25CF CLEAN', { font: jzMonoF(ctx), size: Math.max(10 * ctx.u, u * 0.014), color: sc.accent, x: 0, y: 0, align: 'left', name: 'REC label' });
            jzXf(RC, 'ADBE Anchor Point').setValue([0, 0]);
            jzXf(RC, 'ADBE Position').setValue([qc[0] + u * 0.012, qc[1] + u * 0.018 + LD2_CY * Math.max(10 * ctx.u, u * 0.014)]);
            jzSetExpr(jzXf(RC, 'ADBE Opacity'), TH + '(Math.floor(time*24)%4<3?100:30)*K'); jzNoGhost(RC);
        }
        var size = Math.min(ld2_fit(ctx, t2, font, qc[2] * 0.88, qc[3] * 0.8, 0.04, 1.1), u * 0.24);
        var L = ld2_T(ctx, t2, font, size, { color: sc.fg, x: qc[0] + qc[2] / 2, y: qc[1] + qc[3] / 2, track: 0.04, lead: 1.1 });
        jzAnimate(ctx, L, { mi: 0 });
        return jzBB(L);
    }
});

/* ================================================================== 30 mosaicTiles \u2014 \u30BF\u30A4\u30EB\u753B */
// AE-native: the fat lyric (precomp) -> reveal (wipe / block dissolve / growing iris mask) -> Mosaic on a grid aligned to the
// tile pitch -> grout lines cut out with an inverted matte; a faint floor of empty tiles behind
jzReg('layout', 'mosaicTiles', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display'])), D: rng.int(9, 12), wave: rng.pick(['diag', 'random', 'center']), rot: rng.chance(0.4), col: rng.pick(['fg', 'accent', 'fg']), floor: rng.chance(0.75) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx);
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), text = ld2_brk(c.text, port ? 4 : 7), lead = 1.15, D = jzP(ctx, 'D', 10), wave = jzP(ctx, 'wave', 'diag');
        if (!jzCount(text)) return null;
        var m100 = ld2_meas(ctx, text, font, 0, lead), cols = Math.ceil(m100.w / 100 * D) + 2, rows = Math.ceil(m100.h / 100 * D) + 2;
        var p0 = Math.min(W * 0.88 / cols, H * 0.66 / rows), nx = Math.max(4, Math.round(W / p0)), ny = Math.max(4, Math.round(H / p0)), px = W / nx, py = H / ny, p = Math.min(px, py);
        var gx = Math.round((W / 2 - cols * px / 2) / px) * px, gy = Math.round((H / 2 - rows * py / 2) / py) * py, gw = cols * px, gh = rows * py;
        var col = jzP(ctx, 'col', 'fg') === 'accent' && jzContrast(sc.accent, sc.bg) > 1.8 ? sc.accent : sc.fg, TH = ld2_TH(ctx), span = jzClamp(c.dur * 0.3, 0.3, 0.8);
        // grout floor: every cell of the panel as a faint tile
        if (jzP(ctx, 'floor', true)) {
            var FL = jzShapeLayer(ctx, 'tile floor', 0, 0), gf = jzGrp(FL, 'tiles'), tw = p * 0.86;
            jzAddRect(gf, tw, tw, 0, gx - px / 2, gy - py / 2); jzAddFill(gf, jzMixHex(sc.bg, sc.fg, 0.07));
            var r1 = jzVecs(gf).addProperty('ADBE Vector Filter - Repeater'); r1.property('ADBE Vector Repeater Copies').setValue(cols + 2);
            r1.property('ADBE Vector Repeater Transform').property('ADBE Vector Repeater Position').setValue([px, 0]);
            var r2 = jzVecs(gf).addProperty('ADBE Vector Filter - Repeater'); r2.property('ADBE Vector Repeater Copies').setValue(rows + 2);
            r2.property('ADBE Vector Repeater Transform').property('ADBE Vector Repeater Position').setValue([0, py]);
            jzSetExpr(jzXf(FL, 'ADBE Opacity'), TH + '100*oc(time/0.4)*K'); jzNoGhost(FL);
        }
        // the lyric, drawn fat, in a precomp
        var PC = ld2_precomp(ctx, 'JZ mosaic art'), fsz = D * p;
        var L = ld2_T(PC.ctx, text, font, fsz, { color: col, x: W / 2, y: H / 2, lead: lead, stroke: p * 0.35, strokeColor: col, strokeOver: false });
        jzAnimate(PC.ctx, L, { mi: 0, noHold: true, treat: false });
        var ML = ld2_use(ctx, PC.comp, 'mosaic');
        var IN = TH + 'var pin=cl((time-0.08)/' + jzN(span + 0.22) + '),pout=cl(PO*1.25);';
        if (wave === 'center') {
            var Rm = Math.sqrt(gw * gw + gh * gh) / 2, k = 0.5523 * Rm, sh = new Shape();
            sh.vertices = [[W / 2, H / 2 - Rm], [W / 2 + Rm, H / 2], [W / 2, H / 2 + Rm], [W / 2 - Rm, H / 2]];
            sh.inTangents = [[-k, 0], [0, -k], [k, 0], [0, k]]; sh.outTangents = [[k, 0], [0, k], [-k, 0], [0, -k]]; sh.closed = true;
            var mk = ld2_mask(ML, sh);
            jzSetExpr(mk.property('ADBE Mask Offset'), IN + '-' + jzN(Rm * 0.98) + '*(1-oc(pin))');
        } else if (wave === 'diag') {
            var lwp = jzEffect(ML, 'ADBE Linear Wipe', 'JZ Tiles In');
            jzEP(lwp, 2, 315); jzEP(lwp, 3, p * 3);
            jzEX(lwp, 1, IN + '(1-pin)*100');
        }
        var bd = jzEffect(ML, 'ADBE Block Dissolve', 'JZ Tiles Out');
        jzEP(bd, 2, px); jzEP(bd, 3, py); jzEP(bd, 4, 0); jzEP(bd, 5, 0);
        jzEX(bd, 1, IN + (wave === 'random' ? 'Math.max(1-pin,pout)*100' : 'pout*100'));
        var mo = jzEffect(ML, 'ADBE Mosaic', 'JZ Tiles');
        jzEP(mo, 1, nx); jzEP(mo, 2, ny); jzEP(mo, 3, 1);
        // grout: thin lines on the tile grid, cut out of the mosaic (inverted alpha matte)
        var GR = jzShapeLayer(ctx, 'grout (matte)', 0, 0), gl = p * 0.14;
        var gv = jzGrp(GR, 'verticals'); jzAddRect(gv, gl, H * 1.2, 0, 0, H / 2); jzAddFill(gv, '#FFFFFF');
        var rv = jzVecs(gv).addProperty('ADBE Vector Filter - Repeater'); rv.property('ADBE Vector Repeater Copies').setValue(nx + 1);
        rv.property('ADBE Vector Repeater Transform').property('ADBE Vector Repeater Position').setValue([px, 0]);
        var gh2 = jzGrp(GR, 'horizontals'); jzAddRect(gh2, W * 1.2, gl, 0, W / 2, 0); jzAddFill(gh2, '#FFFFFF');
        var rh = jzVecs(gh2).addProperty('ADBE Vector Filter - Repeater'); rh.property('ADBE Vector Repeater Copies').setValue(ny + 1);
        rh.property('ADBE Vector Repeater Transform').property('ADBE Vector Repeater Position').setValue([0, py]);
        ld2_matte(ML, GR, true);
        return ld2_box(gx, gy, gx + gw, gy + gh);
    }
});

/* ================================================================== 31 maskReveal \u2014 \u6587\u5B57\u7A93 */
// the lyric is the alpha matte of a moving pattern layer (stripes / dots / rows of the line / soft shine)
jzReg('layout', 'maskReveal', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display'])), scene: rng.pick(['stripes', 'lines', 'dots', 'shine', 'stripes']), ang: rng.range(20, 35) * rng.pick([1, -1]), speed: rng.range(0.6, 1.2), rim: rng.chance(0.65), label: rng.chance(0.6) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), i, j;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), text = ld2_brk(c.text, port ? 4 : 7), lead = 1.0;
        var size = Math.min(ld2_fit(ctx, text, font, W * 0.88, H * (port ? 0.56 : 0.66), 0, lead), u * 0.46), m = ld2_measAt(ctx, text, font, size, 0, lead);
        var scene = jzP(ctx, 'scene', 'stripes'), ang = jzP(ctx, 'ang', 25), speed = jzP(ctx, 'speed', 0.9), cx = W / 2, cy = H / 2;
        var A = jzContrast(sc.accent, sc.bg) >= 1.8 ? sc.accent : sc.fg, B = null, cand = [sc.fg, ld2_light(sc), sc.accent2];
        for (i = 0; i < cand.length; i++) if (!B && cand[i] && cand[i] !== A && jzContrast(cand[i], sc.bg) >= 1.8) B = cand[i];
        B = B || A;
        var bw = m.w + size, bh = m.h + size, diag = Math.sqrt(bw * bw + bh * bh), TB = 'var TB=time*' + jzN(speed) + ';';
        // the pattern layer (centred on the text), background B
        var P = jzShapeLayer(ctx, 'window scene', cx, cy), gpat;
        if (scene === 'lines') {
            var ls = size * 0.16, unit = jzFlat(c.lineText || c.text) + '\u3000\u30FB\u3000', f = jzBodyF(ctx), uw = Math.max(ls, ld2_measAt(ctx, unit, f, ls, 0, 1).w), rh = ls * 1.3;
            var reps = Math.ceil((bw + uw * 2) / uw) + 1, nr = Math.ceil((bh + rh * 2) / rh) + 1, row = '', lines = [];
            for (j = 0; j < reps; j++) row += unit;
            for (j = 0; j < nr; j++) lines.push(row);
            var gbg = jzGrp(P, 'ground'); jzAddRect(gbg, bw + uw * 2, bh + rh * 2, 0, 0, 0); jzAddFill(gbg, B);
            var LT = jzText(ctx, lines.join('\r'), { font: f, size: ls, color: A, x: 0, y: 0, align: 'left', leading: rh, name: 'window rows' });
            jzXf(LT, 'ADBE Anchor Point').setValue([0, 0]);
            jzXf(LT, 'ADBE Position').setValue([cx - bw / 2 - uw, cy - bh / 2 - rh + LD2_CY * ls]);
            jzSetExpr(jzXf(LT, 'ADBE Position'), TB + '[value[0]-((TB*' + jzN(size * 0.5) + ')%' + jzN(uw) + '),value[1]]');
            var perLine = jzChars(row).length;
            ld2_posAnim(LT, 'JZ Brick Rows', uw, '', 'dx=Math.floor((textIndex-1)/' + perLine + ')%2?' + jzN(-uw / 2) + ':0');
            // rows and ground both need the lyric as matte: two copies of the (animated) lyric, one above each
            P.moveAfter(LT);
            var T0 = ld2_T(ctx, text, font, size, { color: B, x: cx, y: cy, lead: lead, name: c.text });
            jzAnimate(ctx, T0, { mi: 0, treat: false });
            var T1 = T0.duplicate(); T1.moveBefore(LT);
            ld2_matte(LT, T1); ld2_matte(P, T0);
            T0.moveBefore(P);
            gpat = null;
            var Tm = T0;
        } else {
            // pattern first (top), ground second: the pattern is finished before 'ground' is added (AE invalidates older sibling refs)
            gpat = jzGrp(P, 'pattern');
            if (scene === 'dots') {
                var per = size * 0.2, nd = Math.ceil(diag / per) + 3;
                jzAddEllipse(gpat, per * 0.6, per * 0.6, -nd / 2 * per, -nd / 2 * per); jzAddFill(gpat, A);
                var rd1 = jzVecs(gpat).addProperty('ADBE Vector Filter - Repeater'); rd1.property('ADBE Vector Repeater Copies').setValue(nd);
                rd1.property('ADBE Vector Repeater Transform').property('ADBE Vector Repeater Position').setValue([per, 0]);
                var rd2 = jzVecs(gpat).addProperty('ADBE Vector Filter - Repeater'); rd2.property('ADBE Vector Repeater Copies').setValue(nd);
                rd2.property('ADBE Vector Repeater Transform').property('ADBE Vector Repeater Position').setValue([0, per]);
                ld2_gX(gpat, 'ADBE Vector Position', TB + 'var o=(TB*' + jzN(size * 0.3) + ')%' + jzN(per) + ';[o,o]');
                jzGX(gpat).property('ADBE Vector Rotation').setValue(ang);
            } else {
                // stripes (hard) or shine (wide, blurred bands moving along the diagonal)
                var shine = scene === 'shine', per2 = shine ? size * 1.7 : size * 0.26, ns = Math.ceil(diag / per2) + 3;
                jzAddRect(gpat, per2 / 2, diag + size, 0, -ns / 2 * per2, 0); jzAddFill(gpat, shine ? (sc.grad ? sc.grad[1] : B) : A);
                var rs = jzVecs(gpat).addProperty('ADBE Vector Filter - Repeater'); rs.property('ADBE Vector Repeater Copies').setValue(ns);
                rs.property('ADBE Vector Repeater Transform').property('ADBE Vector Repeater Position').setValue([per2, 0]);
                jzGX(gpat).property('ADBE Vector Rotation').setValue(shine ? 45 : ang);
                ld2_gX(gpat, 'ADBE Vector Position', TB + (shine ? 'var o=((TB*0.45)%1)*' + jzN(per2) + ';[o*0.7071,o*0.7071]' : 'var o=(TB*' + jzN(size * 0.5) + ')%' + jzN(per2) + ',a=' + jzN(ang * Math.PI / 180) + ';[o*Math.cos(a),o*Math.sin(a)]'));
                if (shine) { var gb = jzEffect(P, 'ADBE Gaussian Blur 2', 'JZ Shine Soft'); jzEP(gb, 1, per2 * 0.22); }
            }
            gpat = null;
            var gbg2 = jzGrp(P, 'ground');
            jzAddRect(gbg2, diag + size, diag + size, 0, 0, 0); jzAddFill(gbg2, scene === 'shine' && sc.grad ? sc.grad[0] : (scene === 'shine' ? A : B));
            var T2 = ld2_T(ctx, text, font, size, { color: B, x: cx, y: cy, lead: lead, name: c.text });
            jzAnimate(ctx, T2, { mi: 0, treat: false });
            ld2_matte(P, T2);
            Tm = T2;
        }
        var bb = jzBB(Tm);
        if (jzP(ctx, 'rim', true)) {
            var RM = Tm.duplicate(); RM.moveBefore(Tm); RM.name = 'rim';
            try { RM.enabled = true; RM.trackMatteType = TrackMatteType.NO_TRACK_MATTE; } catch (eR) {}
            jzTextDoc(RM, function (td) { td.applyFill = false; td.applyStroke = true; td.strokeColor = jzHex(A); td.strokeWidth = Math.max(1.5, size * 0.012); });
            jzXf(RM, 'ADBE Opacity').setValue(90); jzNoGhost(RM);
        }
        if (jzP(ctx, 'label', true)) {
            var lsz = jzSmallSize(ctx), lab = jzAltCopy(ctx);
            if (jzChars(lab).length > 30) lab = jzChars(lab).slice(0, 29).join('') + '\u2026';
            var LB = jzText(ctx, lab, { font: jzBodyF(ctx), size: lsz, color: sc.sub, x: W / 2 - m.w / 2, y: Math.min(H * 0.95, H / 2 + m.h / 2 + lsz * 1.8), align: 'left', track: 0.2, name: 'label' });
            jzFade(ctx, LB, 0.25, 0.4); jzNoGhost(LB);
        }
        return bb;
    }
});

/* ================================================================== 32 contour \u2014 \u7B49\u9AD8\u7DDA */
jzReg('layout', 'contour', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), fb: rng.pick(jzFontsOf(st, ['display'])), side: rng.pick([1, -1]), rings: rng.int(4, 6), speed: rng.range(0.25, 0.5), col: rng.pick(['sub', 'accent', 'sub']), place: rng.pick(['low', 'center', 'low']) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), i, k;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), fb = jzP(ctx, 'fb', jzFontKeyOf(ctx.st, 'display')), side = jzP(ctx, 'side', 1) < 0 ? -1 : 1;
        var t0 = jzChars(jzStrip(c.text)), big = '';
        for (i = 0; i < t0.length && !big; i++) if (jzIsKanji(t0[i])) big = t0[i];
        for (i = 0; i < t0.length && !big; i++) if (!jzIsPunct(t0[i]) && !ld2_isSmall(t0[i])) big = t0[i];
        if (!big && t0.length) big = t0[0];
        var bs = Math.min(H * 0.95, W * (port ? 1.05 : 0.7)), bx = W / 2 + side * W * (port ? 0.12 : 0.2), by = H * 0.5;
        var lineC = jzP(ctx, 'col', 'sub') === 'accent' ? sc.accent : sc.sub, TH = ld2_TH(ctx);
        // contour rings: a fat stroke in the line colour under a slightly thinner one in the ground colour -> one thin line per level
        if (big) {
            var K = Math.max(1, Math.round(jzP(ctx, 'rings', 5))), gap = bs * 0.03, lw = Math.max(1.2, bs * 0.0028), maxW = 2 * gap * (K + 1) + gap + lw * 2;
            var RH = TH + 'var PH=((time*' + jzN(jzP(ctx, 'speed', 0.35)) + ')%1+1)%1,GR=oc(time/0.9),CI=oc(time/0.5)*K;';
            for (k = K; k >= 1; k--) {
                var wk = '(2*' + jzN(gap) + '*(' + (k - 1) + '+PH)*GR+' + jzN(gap * 0.6) + ')';
                for (var pass = 0; pass < 2; pass++) {
                    var R = jzNoGhost(ld2_G(ctx, big, fb, bs, bx, by, pass ? sc.bg : lineC, 'contour ' + k + (pass ? ' gap' : ' line')));
                    jzTextDoc(R, function (td) { td.applyFill = false; td.applyStroke = true; td.strokeColor = jzHex(pass ? sc.bg : lineC); td.strokeWidth = 0.1; });
                    jzAnimator(R, 'JZ Ring Width', [['ADBE Text Stroke Width', maxW]], RH + 'Math.max(0,Math.min(1,(' + wk + (pass ? '-' : '+') + jzN(lw) + ')/' + jzN(maxW) + '))*100');
                    jzSetExpr(jzXf(R, 'ADBE Opacity'), RH + (pass ? '94*CI' : '80*' + (k === K ? '(1-PH)' : '1') + '*' + jzN(1 - 0.1 * k) + '*CI'));
                }
            }
            var BF = jzNoGhost(ld2_G(ctx, big, fb, bs, bx, by, sc.bg, 'contour core'));
            jzTextDoc(BF, function (td) { td.applyStroke = true; td.strokeColor = jzHex(lineC); td.strokeWidth = lw * 1.6; try { td.strokeOverFill = true; } catch (eS) {} });
            jzSetExpr(jzXf(BF, 'ADBE Opacity'), RH + '100*CI');
        }
        // the lyric, small and solid, in the calm area beside the big character
        var text = ld2_brk(c.text, port ? 6 : 8), tr = 0.06, lead = 1.25;
        var size = Math.min(ld2_fit(ctx, text, font, W * (port ? 0.8 : 0.5), H * 0.3, tr, lead), u * 0.12), m = ld2_measAt(ctx, text, font, size, tr, lead);
        var tx = port ? W / 2 : W / 2 - side * W * 0.2, ty = jzP(ctx, 'place', 'low') === 'low' ? H * (port ? 0.78 : 0.7) : H / 2;
        var mx = jzClamp(tx, W * 0.06 + m.w / 2, W * 0.94 - m.w / 2);
        var L = ld2_T(ctx, text, font, size, { color: sc.fg, x: mx, y: ty, track: tr, lead: lead });
        jzAnimate(ctx, L, { mi: 0 });
        var bb = jzBB(L), ls = jzSmallSize(ctx) * 0.9, LA = TH + 'var LA=oc((time-0.3)/0.4)*K;';
        var LN = jzShapeLayer(ctx, 'rule', bb.x0, bb.y0 - ls * 1.2), gl = jzGrp(LN, 'rule');
        jzAddPath(gl, [[0, 0], [bb.x1 - bb.x0, 0]], false); jzAddStroke(gl, lineC, Math.max(1, u * 0.0015), 80);
        jzSetExpr(jzXf(LN, 'ADBE Scale'), LA + '[100*LA,100]');
        jzSetExpr(jzXf(LN, 'ADBE Opacity'), LA + 'LA>0?100:0');
        var LB = jzText(ctx, big + '  \u2500  No.' + jzLineNo(ctx), { font: jzMonoF(ctx), size: ls, color: sc.sub, x: bb.x0, y: bb.y0 - ls * 2.3, align: 'left', track: 0.2, name: 'contour label' });
        jzSetExpr(jzXf(LB, 'ADBE Opacity'), LA + '100*LA'); ld2_ng([LN, LB]);
        return bb;
    }
});

/* ================================================================== 33 halftoneBig \u2014 \u7DB2\u70B9\u5DE8\u5927\u6587\u5B57 */
// the lyric (fattened) is the alpha matte of a rotated dot screen; one group per screen column (repeater down the column),
// the dot size follows a travelling tone wave
jzReg('layout', 'halftoneBig', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display'])), mode: rng.pick(['duo', 'duo', 'tone']), ang: rng.range(15, 40) * rng.pick([1, -1]), shape: rng.pick(['dot', 'dot', 'line']), speed: rng.range(0.4, 0.8), crop: rng.chance(0.35) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, u = jzU(ctx), port = jzPortrait(ctx), i;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), text = ld2_brk(c.text, port ? 4 : 5), tr = -0.02, lead = 0.98;
        var fitW = jzP(ctx, 'crop', false) ? 1.12 : 0.9;
        var size = Math.min(ld2_fit(ctx, text, font, W * fitW, H * (port ? 0.6 : 0.8), tr, lead), u * 0.7), m = ld2_measAt(ctx, text, font, size, tr, lead);
        var dotC = jzContrast(sc.fg, sc.bg) >= 3 ? sc.fg : (jzContrast(ld2_light(sc), sc.bg) >= 3 ? ld2_light(sc) : sc.fg), duoC = null, cand = [sc.accent, sc.accent2];
        for (i = 0; i < cand.length; i++) if (!duoC && cand[i] && jzContrast(cand[i], sc.bg) >= 1.6 && cand[i] !== dotC) duoC = cand[i];
        duoC = duoC || sc.sub;
        var cx = W / 2, cy = H / 2, TH = ld2_TH(ctx), bb;
        if (jzP(ctx, 'mode', 'duo') === 'duo') {
            var d = size * 0.045, DU = ld2_T(ctx, text, font, size, { color: duoC, x: cx + d, y: cy + d, track: tr, lead: lead, name: 'duo' });
            jzAnimate(ctx, DU, { mi: 0, treat: false }); jzNoGhost(DU);
        }
        var pitch = Math.max(6 * ctx.u, size * 0.068), a = jzP(ctx, 'ang', 25), ar = a * Math.PI / 180, ca = Math.abs(Math.cos(ar)), sa = Math.abs(Math.sin(ar));
        var R = Math.sqrt(m.w * m.w + m.h * m.h) / 2 + pitch;
        var NX = Math.ceil(((m.w * ca + m.h * sa) / 2 + pitch * 2) / pitch), NY = Math.ceil(((m.w * sa + m.h * ca) / 2 + pitch * 2) / pitch), line = jzP(ctx, 'shape', 'dot') === 'line';
        var DS = jzShapeLayer(ctx, 'dot screen', cx, cy);
        jzXf(DS, 'ADBE Rotate Z').setValue(a);
        var TN = TH + 'var GRW=oc(time/0.7)*(1-0.6*ic(PO)),PHT=time*' + jzN(jzP(ctx, 'speed', 0.6)) + ';';
        for (i = -NX; i <= NX; i++) {
            var lx = i * pitch, gcol = jzGrp(DS, 'column ' + (i + NX + 1));
            var TE = TN + 'var f=' + jzN(lx / R * 0.5 + 0.5) + ',tone=cl(0.3+0.7*(0.5+0.5*Math.sin((f*1.6-PHT)*Math.PI))),r=' + jzN(pitch * 0.66) + '*Math.sqrt(tone)*GRW;';
            if (line) { var rc = jzAddRect(gcol, pitch, pitch, 0, lx, -NY * pitch); jzSetExpr(rc.property('ADBE Vector Rect Size'), TE + '[' + jzN(pitch * 1.02) + ',Math.max(0,r*1.44)]'); }
            else { var el = jzAddEllipse(gcol, pitch, pitch, lx, -NY * pitch); jzSetExpr(el.property('ADBE Vector Ellipse Size'), TE + '[Math.max(0,r*2),Math.max(0,r*2)]'); }
            var rp = jzVecs(gcol).addProperty('ADBE Vector Filter - Repeater'); rp.property('ADBE Vector Repeater Copies').setValue(NY * 2 + 1);
            rp.property('ADBE Vector Repeater Transform').property('ADBE Vector Repeater Position').setValue([0, pitch]);
            jzAddFill(gcol, dotC);
        }
        // the matte: the lyric with a fat stroke so dots near the edges stay whole
        var T = ld2_T(ctx, text, font, size, { color: dotC, x: cx, y: cy, track: tr, lead: lead, stroke: pitch * 0.5, strokeColor: dotC, strokeOver: false });
        jzAnimate(ctx, T, { mi: 0, treat: false });
        ld2_matte(DS, T);
        bb = jzBB(T);
        return bb;
    }
});

/* ================================================================== 34 stencil \u2014 \u30B9\u30C6\u30F3\u30B7\u30EB */
jzReg('layout', 'stencil', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display'])), col: rng.pick(['accent', 'fg', 'accent']), drips: rng.int(1, 3), marks: rng.chance(0.75), dir: rng.pick([1, -1]), tilt: rng.range(-3, 3) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, s = c.seed, u = jzU(ctx), port = jzPortrait(ctx), i, k;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), text = ld2_brk(c.text, port ? 4 : 7), tr = 0.08, lead = 1.12;
        var size = Math.min(ld2_fit(ctx, text, font, W * 0.84, H * (port ? 0.5 : 0.56), tr, lead), u * 0.32), m = ld2_measAt(ctx, text, font, size, tr, lead);
        var col = jzP(ctx, 'col', 'accent') === 'accent' && jzContrast(sc.accent, sc.bg) >= 1.8 ? sc.accent : sc.fg, dir = jzP(ctx, 'dir', 1) < 0 ? -1 : 1, tilt = jzP(ctx, 'tilt', 0);
        var x0 = W / 2 - m.w / 2, x1 = W / 2 + m.w / 2, y0 = H / 2 - m.h / 2, y1 = H / 2 + m.h / 2, TH = ld2_TH(ctx);
        // glyph boxes (em layout, centre-relative): x along the line, y = line centre
        var lines = String(text).split('\r'), nL = lines.length, LD = size * lead, boxes = [];
        for (var li = 0; li < nL; li++) {
            var ch = jzChars(lines[li]), wsum = 0, j;
            for (j = 0; j < ch.length; j++) wsum += ld2_adv(ch[j]) * size + (j < ch.length - 1 ? tr * size : 0);
            var xx = -wsum / 2;
            for (j = 0; j < ch.length; j++) {
                var aw = ld2_adv(ch[j]) * size;
                if (ch[j] !== ' ' && ch[j] !== '\u3000' && !jzIsPunct(ch[j])) boxes.push({ lx: xx + aw / 2, ly: (li - (nL - 1) / 2) * LD, w: aw, h: size });
                xx += aw + tr * size;
            }
        }
        var sw = jzClamp(c.dur * 0.3, 0.35, 0.9), hs = dir > 0 ? x0 - size * 0.3 : x1 + size * 0.3, he = dir > 0 ? x1 + size * 0.3 : x0 - size * 0.3;
        var HD = TH + 'var FS=ios((time-0.05)/' + jzN(sw) + '),HEAD=' + jzN(hs) + '+' + jzN(he - hs) + '*FS;';
        // the nozzle has sprayed everything on its trailing side: a rect mask whose edge follows the head (mask expansion)
        var reveal = function (Ly, ox, big) {
            var m0 = dir > 0 ? ld2_rectShape(-big, -big, hs - ox, big) : ld2_rectShape(hs - ox, -big, big, big);
            var had = Ly.property('ADBE Mask Parade').numProperties > 0, mk = ld2_mask(Ly, m0, had ? MaskMode.INTERSECT : MaskMode.ADD);
            jzSetExpr(mk.property('ADBE Mask Offset'), HD + 'Math.max(0,' + (dir > 0 ? 'HEAD-(' + jzN(hs) + ')' : '(' + jzN(hs) + ')-HEAD') + ')');
            return mk;
        };
        if (jzP(ctx, 'marks', true)) {
            var MK = jzShapeLayer(ctx, 'registration marks', 0, 0), Lm = size * 0.18, lwm = Math.max(1, u * 0.0016), pp = size * 0.28, gm = jzGrp(MK, 'marks');
            var cs = [[x0 - pp, y0 - pp], [x1 + pp, y0 - pp], [x1 + pp, y1 + pp], [x0 - pp, y1 + pp]];
            for (i = 0; i < 4; i++) { jzAddPath(gm, [[cs[i][0] - Lm, cs[i][1]], [cs[i][0] + Lm, cs[i][1]]], false); jzAddPath(gm, [[cs[i][0], cs[i][1] - Lm], [cs[i][0], cs[i][1] + Lm]], false); jzAddEllipse(gm, Lm * 0.9, Lm * 0.9, cs[i][0], cs[i][1]); }
            jzAddStroke(gm, sc.sub, lwm);
            jzSetExpr(jzXf(MK, 'ADBE Opacity'), TH + '100*oc(time/0.35)*K');
            var ML = jzText(ctx, 'No.' + jzLineNo(ctx) + '  /  ' + jzFmtTime(c.start), { font: jzMonoF(ctx), size: jzSmallSize(ctx), color: sc.sub, x: x0 - pp + Lm * 1.4, y: y0 - pp, align: 'left', track: 0.25, name: 'stencil label' });
            jzSetExpr(jzXf(ML, 'ADBE Opacity'), TH + '100*oc(time/0.35)*K'); ld2_ng([MK, ML]);
        }
        // overspray mist round every glyph + drips (in the tilted stencil frame)
        var MS = jzShapeLayer(ctx, 'overspray', W / 2, H / 2), gms = jzGrp(MS, 'mist');
        jzXf(MS, 'ADBE Anchor Point').setValue([W / 2, H / 2]); jzXf(MS, 'ADBE Rotate Z').setValue(tilt);
        for (i = 0; i < boxes.length; i++) {
            var b = boxes[i];
            for (k = 0; k < 12; k++) {
                var an = jzR(s, i, k, 1) * Math.PI * 2, rr = 0.5 + jzR(s, i, k, 2) * 0.35, rd = size * (0.004 + jzR(s, i, k, 3) * 0.008);
                jzAddEllipse(gms, rd * 2, rd * 2, W / 2 + b.lx + Math.cos(an) * b.w * rr * 0.62, H / 2 + b.ly + Math.sin(an) * b.h * rr * 0.62);
            }
        }
        jzAddFill(gms, col);
        jzSetExpr(jzXf(MS, 'ADBE Opacity'), TH + '50*K');
        reveal(MS, 0, Math.max(W, H) * 3); jzNoGhost(MS);
        if (boxes.length) {
            var DR = jzShapeLayer(ctx, 'drips', W / 2, H / 2), w0 = size * 0.035, nd = jzP(ctx, 'drips', 2);
            jzXf(DR, 'ADBE Anchor Point').setValue([W / 2, H / 2]); jzXf(DR, 'ADBE Rotate Z').setValue(tilt);
            for (k = 0; k < nd; k++) {
                var bx = boxes[jzHash(s, k, 7) % boxes.length], dx = W / 2 + bx.lx + (jzR(s, k, 8) * 2 - 1) * bx.w * 0.3, top = H / 2 + bx.ly + bx.h * 0.35;
                var t1 = 0.3 + k * 0.25 + (dir > 0 ? (dx - x0) / Math.max(1, x1 - x0) : (x1 - dx) / Math.max(1, x1 - x0)) * sw, Lmax = size * (0.25 + 0.3 * jzR(s, k, 9));
                var DL = TH + 'var DLn=' + jzN(Lmax) + '*oc((time-' + jzN(t1) + ')/1.4);';
                // bead, then run: each sub-group is finished before the next is added (AE invalidates older sibling refs)
                var gd = jzGrp(DR, 'drip ' + (k + 1)), gdb = ld2_sub(gd, 'bead');
                jzAddEllipse(gdb, w0 * 1.2, w0 * 1.2); jzAddFill(gdb, col);
                ld2_gX(gdb, 'ADBE Vector Position', DL + '[0,DLn]');
                var gdr = ld2_sub(gd, 'run');
                jzAddPath(gdr, [[-w0 / 2, 0], [w0 / 2, 0], [w0 * 0.35, 100], [-w0 * 0.35, 100]], true); jzAddFill(gdr, col);
                ld2_gX(gdr, 'ADBE Vector Scale', DL + '[100,DLn]');
                jzGX(gd).property('ADBE Vector Position').setValue([dx, top]);
                ld2_gX(gd, 'ADBE Vector Group Opacity', DL + 'DLn>1?100:0');
            }
            jzSetExpr(jzXf(DR, 'ADBE Opacity'), TH + '100*K'); jzNoGhost(DR);
        }
        // the lyric, cut by the stencil bridges (subtract masks) and revealed behind the nozzle
        var L = ld2_T(ctx, text, font, size, { color: col, x: W / 2, y: H / 2, track: tr, lead: lead });
        jzXf(L, 'ADBE Anchor Point').setValue([0, (nL - 1) / 2 * LD - LD2_CY * size]);
        jzXf(L, 'ADBE Position').setValue([W / 2, H / 2]);
        jzXf(L, 'ADBE Rotate Z').setValue(tilt);
        var bb = ld2_box(x0, y0, x1, y1);
        jzAnimate(ctx, L, { mi: 0, treat: false });
        var ax = 0, ay = (nL - 1) / 2 * LD - LD2_CY * size;
        reveal(L, W / 2 - ax, Math.max(W, H) * 3);
        var bw = size * 0.05;
        for (i = 0; i < boxes.length; i++) {
            var q = boxes[i], vx = q.lx + ((i % 3) - 1) * q.w * 0.06, gx = ax + 0, gy = ay;
            ld2_mask(L, ld2_rectShape(gx + vx - bw / 2, gy + q.ly - q.h * 0.62, gx + vx + bw / 2, gy + q.ly + q.h * 0.62), MaskMode.SUBTRACT);
            if (i % 2) {
                var hy = gy + q.ly - q.h * 0.06 - bw / 2;
                ld2_mask(L, ld2_rectShape(gx + q.lx - q.w * 0.62, hy, gx + vx - bw / 2, hy + bw), MaskMode.SUBTRACT);
                ld2_mask(L, ld2_rectShape(gx + vx + bw / 2, hy, gx + q.lx + q.w * 0.62, hy + bw), MaskMode.SUBTRACT);
            }
        }
        // spray cloud at the nozzle while it moves (3 drawings cycling)
        var CLd = jzShapeLayer(ctx, 'nozzle spray', 0, 0);
        for (var v = 0; v < 3; v++) {
            var gv = jzGrp(CLd, 'puff ' + (v + 1));
            for (k = 0; k < 24; k++) { var r2 = size * (0.01 + jzR(s, k, 13) * 0.02); jzAddEllipse(gv, r2 * 2, r2 * 2, (jzR(s, k, v, 12) * 2 - 1) * size * 0.25, y0 + (y1 - y0) * jzR(s, k, v, 11)); }
            jzAddFill(gv, col);
            ld2_gX(gv, 'ADBE Vector Group Opacity', 'Math.floor(time*24)%3==' + v + '?100:0');
        }
        jzSetExpr(jzXf(CLd, 'ADBE Position'), HD + '[HEAD,0]');
        jzSetExpr(jzXf(CLd, 'ADBE Opacity'), HD + '(FS>0&&FS<1)?35*K:0'); jzNoGhost(CLd);
        return bb;
    }
});

// ================================================================ cut-to-cut transitions (AE port of the trans entries in src/11p_treattrans.js)
// trans.build(t): t.comp = MAIN comp, t.A = previous cut's wrapper layer (already extended to t0+dur, holds its last state),
// t.B = this cut's wrapper layer (above A, starts at t0). Drive effects / transforms on B (and A) with expressions built on
// jzEvHead(t.t0, t.dur) \u2192 p 0..1 (linear; ease it yourself). At p = 0 the screen must look like A, at p = 1 like B.
// Extra graphics (edge bars etc.) go on the main comp with jzEvShape(t.comp, name, t.t0, t.dur) \u2014 it is created on top.

/* ---- wipe \u2014 \u30A8\u30C3\u30B8\u30EF\u30A4\u30D7: straight wipe with a bright leading edge */
jzReg('trans', 'wipe', {
    plan: function (rng, st) { return { dir: rng.wpick([['L', 3], ['R', 2], ['U', 1.4], ['D', 0.8]]) }; },
    build: function (t) {
        var W = t.W, H = t.H, d = t.P.dir || 'L', HD = jzEvHead(t.t0, t.dur) + 'function ioc(x){x=cl(x);return x<0.5?4*x*x*x:1-Math.pow(-2*x+2,3)/2;}' + jzBellExpr() + 'var e=ioc(p);';
        // B is revealed from the side the edge travels from (Linear Wipe hides the region swept by "completion")
        var ang = { L: 90, R: 270, U: 180, D: 0 }[d];   // L: B appears from the right edge moving left \u2026
        var lw = jzEffect(t.B, 'ADBE Linear Wipe', 'JZ Trans Wipe'); jzEP(lw, 2, ang); jzEP(lw, 3, 0);
        jzEX(lw, 1, HD + '100*(1-e)');
        // leading edge bar + hairline, fading in and out with bell(p)
        // (each group is finished before the next one is added \u2014 adding a group invalidates references to its siblings in AE)
        var lwid = Math.max(2, Math.min(W, H) * 0.007), S = jzEvShape(t.comp, 'JZ Trans edge', t.t0, t.dur);
        var horiz = d === 'L' || d === 'R', side = (d === 'R' || d === 'D') ? -1 : 1;
        var g = jzGrp(S, 'edge');
        if (horiz) jzAddRect(g, lwid, H, 0, 0, H / 2); else jzAddRect(g, W, lwid, 0, W / 2, 0);
        jzAddFill(g, jzTAcc(t));
        var g2 = jzGrp(S, 'hair');
        if (horiz) jzAddRect(g2, Math.max(1, lwid * 0.35), H, 0, lwid * 3.2 * side, H / 2); else jzAddRect(g2, W, Math.max(1, lwid * 0.35), 0, W / 2, lwid * 3.2 * side);
        jzAddFill(g2, jzTAcc(t)); jzGX(g2).property('ADBE Vector Group Opacity').setValue(50);
        var pos = { L: '[' + W + '*(1-e),0]', R: '[' + W + '*e,0]', U: '[0,' + H + '*(1-e)]', D: '[0,' + H + '*e]' }[d];
        jzSetExpr(jzXf(S, 'ADBE Position'), HD + pos);
        jzSetExpr(jzXf(S, 'ADBE Opacity'), HD + '100*Math.pow(bell(p),0.6)');
    }
});

// ================================================================ helpers for the entries below (tag tn_)
// Layer strategy: effects on the wrapper layers stay identity outside their window (so they stack when one wrapper is B of one
// transition and A of the next); anything that must sit ABOVE the new cut is a copy of a wrapper that lives only for the transition.
// tAcc(I, second): an accent that reads against both the new and the previous background
function tn_acc(t, second) {
    var sc = t.sc, pb = (t.scPrev || sc).bg;
    var list = second ? [sc.accent2, sc.accent, sc.fg] : [sc.accent, sc.accent2, sc.fg];
    for (var i = 0; i < list.length; i++) if (list[i] && jzContrast(list[i], sc.bg) >= 1.8 && jzContrast(list[i], pb) >= 1.4) return list[i];
    return sc.fg;
}
function tn_md(t) { return Math.min(t.W, t.H); }                  // minD
function tn_lw(t, k) { return Math.max(2, tn_md(t) * k); }        // lwOf
// expression header: T0, TD, p + the browser's easings (ioc inOutCubic, ioq inOutQuart, ios inOutSine, sm smoothstep, bell)
function tn_hd(t) {
    return jzEvHead(t.t0, t.dur) + jzBellExpr() +
        'function ioc(x){x=cl(x);return x<0.5?4*x*x*x:1-Math.pow(-2*x+2,3)/2;}' +
        'function ioq(x){x=cl(x);return x<0.5?8*x*x*x*x:1-8*Math.pow(1-x,4);}' +
        'function ios(x){x=cl(x);return -(Math.cos(Math.PI*x)-1)/2;}' +
        'function sm(a,b,x){x=cl((x-a)/(b-a));return x*x*(3-2*x);}\n';
}
// compact header for expressions repeated on many layers / groups
function tn_hs(t) { return 'var T0=' + jzN(t.t0) + ',TD=' + jzN(Math.max(1 / 240, t.dur)) + ';function cl(x){return Math.max(0,Math.min(1,x));}var p=cl((time-T0)/TD);'; }
var TN_IOC = 'function ioc(x){x=cl(x);return x<0.5?4*x*x*x:1-Math.pow(-2*x+2,3)/2;}';
// Transform effect driven by the transition. o: anc [x,y] pivot (layer px) \u00B7 pos [dxExpr, dyExpr] \u00B7 sc factor \u00B7 sw / sh factors
// (non-uniform) \u00B7 rot degrees \u00B7 op 0..1 \u2014 all expressions relative to the effect's own values
function tn_xf(L, name, HD, o) {
    var e = jzEffect(L, 'ADBE Geometry2', name);
    if (!e) return null;
    if (o.anc) { jzEP(e, 1, o.anc); jzEP(e, 2, o.anc); }
    if (o.pos) jzEX(e, 2, HD + '[value[0]+(' + o.pos[0] + '),value[1]+(' + o.pos[1] + ')]');
    if (o.sw || o.sh) { jzEP(e, 3, 0); jzEX(e, 4, HD + 'value*(' + (o.sh || '1') + ')'); jzEX(e, 5, HD + 'value*(' + (o.sw || '1') + ')'); }
    else if (o.sc) jzEX(e, 4, HD + 'value*(' + o.sc + ')');
    if (o.rot) jzEX(e, 8, HD + 'value+(' + o.rot + ')');
    if (o.op) jzEX(e, 9, HD + 'value*cl(' + o.op + ')');
    return e;
}
// a copy of wrapper layer L living only during the transition, directly above B (each later copy lands below the earlier ones)
function tn_copy(t, L, name) {
    var C = L.duplicate();
    C.name = name;
    C.moveBefore(t.B);
    C.inPoint = t.t0; C.outPoint = t.t0 + t.dur;
    return C;
}
function tn_solid(t, hex, name, opEx) {
    var S = jzEvSolid(t.comp, name, hex, t.t0, t.dur);
    if (opEx) jzSetExpr(jzXf(S, 'ADBE Opacity'), opEx);
    return S;
}
// rectangle mask in layer px (intersects with masks the layer may already carry)
function tn_mask(L, x0, y0, x1, y1) {
    var mp = L.property('ADBE Mask Parade'), first = mp.numProperties === 0;
    var m = mp.addProperty('ADBE Mask Atom'), sh = new Shape();
    sh.vertices = [[x1, y0], [x1, y1], [x0, y1], [x0, y0]]; sh.closed = true;
    m.property('ADBE Mask Shape').setValue(sh);
    if (!first) { try { m.maskMode = MaskMode.INTERSECT; } catch (e) {} }
    return m;
}
// M becomes the track matte of L (placed directly above it, switched off)
function tn_matte(M, L, type) { M.moveBefore(L); L.trackMatteType = type; M.enabled = false; }
function tn_dirv(d) { return { h: d === 'L' || d === 'R', sg: (d === 'L' || d === 'U') ? -1 : 1 }; }
// soft shadow strip (black rect blurred along one axis), for cover / uncover. Returns the layer; its origin = the edge.
function tn_shadow(t, name, h, into, sw) {
    var S = jzEvShape(t.comp, name, t.t0, t.dur), g = jzGrp(S, 'shadow');
    if (h) jzAddRect(g, sw, t.H * 1.2, 0, into * sw / 2, t.H / 2); else jzAddRect(g, t.W * 1.2, sw, 0, t.W / 2, into * sw / 2);
    jzAddFill(g, '#000000');
    var b = jzEffect(S, 'ADBE Gaussian Blur 2', 'JZ Trans soft'); jzEP(b, 1, sw); jzEP(b, 2, h ? 2 : 3); jzEP(b, 3, 0);
    return S;
}

/* ---- diagonalWipe \u2014 \u659C\u3081\u5E2F\u30EF\u30A4\u30D7: slanted wipe, an accent band runs ahead of the new cut */
jzReg('trans', 'diagonalWipe', {
    plan: function (rng, st) { return { k: rng.range(0.3, 0.55) * (rng.chance(0.5) ? 1 : -1), rev: rng.chance(0.4) }; },
    build: function (t) {
        var W = t.W, H = t.H, k = t.P.k == null ? 0.4 : t.P.k, rev = !!t.P.rev, HD = tn_hd(t) + 'var e=ioc(p);';
        var sl = k * H, n = Math.sqrt(H * H + sl * sl);
        var dx = (rev ? H : -H) / n, dy = sl / n;                 // unit normal of the slanted edge, pointing into the B side
        var ang = Math.atan2(dx, -dy) * 180 / Math.PI; if (ang < 0) ang += 360;
        var lw = jzEffect(t.B, 'ADBE Linear Wipe', 'JZ Trans diagonalWipe'); jzEP(lw, 2, ang); jzEP(lw, 3, 0);
        jzEX(lw, 1, HD + '100*(1-e)');
        // accent band + thin second band on the A side of the edge (width follows bell(p))
        var ext = Math.abs(dx) * W / 2 + Math.abs(dy) * H / 2, band = tn_md(t) * 0.07 * H / n, LEN = (W + H) * 2;
        // (each rect is configured before its fill / the next group is added \u2014 those adds invalidate held references in AE)
        var S = jzEvShape(t.comp, 'JZ Trans diagonal band', t.t0, t.dur), BW = HD + 'var bw=' + jzN(band) + '*bell(p);';
        var g2 = jzGrp(S, 'band 2'), r2 = jzAddRect(g2, 10, LEN, 0, 0, 0);
        jzSetExpr(r2.property('ADBE Vector Rect Size'), BW + '[bw*0.25,' + jzN(LEN) + ']');
        jzSetExpr(r2.property('ADBE Vector Rect Position'), BW + '[bw*1.475,0]');
        jzAddFill(g2, tn_acc(t, true), 85);
        var g1 = jzGrp(S, 'band'), r1 = jzAddRect(g1, 10, LEN, 0, 0, 0);
        jzSetExpr(r1.property('ADBE Vector Rect Size'), BW + '[bw,' + jzN(LEN) + ']');
        jzSetExpr(r1.property('ADBE Vector Rect Position'), BW + '[bw/2,0]');
        jzAddFill(g1, tn_acc(t));
        // the layer origin rides on the wipe edge (same sweep as Linear Wipe), its +x axis points into the A side
        jzSetExpr(jzXf(S, 'ADBE Position'), HD + 'var s=' + jzN(ext) + '*(1-2*e);[' + jzN(W / 2) + '+(' + jzN(dx) + ')*s,' + jzN(H / 2) + '+(' + jzN(dy) + ')*s]');
        jzXf(S, 'ADBE Rotate Z').setValue(Math.atan2(-dy, -dx) * 180 / Math.PI);
    }
});

/* ---- clockWipe \u2014 \u30AF\u30ED\u30C3\u30AF\u30EF\u30A4\u30D7: radial sweep with a clock hand */
jzReg('trans', 'clockWipe', {
    plan: function (rng, st) { return { dir: rng.chance(0.7) ? 1 : -1, a0: rng.pick([-90, -90, 0, 180]) }; },
    build: function (t) {
        var W = t.W, H = t.H, dir = t.P.dir < 0 ? -1 : 1, a0 = t.P.a0 == null ? -90 : t.P.a0, HD = tn_hd(t) + 'var e=ioc(p);';
        var R = Math.sqrt(W * W + H * H) / 2 + 4, lw = tn_lw(t, 0.006), ac = tn_acc(t);
        // Radial Wipe hides the sector that B has NOT reached yet (AE angle 0 = 12 o'clock = canvas -90\u00B0)
        var rw = jzEffect(t.B, 'ADBE Radial Wipe', 'JZ Trans clockWipe');
        jzEX(rw, 1, HD + '100*(1-e)');
        if (dir > 0) jzEX(rw, 2, HD + jzN(a0 + 90) + '+360*e'); else jzEP(rw, 2, a0 + 90);
        jzEP(rw, 3, [W / 2, H / 2]); jzEP(rw, 4, 1); jzEP(rw, 5, 0);
        var S = jzEvShape(t.comp, 'JZ Trans clock hand', t.t0, t.dur);
        var g2 = jzGrp(S, 'hub'); jzAddEllipse(g2, lw * 4.4, lw * 4.4, 0, 0); jzAddFill(g2, ac);    // (finished before the next group is added)
        var g = jzGrp(S, 'hand');
        jzAddPath(g, [[0, 0], [R, 0]], false);
        var sk = jzAddStroke(g, ac, lw); sk.property('ADBE Vector Stroke Line Cap').setValue(2);
        jzXf(S, 'ADBE Position').setValue([W / 2, H / 2]);
        jzSetExpr(jzXf(S, 'ADBE Rotate Z'), HD + jzN(a0) + '+' + jzN(360 * dir) + '*e');
        jzSetExpr(jzXf(S, 'ADBE Opacity'), HD + '100*Math.pow(bell(p),0.5)');
    }
});

/* ---- irisOpen \u2014 \u30A2\u30A4\u30EA\u30B9\u30A4\u30F3: a circle opens on the new cut, rimmed by two accent rings */
jzReg('trans', 'irisOpen', {
    plan: function (rng, st) { return { x: 0.5 + rng.range(-0.12, 0.12), y: 0.5 + rng.range(-0.1, 0.1) }; },
    build: function (t) {
        var W = t.W, H = t.H, cx = W * (t.P.x == null ? 0.5 : t.P.x), cy = H * (t.P.y == null ? 0.5 : t.P.y);
        var R = Math.sqrt(Math.pow(Math.max(cx, W - cx), 2) + Math.pow(Math.max(cy, H - cy), 2)) + 4, lw = tn_lw(t, 0.009);
        var HD = tn_hd(t) + 'var r=' + jzN(R) + '*ioc(p);';
        // the old cut stays on top (copy) and the iris (32-gon) cuts a growing hole into it
        var C = tn_copy(t, t.A, 'JZ Trans iris (prev cut)');
        var iw = jzEffect(C, 'ADBE Iris Wipe', 'JZ Trans irisOpen');
        jzEP(iw, 1, [cx, cy]); jzEP(iw, 2, 32); jzEP(iw, 4, 0); jzEP(iw, 7, 0);
        jzEX(iw, 3, HD + 'r/0.995');
        // (each ellipse is configured before its stroke / the next group is added)
        var S = jzEvShape(t.comp, 'JZ Trans iris rings', t.t0, t.dur);
        var g1 = jzGrp(S, 'ring'), e1 = jzAddEllipse(g1, 10, 10, 0, 0);
        jzSetExpr(e1.property('ADBE Vector Ellipse Size'), HD + 'var d=2*r+' + jzN(lw) + ';[d,d]');
        jzAddStroke(g1, tn_acc(t), lw);
        var g2 = jzGrp(S, 'ring 2'), e2 = jzAddEllipse(g2, 10, 10, 0, 0);
        jzSetExpr(e2.property('ADBE Vector Ellipse Size'), HD + 'var d=2*(r*1.06+' + jzN(lw * 2.5) + ');[d,d]');
        jzAddStroke(g2, tn_acc(t, true), lw * 0.4, 60);
        jzXf(S, 'ADBE Position').setValue([cx, cy]);
        jzSetExpr(jzXf(S, 'ADBE Opacity'), HD + '100*Math.pow(bell(p),0.6)');
    }
});

/* ---- pushSlide \u2014 \u30D7\u30C3\u30B7\u30E5: the new cut shoves the old one out */
jzReg('trans', 'pushSlide', {
    plan: function (rng, st) { return { dir: rng.wpick([['L', 3], ['R', 1.6], ['U', 1.4], ['D', 0.6]]) }; },
    build: function (t) {
        var W = t.W, H = t.H, D = tn_dirv(t.P.dir || 'L'), Lx = D.h ? W : H, sg = D.sg;
        var HD = tn_hd(t) + 'var off=Math.round(ioc(p)*' + jzN(Lx) + ')*' + sg + ';';
        var bo = 'off-(' + jzN(sg * Lx) + ')';
        tn_xf(t.A, 'JZ Trans out pushSlide', HD, { pos: D.h ? ['off', '0'] : ['0', 'off'] });
        tn_xf(t.B, 'JZ Trans in pushSlide', HD, { pos: D.h ? [bo, '0'] : ['0', bo] });
        // accent line on the seam
        var lw = tn_lw(t, 0.005), S = jzEvShape(t.comp, 'JZ Trans push seam', t.t0, t.dur), g = jzGrp(S, 'seam');
        if (D.h) jzAddRect(g, lw, H, 0, 0, H / 2); else jzAddRect(g, W, lw, 0, W / 2, 0);
        jzAddFill(g, tn_acc(t));
        var q = sg < 0 ? jzN(Lx) + '+off' : 'off';
        jzSetExpr(jzXf(S, 'ADBE Position'), HD + (D.h ? '[' + q + ',0]' : '[0,' + q + ']'));
        jzSetExpr(jzXf(S, 'ADBE Opacity'), HD + '100*Math.pow(bell(p),0.6)');
    }
});

/* ---- cover \u2014 \u30AB\u30D0\u30FC: the new cut slides in over the old one, which dims and drifts back */
jzReg('trans', 'cover', {
    plan: function (rng, st) { return { dir: rng.wpick([['L', 2], ['R', 2], ['U', 1.5], ['D', 1]]) }; },
    build: function (t) {
        var W = t.W, H = t.H, D = tn_dirv(t.P.dir || 'L'), Lx = D.h ? W : H, sg = D.sg, sw = tn_md(t) * 0.06;
        var HD = tn_hd(t) + 'var e=ioc(p),bq=Math.round((1-e)*' + jzN(Lx) + ')*' + (-sg) + ',ao=Math.round(e*' + jzN(Lx * 0.18) + ')*' + sg + ';';
        tn_xf(t.A, 'JZ Trans out cover', HD, { pos: D.h ? ['ao', '0'] : ['0', 'ao'] });
        tn_xf(t.B, 'JZ Trans in cover', HD, { pos: D.h ? ['bq', '0'] : ['0', 'bq'] });
        // under B: soft shadow ahead of its incoming edge, and the dimming of A
        var Sh = tn_shadow(t, 'JZ Trans cover shadow', D.h, -sg, sw);
        var edge = sg > 0 ? jzN(Lx) + '+bq' : 'bq';
        jzSetExpr(jzXf(Sh, 'ADBE Position'), HD + (D.h ? '[' + edge + ',0]' : '[0,' + edge + ']'));
        jzSetExpr(jzXf(Sh, 'ADBE Opacity'), HD + '90*bell(p)');
        var Dk = tn_solid(t, '#000000', 'JZ Trans cover dim', HD + '45*e');
        Dk.moveAfter(t.B); Sh.moveAfter(t.B);
    }
});

/* ---- uncover \u2014 \u30A2\u30F3\u30AB\u30D0\u30FC: the old cut slides away and uncovers the new one waiting underneath */
jzReg('trans', 'uncover', {
    plan: function (rng, st) { return { dir: rng.wpick([['L', 2], ['R', 2], ['U', 1.6], ['D', 1]]) }; },
    build: function (t) {
        var W = t.W, H = t.H, D = tn_dirv(t.P.dir || 'L'), Lx = D.h ? W : H, sg = D.sg, sw = tn_md(t) * 0.07;
        var HD = tn_hd(t) + 'var e=ioc(p),ao=Math.round(e*' + jzN(Lx) + ')*' + sg + ';';
        var C = tn_copy(t, t.A, 'JZ Trans uncover (prev cut)');
        tn_xf(C, 'JZ Trans uncover slide', HD, { pos: D.h ? ['ao', '0'] : ['0', 'ao'] });
        tn_xf(t.B, 'JZ Trans in uncover', HD, { sc: '1.05-0.05*oc(p)' });
        // between B and the sliding copy: dimming of B + soft shadow behind the trailing edge
        var Sh = tn_shadow(t, 'JZ Trans uncover shadow', D.h, sg, sw);
        var edge = sg > 0 ? 'ao' : jzN(Lx) + '+ao';
        jzSetExpr(jzXf(Sh, 'ADBE Position'), HD + (D.h ? '[' + edge + ',0]' : '[0,' + edge + ']'));
        jzSetExpr(jzXf(Sh, 'ADBE Opacity'), HD + '100*bell(p)');
        var Dk = tn_solid(t, '#000000', 'JZ Trans uncover dim', HD + '40*(1-e)');
        Dk.moveAfter(C); Sh.moveAfter(C);
    }
});

/* ---- zoomThrough \u2014 \u30BA\u30FC\u30E0\u30B9\u30EB\u30FC: the old cut rushes past the camera while the new one settles in */
jzReg('trans', 'zoomThrough', {
    plan: function (rng, st) { return { z: rng.range(1.5, 2.2) }; },
    build: function (t) {
        var z = t.P.z || 1.8, HD = tn_hd(t) + 'var sa=1+' + jzN(z - 1) + '*Math.pow(p,1.4),aa=1-ic(p*1.12);';
        var E = tn_copy(t, t.A, 'JZ Trans zoom echo');                 // (created first \u2192 ends up above the main copy)
        tn_xf(E, 'JZ Trans zoom echo', HD, { sc: 'sa*(1+0.08*p)', op: '(p>0.08&&aa>0.15)?aa*0.35:0' });
        var C = tn_copy(t, t.A, 'JZ Trans zoom (prev cut)');
        tn_xf(C, 'JZ Trans zoom out', HD, { sc: 'sa', op: 'aa' });
        tn_xf(t.B, 'JZ Trans in zoomThrough', HD, { sc: '0.86+0.14*oc(p)' });
        var bg = tn_solid(t, t.sc.bg, 'JZ Trans zoom bg'); bg.moveAfter(t.B);
    }
});

/* ---- doorsOpen \u2014 \u89B3\u97F3\u958B\u304D: the old cut splits down the middle and swings open */
jzReg('trans', 'doorsOpen', {
    plan: function (rng, st) { return { vert: rng.chance(0.3) }; },
    build: function (t) {
        var W = t.W, H = t.H, v = !!t.P.vert, lw = tn_lw(t, 0.005), ac = tn_acc(t), Lx = v ? H : W, hw = Math.floor(Lx / 2);
        var HD = tn_hd(t) + 'var e=ioc(p),off=Math.round(e*' + jzN(Lx - hw + lw * 2) + ');';
        var D1 = tn_copy(t, t.A, 'JZ Trans door 1'), D2 = tn_copy(t, t.A, 'JZ Trans door 2');
        if (!v) { tn_mask(D1, 0, 0, hw, H); tn_mask(D2, hw, 0, W, H); } else { tn_mask(D1, 0, 0, W, hw); tn_mask(D2, 0, hw, W, H); }
        tn_xf(D1, 'JZ Trans door', HD, { pos: v ? ['0', '-off'] : ['-off', '0'] });
        tn_xf(D2, 'JZ Trans door', HD, { pos: v ? ['0', 'off'] : ['off', '0'] });
        // behind the doors: the new cut settles from 93 % over its background, dimmed at first
        var Dk = tn_solid(t, '#000000', 'JZ Trans doors dim', HD + '35*(1-e)'); Dk.moveBefore(t.B);
        tn_xf(t.B, 'JZ Trans in doorsOpen', HD, { sc: '0.93+0.07*oc(p)' });
        var bg = tn_solid(t, t.sc.bg, 'JZ Trans doors bg'); bg.moveAfter(t.B);
        // accent bars on the inner edges
        // (group 1 is finished before group 2 is added)
        var S = jzEvShape(t.comp, 'JZ Trans door edges', t.t0, t.dur);
        var g1 = jzGrp(S, 'edge 1');
        if (!v) jzAddRect(g1, lw, H, 0, hw - lw / 2, H / 2); else jzAddRect(g1, W, lw, 0, W / 2, hw - lw / 2);
        jzAddFill(g1, ac);
        jzSetExpr(jzGX(g1).property('ADBE Vector Position'), HD + (v ? '[0,-off]' : '[-off,0]'));
        var g2 = jzGrp(S, 'edge 2');
        if (!v) jzAddRect(g2, lw, H, 0, hw + lw / 2, H / 2); else jzAddRect(g2, W, lw, 0, W / 2, hw + lw / 2);
        jzAddFill(g2, ac);
        jzSetExpr(jzGX(g2).property('ADBE Vector Position'), HD + (v ? '[0,off]' : '[off,0]'));
        jzSetExpr(jzXf(S, 'ADBE Opacity'), HD + '100*Math.pow(bell(p),0.5)');
    }
});

/* ---- blinds \u2014 \u30D6\u30E9\u30A4\u30F3\u30C9\u8EE2\u63DB: slats flip over one after another */
jzReg('trans', 'blinds', {
    plan: function (rng, st) { return { n: rng.int(7, 12), vert: rng.chance(0.35), rev: rng.chance(0.4) }; },
    build: function (t) {
        var W = t.W, H = t.H, n = Math.max(2, Math.round(t.P.n || 9)), v = !!t.P.vert, rev = !!t.P.rev, Lx = v ? W : H;
        var lw = Math.max(1, tn_md(t) * 0.003), ac = tn_acc(t), HS = tn_hs(t) + TN_IOC;
        // the old cut stays on top (copy); a matte of growing slats cuts it away
        var C = tn_copy(t, t.A, 'JZ Trans blinds (prev cut)');
        var M = jzEvShape(t.comp, 'JZ Trans blinds matte', t.t0, t.dur), Ln = jzEvShape(t.comp, 'JZ Trans blinds lines', t.t0, t.dur);
        for (var i = 0; i < n; i++) {
            var k = rev ? n - 1 - i : i, a0 = Math.round(i * Lx / n), a1 = Math.round((i + 1) * Lx / n);
            var Q = HS + 'var q=ioc(p*1.55-' + jzN(k / n * 0.55) + ');';
            var g = jzGrp(M, 'slat ' + (i + 1));
            if (v) jzAddRect(g, a1 - a0 + 1, H, 0, a0 + (a1 - a0 + 1) / 2, H / 2); else jzAddRect(g, W, a1 - a0 + 1, 0, W / 2, a0 + (a1 - a0 + 1) / 2);
            jzAddFill(g, '#FFFFFF');
            var gx = jzGX(g);
            gx.property('ADBE Vector Anchor').setValue(v ? [a0, 0] : [0, a0]); gx.property('ADBE Vector Position').setValue(v ? [a0, 0] : [0, a0]);
            jzSetExpr(gx.property('ADBE Vector Scale'), Q + (v ? '[100*q,100]' : '[100,100*q]'));
            var gl = jzGrp(Ln, 'line ' + (i + 1));
            if (v) jzAddRect(gl, lw, H, 0, a0 + lw / 2, H / 2); else jzAddRect(gl, W, lw, 0, W / 2, a0 + lw / 2);
            jzAddFill(gl, ac);
            jzSetExpr(jzGX(gl).property('ADBE Vector Position'), Q + 'var d=' + jzN(a1 - a0) + '*q;' + (v ? '[d,0]' : '[0,d]'));
            jzSetExpr(jzGX(gl).property('ADBE Vector Group Opacity'), Q + '(q>0&&q<1)?100*(1-q):0');
        }
        tn_matte(M, C, TrackMatteType.ALPHA_INVERTED);
    }
});

/* ---- checker \u2014 \u5E02\u677E\u8EE2\u63DB: squares open in two chequered waves */
jzReg('trans', 'checker', {
    plan: function (rng, st) { return { n: rng.int(4, 6), rev: rng.chance(0.5) }; },
    build: function (t) {
        var W = t.W, H = t.H, n = Math.max(2, Math.round(t.P.n || 5)), rev = !!t.P.rev, cell = tn_md(t) / n;
        var cols = Math.ceil(W / cell - 1e-6), rows = Math.ceil(H / cell - 1e-6), HS = tn_hs(t) + 'function oc(x){x=cl(x);return 1-Math.pow(1-x,3);}';
        var C = tn_copy(t, t.A, 'JZ Trans checker (prev cut)');
        var M = jzEvShape(t.comp, 'JZ Trans checker matte', t.t0, t.dur);
        for (var j = 0; j < rows; j++) for (var i = 0; i < cols; i++) {
            var d = ((i + j) % 2) * 0.32 + 0.18 * ((rev ? cols - 1 - i : i) + j) / Math.max(1, cols + rows - 2);
            var x0 = Math.round(i * W / cols), x1 = Math.round((i + 1) * W / cols), y0 = Math.round(j * H / rows), y1 = Math.round((j + 1) * H / rows);
            var mx = (x0 + x1) / 2, my = (y0 + y1) / 2, g = jzGrp(M, 'cell ' + (i + 1) + '/' + (j + 1));
            jzAddRect(g, x1 - x0 + 1, y1 - y0 + 1, 0, mx, my); jzAddFill(g, '#FFFFFF');
            var gx = jzGX(g); gx.property('ADBE Vector Anchor').setValue([mx, my]); gx.property('ADBE Vector Position').setValue([mx, my]);
            jzSetExpr(gx.property('ADBE Vector Scale'), HS + 'var q=oc((p-' + jzN(d) + ')/0.5);if(q>0.97)q=1;[100*q,100*q]');
        }
        tn_matte(M, C, TrackMatteType.ALPHA_INVERTED);
    }
});

/* ---- blockDissolve \u2014 \u30D6\u30ED\u30C3\u30AF\u5D29\u3057: random blocks flip to the new cut, each with a short accent flash */
jzReg('trans', 'blockDissolve', {
    plan: function (rng, st) { return { n: rng.int(7, 11), side: rng.pick([0, 0, 1, 2]) }; },
    build: function (t) {
        var W = t.W, H = t.H, n = Math.max(2, Math.round(t.P.n || 9)), side = t.P.side || 0, cell = tn_md(t) / n;
        var cols = Math.ceil(W / cell - 1e-6), rows = Math.ceil(H / cell - 1e-6), bw = W / cols, bh = H / rows;
        var HD = tn_hd(t) + (side ? 'function fr(x,b){return cl(((x-0.02)/0.84-0.45*b)/0.55);}' : 'function fr(x,b){return cl((x-0.02)/0.84);}');
        // Block Dissolve has no seed: the copy of the old cut and the accent flash layer share name, size and blocks, so the flash
        // (dissolving 0.1 later, under the copy) shows exactly on the blocks that flipped last.
        // side 1 / 2 (the browser biases the flip time towards the left / top): bands of block columns / rows, each with its own
        // completion = the share of its blocks flipped by then
        var NM = 'JZ Trans blocks', N = side === 1 ? cols : side === 2 ? rows : 1, nb = side ? Math.min(5, N) : 1;
        for (var k = 0; k < nb; k++) {
            var c0 = Math.floor(k * N / nb), c1 = Math.floor((k + 1) * N / nb), bias = side ? ((c0 + c1 - 1) / 2) / Math.max(1, N - 1) : 0;
            // flash fades in two steps (blocks younger than 0.04: ~67 %, up to 0.1: 40 %) like the browser's 0.75 \u2192 0 fade
            var C = tn_copy(t, t.A, NM), F1 = tn_solid(t, tn_acc(t), NM), F2 = tn_solid(t, tn_acc(t), NM);
            F2.moveAfter(C); F1.moveAfter(C); jzXf(F1, 'ADBE Opacity').setValue(45); jzXf(F2, 'ADBE Opacity').setValue(40);
            var Ls = [[C, 'p'], [F1, 'p-0.04'], [F2, 'p-0.1']];
            for (var i = 0; i < Ls.length; i++) {
                var L = Ls[i][0], o0 = k ? 1 : 0, o1 = k < nb - 1 ? 1 : 0;   // neighbouring bands overlap by 2 px (no seams)
                if (side === 1) tn_mask(L, Math.round(c0 * bw) - o0, 0, Math.round(c1 * bw) + o1, H);
                else if (side === 2) tn_mask(L, 0, Math.round(c0 * bh) - o0, W, Math.round(c1 * bh) + o1);
                var bd = jzEffect(L, 'ADBE Block Dissolve', 'JZ Trans blockDissolve');
                jzEP(bd, 2, bw); jzEP(bd, 3, bh); jzEP(bd, 4, 0); jzEP(bd, 5, 0);
                jzEX(bd, 1, HD + '100*fr(' + Ls[i][1] + ',' + jzN(bias) + ')');
            }
        }
    }
});

/* ---- whipPan \u2014 \u30DB\u30A4\u30C3\u30D7\u30D1\u30F3: both frames rush sideways, smeared by motion blur */
jzReg('trans', 'whipPan', {
    plan: function (rng, st) { return { dir: rng.chance(0.65) ? -1 : 1, vert: rng.chance(0.2) }; },
    build: function (t) {
        var W = t.W, H = t.H, dir = t.P.dir > 0 ? 1 : -1, v = !!t.P.vert, Lx = v ? H : W;
        var HD = tn_hd(t) + 'var off=Math.round(ioq(p)*' + jzN(Lx) + ')*' + dir + ';';
        var bo = 'off-(' + jzN(Lx * dir) + ')';
        tn_xf(t.A, 'JZ Trans out whipPan', HD, { pos: v ? ['0', 'off'] : ['off', '0'] });
        tn_xf(t.B, 'JZ Trans in whipPan', HD, { pos: v ? ['0', bo] : [bo, '0'] });
        var Aj = jzAdjLayer(t.comp, 'JZ Trans whip blur', t.t0, t.dur); Aj.moveBefore(t.B);
        var mb = jzEffect(Aj, 'ADBE Motion Blur', 'JZ Trans whip smear'); jzEP(mb, 1, v ? 0 : 90);
        jzEX(mb, 2, HD + jzN(Lx * 0.13) + '*Math.pow(bell(p),2)');
    }
});

/* ---- spinOut \u2014 \u56DE\u8EE2\u30A2\u30A6\u30C8: the old cut spins away into the distance, revealing the new one */
jzReg('trans', 'spinOut', {
    plan: function (rng, st) { return { rot: rng.range(100, 200) * (rng.chance(0.5) ? 1 : -1) }; },
    build: function (t) {
        var W = t.W, H = t.H, rot = t.P.rot || 150, md = tn_md(t), lw = tn_lw(t, 0.008);
        var HD = tn_hd(t) + 'var e=ic(p),s=1-e;';
        var C = tn_copy(t, t.A, 'JZ Trans spin (prev cut)');
        tn_xf(C, 'JZ Trans spin', HD, { sc: 's', rot: jzN(rot) + '*e' });
        // its shadow turns with it (offset 0.02 / 0.03 minD in the spinning frame)
        var ds = jzEffect(C, 'ADBE Drop Shadow', 'JZ Trans spin shadow');
        jzEP(ds, 1, [0, 0, 0]); jzEP(ds, 2, 89); jzEP(ds, 5, 0);
        jzEX(ds, 3, HD + '146.31+' + jzN(rot) + '*e');
        jzEX(ds, 4, HD + jzN(md * 0.0361) + '*s');
        var Dk = tn_solid(t, '#000000', 'JZ Trans spin dim', HD + '40*(1-oc(p))'); Dk.moveBefore(t.B);
        tn_xf(t.B, 'JZ Trans in spinOut', HD, { sc: '1.08-0.08*oc(p)' });
        // accent frame around the spinning picture (screen-constant line width)
        var S = jzEvShape(t.comp, 'JZ Trans spin frame', t.t0, t.dur), g = jzGrp(S, 'frame');
        var r = jzAddRect(g, W, H, 0, 0, 0);    // (configured before the stroke is added \u2014 that invalidates r in AE)
        jzSetExpr(r.property('ADBE Vector Rect Size'), HD + '[Math.max(0,' + jzN(W) + '*s-' + jzN(lw) + '),Math.max(0,' + jzN(H) + '*s-' + jzN(lw) + ')]');
        jzAddStroke(g, tn_acc(t), lw);
        jzXf(S, 'ADBE Position').setValue([W / 2, H / 2]);
        jzSetExpr(jzXf(S, 'ADBE Rotate Z'), HD + jzN(rot) + '*e');
        jzSetExpr(jzXf(S, 'ADBE Opacity'), HD + 's<0.01?0:100*Math.min(1,p*6)');
    }
});

/* ---- inkBlob \u2014 \u30A4\u30F3\u30AF: an organic splash spreads from a point, rimmed in accent ink */
// smooth closed blob path (Catmull-Rom tangents) of radius R around the group origin, frozen at phase ph
function tn_blob(g, s0, R, ph) {
    var n = 72, v = [], it = [], ot = [], i, TAU = Math.PI * 2;
    var q1 = jzR(s0, 1, 17, 0) * 6, q2 = jzR(s0, 2, 17, 0) * 6, q3 = jzR(s0, 3, 17, 0) * 6;
    for (i = 0; i < n; i++) {
        var a = i / n * TAU, w = 1 + 0.13 * Math.sin(a * 3 + q1 + ph) + 0.08 * Math.sin(a * 5 + q2 - ph * 1.3) + 0.05 * Math.sin(a * 9 + q3 + ph * 0.7);
        v.push([Math.cos(a) * R * w, Math.sin(a) * R * w]);
    }
    for (i = 0; i < n; i++) {
        var P0 = v[(i + n - 1) % n], P2 = v[(i + 1) % n], tx = (P2[0] - P0[0]) / 6, ty = (P2[1] - P0[1]) / 6;
        ot.push([tx, ty]); it.push([-tx, -ty]);
    }
    var p = jzVecs(g).addProperty('ADBE Vector Shape - Group'), sh = new Shape();
    sh.vertices = v; sh.inTangents = it; sh.outTangents = ot; sh.closed = true;
    p.property('ADBE Vector Shape').setValue(sh);
    return p;
}
jzReg('trans', 'inkBlob', {
    plan: function (rng, st) { return { x: rng.pick([0.5, 0.5, 0.15, 0.85]) + rng.range(-0.08, 0.08), y: rng.pick([0.5, 0.25, 0.8]) + rng.range(-0.06, 0.06) }; },
    build: function (t) {
        var W = t.W, H = t.H, cx = W * (t.P.x == null ? 0.5 : t.P.x), cy = H * (t.P.y == null ? 0.5 : t.P.y), md = tn_md(t), ac = tn_acc(t);
        var s0 = (t.cut && t.cut.seed) || 7, TAU = Math.PI * 2;
        var R = Math.sqrt(Math.pow(Math.max(cx, W - cx), 2) + Math.pow(Math.max(cy, H - cy), 2)) * 1.36;
        var HD = tn_hd(t) + 'var e=ios(p),r=' + jzN(R) + '*e,rim=' + jzN(md * 0.035) + '*(0.4+e);';
        // the old cut on top (copy) with the blob as inverted matte \u2192 the new cut shows inside the blob
        var C = tn_copy(t, t.A, 'JZ Trans ink (prev cut)');
        var M = jzEvShape(t.comp, 'JZ Trans ink matte', t.t0, t.dur), gm = jzGrp(M, 'blob');
        tn_blob(gm, s0, R, 1.2); jzAddFill(gm, '#FFFFFF');
        jzSetExpr(jzGX(gm).property('ADBE Vector Scale'), HD + 'var k=100*r/' + jzN(R) + ';[k,k]');
        jzSetExpr(jzGX(gm).property('ADBE Vector Rotation'), HD + '24*p');
        jzXf(M, 'ADBE Position').setValue([cx, cy]);
        // accent rim (blob grown by `rim`) + droplets, kept out of the blob by a second copy of the matte
        var Rm = jzEvShape(t.comp, 'JZ Trans ink rim', t.t0, t.dur);
        for (var k = 0; k < 6; k++) {
            var a = jzR(s0, k, 7, 0) * TAU, f = 1.12 + 0.3 * jzR(s0, k, 8, 0), rr = md * (0.008 + 0.02 * jzR(s0, k, 9, 0));
            var gd = jzGrp(Rm, 'drop ' + (k + 1)), ee = jzAddEllipse(gd, 10, 10, 0, 0);
            jzSetExpr(ee.property('ADBE Vector Ellipse Size'), HD + 'var d=' + jzN(2 * rr) + '*cl(p*4);[d,d]');
            jzAddFill(gd, ac);    // (after the ellipse is configured \u2014 the add invalidates ee in AE)
            jzSetExpr(jzGX(gd).property('ADBE Vector Position'), HD + 'var d=r*' + jzN(f) + '+rim;[' + jzN(Math.cos(a)) + '*d,' + jzN(Math.sin(a)) + '*d]');
        }
        var gr = jzGrp(Rm, 'rim'); tn_blob(gr, s0, R, 1.2); jzAddFill(gr, ac);
        jzSetExpr(jzGX(gr).property('ADBE Vector Scale'), HD + 'var k=100*(r+rim)/' + jzN(R) + ';[k,k]');
        jzSetExpr(jzGX(gr).property('ADBE Vector Rotation'), HD + '24*p');
        jzXf(Rm, 'ADBE Position').setValue([cx, cy]);
        jzSetExpr(jzXf(Rm, 'ADBE Opacity'), HD + '100*(1-sm(0.75,0.98,p))');
        var M2 = M.duplicate(); M2.name = 'JZ Trans ink matte 2';
        tn_matte(M, C, TrackMatteType.ALPHA_INVERTED);
        Rm.moveBefore(M);
        tn_matte(M2, Rm, TrackMatteType.ALPHA_INVERTED);
    }
});

/* ---- shatterTiles \u2014 \u30BF\u30A4\u30EB\u5D29\u843D: the old cut breaks into tiles that tumble down */
jzReg('trans', 'shatterTiles', {
    plan: function (rng, st) { return { n: rng.int(6, 9), x: rng.range(0.3, 0.7), y: rng.range(0.3, 0.6) }; },
    build: function (t) {
        var W = t.W, H = t.H, n = Math.max(2, Math.round(t.P.n || 7)), cell = Math.max(W, H) / n, s0 = (t.cut && t.cut.seed) || 7;
        var cols = Math.ceil(W / cell - 1e-6), rows = Math.ceil(H / cell - 1e-6), D = Math.sqrt(W * W + H * H);
        var cx = W * (t.P.x == null ? 0.5 : t.P.x), cy = H * (t.P.y == null ? 0.45 : t.P.y);
        var HS = tn_hs(t) + 'function sm(a,b,x){x=cl((x-a)/(b-a));return x*x*(3-2*x);}';
        // one masked copy of the old cut per tile; tiles start falling outwards from (x, y)
        for (var j = 0; j < rows; j++) for (var i = 0; i < cols; i++) {
            var x0 = Math.round(i * W / cols), x1 = Math.round((i + 1) * W / cols), y0 = Math.round(j * H / rows), y1 = Math.round((j + 1) * H / rows);
            var mx = (x0 + x1) / 2, my = (y0 + y1) / 2;
            var d = 0.5 * Math.sqrt((mx - cx) * (mx - cx) + (my - cy) * (my - cy)) / D * 1.4 + 0.08 * jzR(s0, i, j, 3);
            var T = HS + 'var t=cl((p-' + jzN(Math.min(0.55, d)) + ')/0.45);';
            var Tl = tn_copy(t, t.A, 'JZ Trans tile ' + (i + 1) + '/' + (j + 1));
            tn_mask(Tl, x0, y0, x1, y1);
            tn_xf(Tl, 'JZ Trans tile', T, {
                anc: [mx, my],
                pos: [jzN((jzR(s0, i, j, 4) * 2 - 1) * W * 0.12) + '*t', '(t*t*1.25-' + jzN(jzR(s0, i, j, 6) * 0.08) + '*t)*' + jzN(H)],
                sc: '1-0.25*t', rot: jzN((jzR(s0, i, j, 5) * 2 - 1) * 70) + '*t', op: '1-sm(0.7,1,t)'
            });
        }
        var Dk = tn_solid(t, '#000000', 'JZ Trans shatter dim', tn_hd(t) + '30*(1-oc(p))'); Dk.moveBefore(t.B);
    }
});

/* ---- sliceShift \u2014 \u77ED\u518A\u305A\u3089\u3057: strips slide in alternate directions, trading the old cut for the new */
jzReg('trans', 'sliceShift', {
    plan: function (rng, st) { return { n: rng.int(5, 9), vert: rng.chance(0.25) }; },
    build: function (t) {
        var W = t.W, H = t.H, n = Math.max(2, Math.round(t.P.n || 7)), v = !!t.P.vert, Lx = v ? W : H, Mx = v ? H : W;
        var lw = Math.max(1, tn_md(t) * 0.003), HS = tn_hs(t) + TN_IOC;
        for (var i = 0; i < n; i++) {
            var a0 = Math.round(i * Lx / n), a1 = Math.round((i + 1) * Lx / n), sg = i % 2 ? 1 : -1;
            var O = HS + 'var o=Math.round(ioc((p-' + jzN(i / Math.max(1, n - 1) * 0.3) + ')/0.7)*' + jzN(Mx) + ')*' + sg + ';';
            var pr = [[t.A, 'o', 'A'], [t.B, 'o-(' + jzN(sg * Mx) + ')', 'B']];
            for (var q = 0; q < 2; q++) {
                var C = tn_copy(t, pr[q][0], 'JZ Trans slice ' + pr[q][2] + (i + 1));
                if (v) tn_mask(C, a0, 0, a1, H); else tn_mask(C, 0, a0, W, a1);
                tn_xf(C, 'JZ Trans slice', O, { pos: v ? ['0', pr[q][1]] : [pr[q][1], '0'] });
            }
        }
        var S = jzEvShape(t.comp, 'JZ Trans slice lines', t.t0, t.dur);
        for (var k = 1; k < n; k++) {
            var a = Math.round(k * Lx / n), g = jzGrp(S, 'line ' + k);
            if (v) jzAddRect(g, lw, H, 0, a, H / 2); else jzAddRect(g, W, lw, 0, W / 2, a);
            jzAddFill(g, tn_acc(t));
        }
        jzSetExpr(jzXf(S, 'ADBE Opacity'), tn_hd(t) + '90*Math.pow(bell(p),0.7)');
    }
});

/* ---- cubeTurn \u2014 \u30AD\u30E5\u30FC\u30D6: faux-3D cube turn \u2014 each face is drawn in vertical slices (masked copies) like the browser */
jzReg('trans', 'cubeTurn', {
    plan: function (rng, st) { return { dir: rng.chance(0.6) ? 1 : -1 }; },
    build: function (t) {
        var W = t.W, H = t.H, dir = t.P.dir < 0 ? -1 : 1, K = 8, SW = W / K;
        var HS = tn_hs(t) + TN_IOC + 'var ph=ioc(p)*Math.PI/2,cs=Math.cos(ph),sn=Math.sin(ph),DR=' + dir + ',CW=' + jzN(W) + ',CH=' + jzN(H) + ';' +
            'function prj(x,z){var xr=(x*cs-z*sn)*DR,zr=x*sn+z*cs,k=2.4/(3.4-zr);return [CW/2+xr*k*CW/2,k*CH/2];}';
        // faces: A = front (edges (-1,1)\u2192(1,1)), B = side ((1,1)\u2192(1,-1)); shade = black Fill
        var faces = [[t.A, [-1, 1], [1, 1], '0.55*(1-cs)', 'A'], [t.B, [1, 1], [1, -1], '0.55*(1-sn)', 'B']];
        for (var f = 0; f < 2; f++) {
            var F = faces[f], e0 = F[1], e1 = F[2];
            var vis = 'var f0=prj(' + e0[0] + ',' + e0[1] + '),f1=prj(' + e1[0] + ',' + e1[1] + '),vis=(f1[0]-f0[0])*DR>0.5;';
            for (var k = 1; k <= K; k++) {
                var u0 = (k - 1) / K, u1 = k / K, su = dir > 0 ? (k - 1) / K : 1 - k / K;
                var X0 = e0[0] + (e1[0] - e0[0]) * u0, Z0 = e0[1] + (e1[1] - e0[1]) * u0, X1 = e0[0] + (e1[0] - e0[0]) * u1, Z1 = e0[1] + (e1[1] - e0[1]) * u1;
                var Q = HS + vis + 'var q0=prj(' + jzN(X0) + ',' + jzN(Z0) + '),q1=prj(' + jzN(X1) + ',' + jzN(Z1) + '),xa=Math.min(q0[0],q1[0]),xb=Math.max(q0[0],q1[0]),hv=(q0[1]+q1[1])/2;';
                var C = tn_copy(t, F[0], 'JZ Trans cube ' + F[4] + k);
                tn_mask(C, su * W - 0.5, 0, su * W + SW + 0.5, H);
                var sh = jzEffect(C, 'ADBE Fill', 'JZ Trans cube shade'); jzEP(sh, 3, [0, 0, 0]);
                jzEX(sh, 7, Q + F[3]);
                var e = jzEffect(C, 'ADBE Geometry2', 'JZ Trans cube face');
                jzEP(e, 1, [su * W + SW / 2, H / 2]); jzEP(e, 3, 0);
                jzEX(e, 2, Q + '[(xa+xb)/2,CH/2]');
                jzEX(e, 4, Q + '100*2*hv/CH');
                jzEX(e, 5, Q + '100*(xb-xa+1)/' + jzN(SW));
                jzEX(e, 9, Q + 'vis?100:0');
            }
        }
        // the void behind the cube: both backgrounds darkened, cross-fading
        var S2 = tn_solid(t, jzMixHex(t.sc.bg, '#000000', 0.55), 'JZ Trans cube back 2', tn_hd(t) + '100*p'); S2.moveBefore(t.B);
        var S1 = tn_solid(t, jzMixHex((t.scPrev || t.sc).bg, '#000000', 0.55), 'JZ Trans cube back'); S1.moveBefore(t.B);
    }
});

/* ---- flashCross \u2014 \u30D5\u30E9\u30C3\u30B7\u30E5\u8EE2\u63DB: a quick flash of light carries the cut over */
jzReg('trans', 'flashCross', {
    plan: function (rng, st) { return { c: rng.chance(0.3) ? 'accent' : 'white' }; },
    build: function (t) {
        var pb = (t.scPrev || t.sc).bg, light = jzLum(pb) > 0.62 && jzLum(t.sc.bg) > 0.62;
        var fl = (t.P.c === 'accent' || light) ? tn_acc(t) : '#FFFFFF', HD = tn_hd(t);
        // cross-dissolve: the old cut (a copy on top) fades out between p .30 and .62 \u2026
        var C = tn_copy(t, t.A, 'JZ Trans flash (prev cut)');
        jzSetExpr(jzXf(C, 'ADBE Opacity'), HD + '100*(1-sm(0.3,0.62,p))');
        // \u2026 under a flash that peaks at p .45
        tn_solid(t, fl, 'JZ Trans flash', HD + 'var a=p<0.45?iq(p/0.45):1-oc((p-0.45)/0.55);92*a');
    }
});

/* ---- pixelate \u2014 \u30E2\u30B6\u30A4\u30AF\u8EE2\u63DB: the old cut breaks into big pixels, the new one resolves out of them */
jzReg('trans', 'pixelate', {
    plan: function (rng, st) { return { k: rng.range(11, 17) }; },
    build: function (t) {
        var W = t.W, H = t.H, maxB = tn_md(t) / (t.P.k || 14);
        var HD = tn_hd(t) + 'function mb(q){var bs=1+' + jzN(maxB - 1) + '*q;return bs<1.6?[' + W + ',' + H + ']:[Math.ceil(' + W + '/bs),Math.ceil(' + H + '/bs)];}';
        var Bc = tn_copy(t, t.B, 'JZ Trans pixelate (new cut)'), Ac = tn_copy(t, t.A, 'JZ Trans pixelate (prev cut)');
        var ls = [[Ac, 'ic(p/0.55)'], [Bc, 'ic((1-p)/0.55)']];
        for (var i = 0; i < 2; i++) {
            var mo = jzEffect(ls[i][0], 'ADBE Mosaic', 'JZ Trans pixelate'); jzEP(mo, 3, 0);
            jzEX(mo, 1, HD + 'mb(' + ls[i][1] + ')[0]');
            jzEX(mo, 2, HD + 'mb(' + ls[i][1] + ')[1]');
        }
        jzSetExpr(jzXf(Bc, 'ADBE Opacity'), HD + '100*sm(0.4,0.6,p)');
    }
});

// ================================================================ text treatments part 1 (AE port of the treat entries in src/11p_looks.js)
// treat.apply(ctx, L, P, o) runs for every MAIN lyric layer after its motion is set. P = the cut's treatP (same keys as the
// browser plan). Work on the layer itself (text document, animators, effects) or duplicate it (L.duplicate() copies the
// motion) for shadows / extrusions \u2014 put duplicates behind with dup.moveAfter(L).
// Part 1 prefers effects on the lyric itself (stacked Drop Shadows for extrusion / long shadow / ring / glow) and the lyric as
// a track matte for fills: AE's random() / wiggle() depend on the layer, so a duplicate's glyphs would not follow a random
// enter / exit. Marks (marker, lines, boxes, dots) are shape layers parented to the lyric (its layer space).

/* ---- outline \u2014 \u888B\u6587\u5B57: hollow letters (stroke only) */
jzReg('treat', 'outline', {
    plan: function (rng, st) { return { k: rng.range(0.022, 0.038) }; },
    apply: function (ctx, L, P, o) {
        var col = jzTextColor(L);
        jzTextDoc(L, function (td) {
            td.applyFill = false; td.applyStroke = true; td.strokeColor = jzHex(col);
            td.strokeWidth = Math.max(1.4 * ctx.u, td.fontSize * (P.k || 0.03));
        });
    }
});

// ---------------------------------------------------------------- shared helpers (tr1_)
// colour choices: same rules as the browser pack (accentFor / textOn / markCol / twoTone)
function tr1_first(list, test, fb) { for (var i = 0; i < list.length; i++) if (list[i] && test(list[i])) return list[i]; return fb; }
function tr1_best(list, against) { var b = list[0], bv = -1; for (var i = 0; i < list.length; i++) { if (!list[i]) continue; var v = jzContrast(list[i], against); if (v > bv) { bv = v; b = list[i]; } } return b; }
function tr1_dark(c) { return jzLum(c) < 0.45; }
function tr1_accentFor(sc, col, min) {
    if (min == null) min = 1.6;
    return tr1_first([sc.accent, sc.accent2, sc.ghostA, sc.ghostB], function (c) { return jzContrast(c, col) >= min && jzContrast(c, sc.bg) >= 1.5; }, jzFitContrast(sc.accent, col, min + 0.3));
}
function tr1_textOn(box, pref, sc) { return jzContrast(pref, box) >= 3 ? pref : tr1_best([sc.bg, sc.fg, sc.ink, '#111111', '#FFFFFF'], box); }
function tr1_markCol(sc, col) { return tr1_first([sc.accent, sc.accent2, col], function (c) { return jzContrast(c, sc.bg) >= 2; }, col); }
function tr1_twoTone(sc, col) { return tr1_first([sc.accent, sc.accent2, sc.ghostA, sc.ghostB], function (c) { return jzContrast(c, col) >= 1.5 && jzContrast(c, sc.bg) >= 2; }, jzMixHex(col, sc.bg, 0.45)); }

// static (pre-expression) value of a transform property
function tr1_sv(L, mn) { var p = jzXf(L, mn); try { return p.valueAtTime(0, true); } catch (e) { return p.value; } }
function tr1_doc(L) { return L.property('ADBE Text Properties').property('ADBE Text Document').value; }
// the browser's alive(): filled, opaque enough, has text
function tr1_alive(L, amin) {
    var td; try { td = tr1_doc(L); } catch (e) { return false; }
    if (!td || td.applyFill === false || !jzTrim(String(td.text || '')) || !(td.fontSize > 1)) return false;
    return tr1_sv(L, 'ADBE Opacity') >= (amin == null ? 0.9 : amin) * 100 - 0.01;
}
// expression header of this lyric layer's motion (IN, DL, OS, OD, DUR, SD, SZ, P, PO, AMT + easing fns)
function tr1_head(ctx, L, o) { return jzHead(ctx, { mi: (o && o.mi) || 0, size: jzFontSize(L) }); }
// fewer copies when the content comp already holds many layers (layouts with one layer per glyph)
function tr1_budget(ctx, want) { var n = ctx.comp.numLayers; return n > 30 ? Math.min(want, 2) : (n > 16 ? Math.min(want, 4) : want); }
// duplicate of the lyric (keeps text, animators, effects and motion expressions); placed right under `after`
function tr1_dup(L, name, after) { var D = L.duplicate(); try { D.name = name; } catch (e) {} D.moveAfter(after || L); return D; }
// set every Fill / Stroke Colour animator of a copy to one colour (layout accents must not tint shadows / outlines)
function tr1_recolor(D, hex) {
    var an = D.property('ADBE Text Properties').property('ADBE Text Animators'), i, c = jzHex(hex);
    for (i = 1; i <= an.numProperties; i++) {
        var pr = an.property(i).property('ADBE Text Animator Properties'), f = pr.property('ADBE Text Fill Color'), s = pr.property('ADBE Text Stroke Color');
        if (f) f.setValue(c);
        if (s) s.setValue(c);
    }
}
// copy of L drawn in one colour (fill, optional stroke) and shifted by (dx, dy) comp px in the layer's own frame
function tr1_copy(L, name, after, hex, stroke, dx, dy, fill) {
    var D = tr1_dup(L, name, after);
    jzTextDoc(D, function (td) {
        td.applyFill = fill !== false; if (td.applyFill) td.fillColor = jzHex(hex);
        if (stroke > 0) { td.applyStroke = true; td.strokeColor = jzHex(hex); td.strokeWidth = stroke; try { td.strokeOverFill = false; } catch (e) {} }
        else td.applyStroke = false;
    });
    tr1_recolor(D, hex);
    if (dx || dy) {  // shift through the anchor point: the offset scales / rotates with the layer like the browser's per-glyph offset
        var a = tr1_sv(D, 'ADBE Anchor Point'), s = tr1_sv(D, 'ADBE Scale'), sx = Math.abs(s[0]) / 100 || 1, sy = Math.abs(s[1]) / 100 || 1;
        jzXf(D, 'ADBE Anchor Point').setValue([a[0] - (dx || 0) / sx, a[1] - (dy || 0) / sy]);
    }
    return D;
}
// Drop Shadow on L (layer px; direction from an offset vector). Stacked shadows see the previous ones: that is how the
// extrusion / long shadow / outer ring are grown from the lyric's own pixels (they follow every per-glyph motion exactly).
function tr1_shadow(L, name, hex, dx, dy, soft, alpha) {
    var e = jzEffect(L, 'ADBE Drop Shadow', name);
    jzEP(e, 1, jzHex(hex)); jzEP(e, 2, Math.round(jzClamp(alpha == null ? 1 : alpha, 0, 1) * 255));
    jzEP(e, 3, (dx || dy) ? ((Math.atan2(dx, -dy) * 180 / Math.PI) + 360) % 360 : 135); jzEP(e, 4, Math.sqrt(dx * dx + dy * dy));
    if ((soft || 0) <= 250) jzEP(e, 5, soft || 0); else jzEX(e, 5, jzN(soft));     // big radii (4K) through an expression: no range error
    return e;
}
// offsets of a solid sweep of length len (layer px) built from stacked shadows: each stage copies everything so far,
// so the offsets grow geometrically (factor 1 + g) from a first step d
function tr1_sweep(len, d, g) {
    var out = [], cov = 0, o;
    d = Math.max(0.5, d);
    while (cov < len - 0.01 && out.length < 16) { o = cov < d * 2 ? d : cov * g; o = Math.min(o, len - cov); out.push({ o: o, t0: cov, t1: cov + o }); cov += o; }
    return out;
}
// average layer scale (layer px -> comp px)
function tr1_k(L) { var s = tr1_sv(L, 'ADBE Scale'); return Math.max(0.01, (Math.abs(s[0]) + Math.abs(s[1])) / 200); }
// does the lyric's motion use per-layer randomness? (AE's random() / wiggle() depend on the layer, so a DUPLICATE of the
// layer would move its glyphs differently \u2014 duplicates are only used when this is false, or hidden while glyphs move)
function tr1_rnd(L) {
    var re = /random|wiggle/, i, j, an, fx, mns = ['ADBE Position', 'ADBE Scale', 'ADBE Rotate Z', 'ADBE Opacity'];
    var chk = function (p) { try { return !!(p && p.expression && re.test(p.expression)); } catch (e) { return false; } };
    try {
        an = L.property('ADBE Text Properties').property('ADBE Text Animators');
        for (i = 1; i <= an.numProperties; i++) {
            var sels = an.property(i).property('ADBE Text Selectors'), pr = an.property(i).property('ADBE Text Animator Properties');
            for (j = 1; j <= sels.numProperties; j++) if (sels.property(j).matchName === 'ADBE Text Expressible Selector' && chk(sels.property(j).property('ADBE Text Expressible Amount'))) return true;
            for (j = 1; j <= pr.numProperties; j++) if (chk(pr.property(j))) return true;
        }
        fx = L.property('ADBE Effect Parade');
        for (i = 1; i <= fx.numProperties; i++) for (j = 1; j <= fx.property(i).numProperties; j++) if (chk(fx.property(i).property(j))) return true;
        for (i = 0; i < mns.length; i++) if (chk(jzXf(L, mns[i]))) return true;
    } catch (e2) { return true; }
    return false;
}
// alpha track matte: `F` shows only inside matte M's alpha (M hidden). AE 23+ links the matte by identity (setTrackMatte), so
// layers a layout later moves between them cannot break it; older AE (and the preview) use the layer directly above.
function tr1_matte(M, F) {
    F.moveAfter(M);
    var ok = false;
    if (typeof F.setTrackMatte === 'function') { try { F.setTrackMatte(M, TrackMatteType.ALPHA); ok = true; } catch (e) { ok = false; } }
    if (!ok) F.trackMatteType = TrackMatteType.ALPHA;
    M.enabled = false;
}
// fill layer in L's layer space shown through the glyphs of L (L = the matte): gradient rows / split colours / patterns.
// Flat far-reaching areas keep glyphs that fly far from their place (enter / exit) filled.
function tr1_fillLayer(ctx, L, name) { var F = tr1_shapeOn(ctx, L, name, false, false); tr1_matte(L, F); return F; }
function tr1_far(ctx, L) { return (ctx.W + ctx.H) * 2 / tr1_k(L); }

// ---- text geometry in the layer's own space (the space of sourceRectAtTime / effect points / parented shapes)
function tr1_rectOf(ctx, L, str) {
    var td = tr1_doc(L), P = ctx.comp.layers.addText(str), r = null;
    try {
        td.text = str; td.applyStroke = false;
        P.property('ADBE Text Properties').property('ADBE Text Document').setValue(td);
        r = P.sourceRectAtTime(0, false);
    } catch (e) { r = null; }
    try { P.remove(); } catch (e2) {}
    if (!r || !(r.width >= 0)) { var n = jzChars(str).length; r = { left: -td.fontSize * n / 2, top: -td.fontSize * 0.88, width: td.fontSize * n, height: td.fontSize }; }
    return { left: r.left, top: r.top, width: r.width, height: r.height };
}
function tr1_adv(ch) {       // rough advance (em) for spreading glyph centres along a measured line
    var c = ch.charCodeAt(0);
    if (ch === '\u3000' || c >= 0x2E80 || (c >= 0xFF01 && c <= 0xFF60)) return 1;
    if (ch === ' ') return 0.3;
    if (/[MWmw@%]/.test(ch)) return 0.86;
    if (/[A-Z]/.test(ch)) return 0.66;
    if (/[a-z0-9]/.test(ch)) return 0.55;
    return 0.34;
}
// rows (text lines) with their measured extent, glyph centres (textIndex order), and the browser's "line spans":
// one span per row for horizontal text, one per column for vertical text (one glyph per row)
function tr1_lay(ctx, L) {
    var td = tr1_doc(L), fs = td.fontSize, lead = (td.autoLeading === false && td.leading > 0) ? td.leading : fs * 1.2;
    var trk = (td.tracking || 0) / 1000 * fs, just = td.justification;
    var LEFT = ParagraphJustification.LEFT_JUSTIFY, RIGHT = ParagraphJustification.RIGHT_JUSTIFY;
    var rowsT = String(td.text).split(/\r\n|\r|\n/), rows = [], glyphs = [], gi = 0, vert = rowsT.length > 1, i, k;
    for (i = 0; i < rowsT.length; i++) {
        var cs = jzChars(rowsT[i]), n = cs.length, vis = 0;
        for (k = 0; k < n; k++) if (!/[\s\u3000]/.test(cs[k])) vis++;
        if (vis > 1) vert = false;
        if (!vis) { gi += n; continue; }
        var r = tr1_rectOf(ctx, L, rowsT[i]), adv = [], tot = 0;
        for (k = 0; k < n; k++) { adv.push(tr1_adv(cs[k]) * fs); tot += adv[k] + (k < n - 1 ? trk : 0); }
        var kf = r.width > 1 && tot > 1 ? r.width / tot : 1; if (kf < 0.8 || kf > 1.25) kf = 1;
        var w = tot * kf, x0 = just === LEFT ? r.left : (just === RIGHT ? r.left + r.width - w : r.left + r.width / 2 - w / 2);
        var base = i * lead, cy = base - 0.38 * fs, x = x0, row = { li: i, x0: x0, x1: x0 + w, cy: cy, base: base, g: [] };
        for (k = 0; k < n; k++) {
            var gd = { ch: cs[k], i: gi + k, row: i, cx: x + adv[k] * kf / 2, cy: cy, w: adv[k] * kf, h: fs };
            if (!/[\s\u3000]/.test(cs[k])) { glyphs.push(gd); row.g.push(gd); }
            x += (adv[k] + trk) * kf;
        }
        rows.push(row); gi += n;
    }
    var spans = [];
    if (vert && glyphs.length > 1) {
        var cx = 0; for (k = 0; k < glyphs.length; k++) { cx += glyphs[k].cx; glyphs[k].w = fs; glyphs[k].h = Math.min(fs, lead); }
        spans.push({ vert: true, a0: glyphs[0].cy - fs / 2, a1: glyphs[glyphs.length - 1].cy + fs / 2, c: cx / glyphs.length, g: glyphs.slice(0) });
    } else {
        vert = false;
        for (i = 0; i < rows.length; i++) spans.push({ vert: false, a0: rows[i].x0, a1: rows[i].x1, c: rows[i].cy, g: rows[i].g });
    }
    var bx0 = 1e9, bx1 = -1e9, by0 = 1e9, by1 = -1e9;
    for (k = 0; k < glyphs.length; k++) { var G = glyphs[k]; bx0 = Math.min(bx0, G.cx - G.w / 2); bx1 = Math.max(bx1, G.cx + G.w / 2); by0 = Math.min(by0, G.cy - fs / 2); by1 = Math.max(by1, G.cy + fs / 2); }
    if (bx0 > bx1) { var rr = jzRect(L); bx0 = rr.left; bx1 = rr.left + rr.width; by0 = rr.top; by1 = rr.top + rr.height; }
    return { fs: fs, lead: lead, rows: rows, glyphs: glyphs, spans: spans, vert: vert, n: gi, box: { x0: bx0, x1: bx1, y0: by0, y1: by1, cx: (bx0 + bx1) / 2, cy: (by0 + by1) / 2 } };
}
function tr1_isVert(L) {
    var rows = String(tr1_doc(L).text).split(/\r\n|\r|\n/), i;
    if (rows.length < 2) return false;
    for (i = 0; i < rows.length; i++) if (jzCount(rows[i]) > 1) return false;
    return true;
}
// shape layer living in L's layer space (parented, identity transform): follows the lyric's layer motion and opacity.
// fade: expression header (tr1_head) = also fade out with the exit (the browser draws marks with the item's alpha); false = no opacity link
function tr1_shapeOn(ctx, L, name, above, fade) {
    var S = ctx.comp.layers.addShape(); S.name = name;
    S.parent = L;
    jzXf(S, 'ADBE Anchor Point').setValue([0, 0]); jzXf(S, 'ADBE Position').setValue([0, 0]);
    jzXf(S, 'ADBE Scale').setValue([100, 100]); jzXf(S, 'ADBE Rotate Z').setValue(0);
    if (above) S.moveBefore(L); else S.moveAfter(L);
    if (fade !== false) jzSetExpr(jzXf(S, 'ADBE Opacity'), (fade || '') + 'value*(hasParent?parent.transform.opacity/100:1)' + (fade ? '*(1-iq(PO))' : ''));
    return S;
}
function tr1_arr(a) { var s = []; for (var i = 0; i < a.length; i++) s.push(a[i] == null ? 'null' : jzN(a[i])); return '[' + s.join(',') + ']'; }

// ================================================================ outlines / extrusions / shadows
/* ---- outlineFill \u2014 \u7E01\u53D6\u308A: a thick accent stroke under the fill */
jzReg('treat', 'outlineFill', {
    plan: function (rng, st) { return { k: rng.range(0.075, 0.12), c: rng.int(0, 2) }; },
    apply: function (ctx, L, P, o) {
        if (!tr1_alive(L)) return;
        var sc = ctx.sc, col = jzTextColor(L);
        var sk = P.c === 2 ? tr1_first([sc.accent2, sc.accent], function (c) { return jzContrast(c, col) >= 1.8; }, tr1_accentFor(sc, col, 1.8)) : tr1_accentFor(sc, col, 1.8);
        jzTextDoc(L, function (td) {
            td.applyStroke = true; td.strokeColor = jzHex(sk);
            td.strokeWidth = Math.max(2 * ctx.u, td.fontSize * (P.k || 0.09));
            try { td.strokeOverFill = false; } catch (e) {}
        });
    }
});

/* ---- doubleOutline \u2014 \u4E8C\u91CD\u7E01: gap-coloured stroke under the fill + an outer accent ring (eight stacked hard shadows = dilation) */
jzReg('treat', 'doubleOutline', {
    plan: function (rng, st) { return { a: rng.range(0.07, 0.09), b: rng.range(0.08, 0.11) }; },
    apply: function (ctx, L, P, o) {
        if (!tr1_alive(L)) return;
        var sc = ctx.sc, col = jzTextColor(L), s = jzFontSize(L), a = P.a || 0.08, b = P.b || 0.095, i;
        var ring = tr1_accentFor(sc, col, 1.6);
        var gap = jzContrast(sc.bg, col) >= 1.5 && jzContrast(sc.bg, ring) >= 1.3 ? sc.bg : tr1_best([sc.ink, sc.fg, '#000000', '#FFFFFF'], ring);
        jzTextDoc(L, function (td) {
            td.applyStroke = true; td.strokeColor = jzHex(gap); td.strokeWidth = Math.max(2 * ctx.u / tr1_k(L), s * a);
            try { td.strokeOverFill = false; } catch (e) {}
        });
        // ring: the browser strokes a copy s*(a+b) wide, i.e. s*b/2 beyond the gap stroke; 8 shadows at 45deg steps grow an octagon of that radius
        var r = Math.max(2 * ctx.u / tr1_k(L), s * b / 2) * 0.4142;
        for (i = 0; i < 8; i++) tr1_shadow(L, 'JZ Ring ' + (i + 1), ring, Math.cos(i * Math.PI / 4) * r, Math.sin(i * Math.PI / 4) * r, 0, 1);
    }
});

/* ---- extrude \u2014 \u7ACB\u4F53: solid extrusion grown from the lyric's own alpha (stacked hard shadows with doubling offsets) */
jzReg('treat', 'extrude', {
    plan: function (rng, st) { return { d: rng.range(0.09, 0.13), dir: rng.pick([[1, 1], [1, 1], [-1, 1], [1, 0.55]]), c: rng.int(0, 1) }; },
    apply: function (ctx, L, P, o) {
        if (!tr1_alive(L)) return;
        var sc = ctx.sc, col = jzTextColor(L), s = jzFontSize(L), dir = P.dir || [1, 1], i;
        var base = P.c ? tr1_first([sc.ink, sc.accent2], function (c) { return jzContrast(c, col) >= 1.6 && jzContrast(c, sc.bg) >= 1.4; }, tr1_accentFor(sc, col, 1.6)) : tr1_accentFor(sc, col, 1.6);
        var ec = jzMixHex(base, '#000000', tr1_dark(sc.bg) ? 0.3 : 0.2);
        var ln = Math.sqrt(dir[0] * dir[0] + dir[1] * dir[1]) || 1, len = s * (P.d || 0.11) * ln, ux = dir[0] / ln, uy = dir[1] / ln;
        var st = tr1_sweep(len, 1.5 * ctx.u / tr1_k(L), 1);
        for (i = 0; i < st.length; i++) tr1_shadow(L, 'JZ Extrude ' + (i + 1), ec, ux * st[i].o, uy * st[i].o, 0, 1);
    }
});

/* ---- longShadow \u2014 \u9577\u3044\u5F71: a long flat shadow fading out along its angle (stacked shadows, farther stages fainter) */
jzReg('treat', 'longShadow', {
    plan: function (rng, st) { return { L: rng.range(0.35, 0.65), ang: rng.pick([45, 45, 35, 60, 135]) }; },
    apply: function (ctx, L, P, o) {
        if (!tr1_alive(L)) return;
        var sc = ctx.sc, col = jzTextColor(L), s = jzFontSize(L), i;
        var sh = tr1_dark(sc.bg) ? jzMixHex(sc.bg, tr1_accentFor(sc, col, 1.5), 0.5) : jzMixHex(sc.bg, sc.fg, 0.28);
        var len = s * (P.L || 0.5), an = (P.ang == null ? 45 : P.ang) * Math.PI / 180, ux = Math.cos(an), uy = Math.sin(an);
        // each stage adds the pixels of its band mostly from the text itself, so its opacity ~ the browser's copy alpha there
        var A = function (t) { return 0.72 * (1 - 0.85 * jzClamp(t / len, 0, 1)); };
        var st = tr1_sweep(len, 1.5 * ctx.u / tr1_k(L), 0.35);
        for (i = 0; i < st.length; i++) {
            var al = A((st[i].t0 + st[i].t1) / 2);
            tr1_shadow(L, 'JZ Long Shadow ' + (i + 1), sh, ux * st[i].o, uy * st[i].o, 0, al);
        }
    }
});

/* ---- hardShadow \u2014 \u305A\u3089\u3057\u5F71: one flat accent shadow offset behind (hard Drop Shadow) */
jzReg('treat', 'hardShadow', {
    plan: function (rng, st) { return { d: rng.range(0.05, 0.085), dir: rng.pick([[1, 1], [1, 1], [-1, 1], [1, -1], [0.45, 1]]) }; },
    apply: function (ctx, L, P, o) {
        if (!tr1_alive(L)) return;
        var sh = tr1_accentFor(ctx.sc, jzTextColor(L), 1.6), s = jzFontSize(L), dir = P.dir || [1, 1], d = s * (P.d || 0.065);
        tr1_shadow(L, 'JZ Hard Shadow', sh, dir[0] * d, dir[1] * d, 0, 1);
    }
});

/* ---- softShadow \u2014 \u307C\u304B\u3057\u5F71: soft Drop Shadow on the lyric layer */
jzReg('treat', 'softShadow', {
    plan: function (rng, st) { return { b: rng.range(0.08, 0.14), dy: rng.range(0.03, 0.07) }; },
    apply: function (ctx, L, P, o) {
        if (!tr1_alive(L)) return;
        var sc = ctx.sc, s = jzFontSize(L), dk = tr1_dark(sc.bg);
        var c = dk ? jzMixHex(sc.bg, sc.accent, 0.45) : jzMixHex(sc.fg, '#000000', 0.5), al = dk ? 0.75 : 0.36;
        tr1_shadow(L, 'JZ Soft Shadow', c, s * 0.02, s * (P.dy || 0.05), s * (P.b || 0.11) * 2, al);
    }
});

/* ---- glow \u2014 \u767A\u5149: coloured halo behind the lyric (two soft zero-distance shadows, breathing radius) */
jzReg('treat', 'glow', {
    plan: function (rng, st) { return { b: rng.range(0.16, 0.26), self: rng.chance(0.5) }; },
    apply: function (ctx, L, P, o) {
        if (!tr1_alive(L)) return;
        var sc = ctx.sc, col = jzTextColor(L), dk = tr1_dark(sc.bg), s = jzFontSize(L), b = P.b || 0.21;
        var gc = dk ? tr1_first(P.self ? [col, sc.accent, sc.accent2] : [sc.accent, sc.accent2, col], function (c) { return jzLum(c) > jzLum(sc.bg) + 0.2; }, col)
            : tr1_first([sc.accent, sc.accent2, sc.ghostA, sc.ghostB], function (c) { return jzContrast(c, sc.bg) >= 2 && jzLum(c) > 0.08 && jzContrast(c, col) >= 1.4; }, col);
        // dark schemes: a tight bright core + a wide halo (the browser screens its halo twice); light schemes: one soft halo at 75%
        var pulse = '*(0.9+0.1*Math.sin(time*3.2))', e;
        if (dk) { e = tr1_shadow(L, 'JZ Glow core', gc, 0, 0, s * b * 1.4, 1); jzEX(e, 5, jzN(s * b * 1.4) + pulse); }
        e = tr1_shadow(L, 'JZ Glow halo', gc, 0, 0, s * b * (dk ? 3.2 : 3.6), dk ? 0.9 : 0.6); jzEX(e, 5, jzN(s * b * (dk ? 3.2 : 3.6)) + pulse);
    }
});

// ================================================================ marks drawn with the text (bars / lines / boxes / dots) \u2014 shape layers parented to the lyric
/* ---- marker \u2014 \u30DE\u30FC\u30AB\u30FC: highlighter bar sweeping in behind each line (box / skewed / lower half), glyphs it covers flip colour */
jzReg('treat', 'marker', {
    plan: function (rng, st) { return { v: rng.pick(['box', 'box', 'skew', 'half']), c: rng.int(0, 1) }; },
    apply: function (ctx, L, P, o) {
        if (!tr1_alive(L, 0.9)) return;
        var sc = ctx.sc, col = jzTextColor(L), half = P.v === 'half', i, k;
        var box = half ? jzFitContrast(tr1_first([sc.accent, sc.accent2], function (c) { return jzContrast(c, col) >= 2; }, sc.accent), col, 2.2)
            : tr1_first(P.c ? [sc.accent2, sc.accent, sc.ink, sc.fg] : [sc.accent, sc.accent2, sc.ink, sc.fg], function (c) { return jzContrast(c, sc.bg) >= 2.2; }, sc.fg);
        var tc = half ? col : tr1_textOn(box, col, sc);
        var G = tr1_lay(ctx, L), s = G.fs, HD = tr1_head(ctx, L, o);
        if (!G.spans.length) return;
        // progress of span k: in = oe over 0.32 s (staggered 0.07 s per line), out = ic(PO) from the start of the line
        var prog = function (k) { return 'var q=oe(cl((time-DL-' + jzN(k * 0.07) + ')/0.32)),o=ic(PO);'; };
        if (tc !== col) {       // glyphs inside the covered part of their line take the text-on-box colour
            var fr = [], li = [];
            for (k = 0; k < G.n; k++) { fr.push(null); li.push(0); }
            for (k = 0; k < G.spans.length; k++) {
                var sp = G.spans[k], len = Math.max(1, sp.a1 - sp.a0);
                for (i = 0; i < sp.g.length; i++) { var g = sp.g[i]; fr[g.i] = ((sp.vert ? g.cy : g.cx) - sp.a0) / len; li[g.i] = k; }
            }
            jzAnimator(L, 'JZ Marker Text', [['ADBE Text Fill Color', jzHex(tc)]], HD + 'var FR=' + tr1_arr(fr) + ',LI=' + tr1_arr(li) + ';var f=FR[textIndex-1],l=LI[textIndex-1]||0;' +
                'var q=oe(cl((time-DL-l*0.07)/0.32)),o=ic(PO);(f==null)?0:((f>=o&&f<=q)?100:0)');
        }
        var S = tr1_shapeOn(ctx, L, 'JZ Marker', false, HD);
        for (k = 0; k < G.spans.length; k++) {
            var Sp = G.spans[k], pad = s * 0.14, a0 = Sp.a0 - pad, ln = Sp.a1 + pad - a0, th = s * (half ? 0.52 : 1.08);
            var c0 = half ? Sp.c + s * 0.02 + th / 2 : Sp.c;
            // (the rect is configured before the fill is added \u2014 adding the fill invalidates r in AE)
            var g2 = jzGrp(S, 'bar ' + (k + 1)), r = jzAddRect(g2, Sp.vert ? th : ln, Sp.vert ? ln : th);
            var H2 = HD + prog(k) + 'var A0=' + jzN(a0) + ',LN=' + jzN(ln) + ',TH=' + jzN(th) + ';var w=LN*Math.max(0,q-o);';
            r.property('ADBE Vector Rect Size').expression = H2 + (Sp.vert ? '[TH,w]' : '[w,TH]');
            r.property('ADBE Vector Rect Position').expression = H2 + 'var m=A0+LN*(o+Math.max(q,o))/2;' + (Sp.vert ? '[0,m]' : '[m,0]');
            jzAddFill(g2, box, half ? 92 : 100);
            var X = jzGX(g2);
            X.property('ADBE Vector Position').setValue(Sp.vert ? [c0, 0] : [0, c0]);
            if (P.v === 'skew') { X.property('ADBE Vector Skew').setValue(23.7); if (Sp.vert) X.property('ADBE Vector Skew Axis').setValue(90); }
        }
    }
});

/* ---- underline \u2014 \u4E0B\u7DDA: line drawn under each line (bar / double / wave), retracts from the start on exit */
jzReg('treat', 'underline', {
    plan: function (rng, st) { return { v: rng.pick(['bar', 'bar', 'double', 'wave']), k: rng.range(0.05, 0.075) }; },
    apply: function (ctx, L, P, o) {
        if (!tr1_alive(L, 0.9) || jzCount(tr1_doc(L).text) < 2) return;
        var lc = tr1_markCol(ctx.sc, jzTextColor(L)), G = tr1_lay(ctx, L), s = G.fs, HD = tr1_head(ctx, L, o), k, j;
        if (!G.spans.length) return;
        var S = jzNoGhost(tr1_shapeOn(ctx, L, 'JZ Underline', true, HD));          // ghost off in the browser (marker / strike / boxes are ghosted)
        for (k = 0; k < G.spans.length; k++) {
            var Sp = G.spans[k], a0 = Sp.a0 - s * 0.04, a1 = Sp.a1 + s * 0.04, off = Sp.c + s * 0.6, th = Math.max(2 * ctx.u, s * (P.k || 0.06));
            var pt = (function (vert, off0) { return function (u, d) { return vert ? [off0 + d, u] : [u, off0 + d]; }; })(Sp.vert, off);
            var g = jzGrp(S, 'line ' + (k + 1)), w = th;
            if (P.v === 'wave') {
                var pts = [], wl = s * 0.32, m = Math.min(160, Math.ceil((a1 - a0) / (wl / 8)) + 1);
                for (j = 0; j < m; j++) { var u = a0 + (a1 - a0) * j / (m - 1); pts.push(pt(u, Math.sin((u - a0) / wl * Math.PI * 2) * th * 0.7)); }
                jzAddPath(g, pts, false); w = th * 0.75;
            } else if (P.v === 'double') {
                jzAddPath(g, [pt(a0, -th * 0.55), pt(a1, -th * 0.55)], false); jzAddPath(g, [pt(a0, th * 0.55), pt(a1, th * 0.55)], false); w = th * 0.42;
            } else jzAddPath(g, [pt(a0, 0), pt(a1, 0)], false);
            jzAddStroke(g, lc, w);
            var H2 = HD + 'var q=oe(cl((time-DL-' + jzN(0.08 + k * 0.08) + ')/0.45)),o=ic(PO);';
            jzAddTrimPaths(g, H2 + 'Math.max(q,o)*100', H2 + 'o*100');
        }
    }
});

/* ---- strike \u2014 \u53D6\u308A\u6D88\u3057\u7DDA: a slightly tilted line struck through each line after the entrance */
jzReg('treat', 'strike', {
    plan: function (rng, st) { return { v: rng.pick(['one', 'one', 'two']), ang: rng.range(-4, 4), k: rng.range(0.06, 0.085) }; },
    apply: function (ctx, L, P, o) {
        if (!tr1_alive(L, 0.9)) return;
        var lc = tr1_accentFor(ctx.sc, jzTextColor(L), 1.8), G = tr1_lay(ctx, L), s = G.fs, HD = tr1_head(ctx, L, o), k;
        if (!G.spans.length) return;
        var S = tr1_shapeOn(ctx, L, 'JZ Strike', true, HD), an = (P.ang || 0) * Math.PI / 180, ca = Math.cos(an), sa = Math.sin(an);
        for (k = 0; k < G.spans.length; k++) {
            var Sp = G.spans[k], a0 = Sp.a0 - s * 0.08, a1 = Sp.a1 + s * 0.08, th = Math.max(2 * ctx.u, s * (P.k || 0.07)), mid = (Sp.a0 + Sp.a1) / 2;
            var cx = Sp.vert ? Sp.c : mid, cy = Sp.vert ? mid : Sp.c + s * 0.02;
            // point u along the line, d across it, rotated by ang about the line's middle
            var pt = (function (vert, cx0, cy0, mid0) { return function (u, d) { var x = vert ? d : u - mid0, y = vert ? u - mid0 : d; return [cx0 + x * ca - y * sa, cy0 + x * sa + y * ca]; }; })(Sp.vert, cx, cy, mid);
            var g = jzGrp(S, 'strike ' + (k + 1));
            if (P.v === 'two') { jzAddPath(g, [pt(a0, -th * 0.8), pt(a1, -th * 0.8)], false); jzAddPath(g, [pt(a0, th * 0.8), pt(a1, th * 0.8)], false); jzAddStroke(g, lc, th * 0.6); }
            else { jzAddPath(g, [pt(a0, 0), pt(a1, 0)], false); jzAddStroke(g, lc, th); }
            var H2 = HD + 'var q=oe(cl((time-DL-IN*0.7-' + jzN(k * 0.1) + ')/0.3)),o=ic(PO);';
            jzAddTrimPaths(g, H2 + 'Math.max(q,o)*100', H2 + 'o*100');
        }
    }
});

/* ---- boxed \u2014 \u7BB1\u7D44: one box per glyph pops in behind it (solid / alternating / frame); glyphs on solid boxes flip colour */
jzReg('treat', 'boxed', {
    plan: function (rng, st) { return { v: rng.pick(['solid', 'solid', 'alt', 'frame']) }; },
    apply: function (ctx, L, P, o) {
        if (!tr1_alive(L, 0.9)) return;
        var sc = ctx.sc, col = jzTextColor(L), frame = P.v === 'frame', off = (o && o.mi) | 0, k;
        var boxA = tr1_first([sc.ink, sc.fg, sc.accent], function (c) { return jzContrast(c, sc.bg) >= 2.2; }, sc.fg);
        var boxB = P.v === 'alt' ? tr1_first([sc.accent, sc.accent2], function (c) { return jzContrast(c, boxA) >= 1.5 && jzContrast(c, sc.bg) >= 1.8; }, boxA) : boxA;
        var tcA = frame ? col : tr1_textOn(boxA, sc.bg, sc), tcB = frame ? col : tr1_textOn(boxB, sc.bg, sc);
        var G = tr1_lay(ctx, L), s = G.fs, HD = tr1_head(ctx, L, o);
        if (!G.glyphs.length) return;
        var Q = function (gi) { return 'var q=ob(cl((time-DL-' + gi + '*0.035)/0.24),1.7)*(1-ic(cl(PO*1.4-' + gi + '*0.03)));'; };
        var alt = function (gi) { return P.v === 'alt' && (gi + off) % 2 === 1; };
        if (!frame) {
            jzAnimator(L, 'JZ Boxed Text', [['ADBE Text Fill Color', jzHex(tcA)]], HD + Q('(textIndex-1)') + 'q>0.55?100:0');
            if (P.v === 'alt' && tcB !== tcA) jzAnimator(L, 'JZ Boxed Text B', [['ADBE Text Fill Color', jzHex(tcB)]], HD + Q('(textIndex-1)') + '(q>0.55&&(textIndex-1+' + off + ')%2===1)?100:0');
        }
        var S = tr1_shapeOn(ctx, L, 'JZ Boxes', false, HD);
        for (k = 0; k < G.glyphs.length; k++) {
            var gl = G.glyphs[k], w = G.vert ? s * 1.02 : Math.max(gl.w * 0.94, s * 0.42), h = G.vert ? Math.max(gl.h * 0.94, s * 0.42) : s * 1.02;
            var g = jzGrp(S, 'box ' + (gl.i + 1)), c = alt(gl.i) ? boxB : boxA;
            jzAddRect(g, w, h);
            if (frame) jzAddStroke(g, c, Math.max(1.5 * ctx.u, s * 0.035)); else jzAddFill(g, c);
            var X = jzGX(g);
            X.property('ADBE Vector Position').setValue([gl.cx, gl.cy]);
            X.property('ADBE Vector Scale').expression = HD + Q(gl.i) + '[100*q,100*q]';
        }
    }
});

// ================================================================ fills (gradient / two colours / patterns)
// one row band (layer px, y0..y1) of a fill layer: a flat rect in one colour
function tr1_band(F, name, cx, w, y0, y1, hex) { if (y1 - y0 < 0.01) return; var g = jzGrp(F, name); jzAddRect(g, w, y1 - y0, 0, cx, (y0 + y1) / 2); jzAddFill(g, hex); }
// row limits: each row owns the space up to half way to its neighbours; the first / last rows reach far out
function tr1_rowLim(G, i, far) { var R = G.rows; return [i === 0 ? G.box.y0 - far : (R[i - 1].cy + R[i].cy) / 2, i === R.length - 1 ? G.box.y1 + far : (R[i].cy + R[i + 1].cy) / 2]; }
function tr1_ramp(L, x, y0, c0, y1, c1) {
    var e = jzEffect(L, 'ADBE Ramp', 'JZ Gradient');
    jzEP(e, 1, [x, y0]); jzEP(e, 2, jzHex(c0)); jzEP(e, 3, [x, y1]); jzEP(e, 4, jzHex(c1)); jzEP(e, 5, 1);
    return e;
}
/* ---- gradientV \u2014 \u7E26\u30B0\u30E9\u30C7: vertical gradient over each glyph row (one row: Gradient Ramp on the lyric; several rows: banded fill layer through the lyric) */
jzReg('treat', 'gradientV', {
    plan: function (rng, st) { return { up: rng.chance(0.4), g: rng.chance(0.5) }; },
    apply: function (ctx, L, P, o) {
        if (!tr1_alive(L)) return;
        var sc = ctx.sc, col = jzTextColor(L), i, k;
        var g = P.g && sc.grad && sc.grad.length > 1 && jzContrast(sc.grad[0], sc.bg) >= 1.6 && jzContrast(sc.grad[1], sc.bg) >= 1.6 ? [sc.grad[0], sc.grad[1]] : [col, tr1_twoTone(sc, col)];
        if (P.up) g = [g[1], g[0]];
        var G = tr1_lay(ctx, L), s = G.fs, R = G.rows;
        if (!R.length) return;
        if (R.length === 1) { tr1_ramp(L, G.box.cx, R[0].cy - s * 0.5, g[0], R[0].cy + s * 0.5, g[1]); return; }
        var far = tr1_far(ctx, L), F = tr1_fillLayer(ctx, L, 'JZ Gradient fill'), nb = 12, bh = s / nb;
        for (i = 0; i < R.length; i++) {
            var lim = tr1_rowLim(G, i, far), y0 = R[i].cy - s * 0.5, y1 = R[i].cy + s * 0.5;
            tr1_band(F, 'row ' + (i + 1) + ' top', G.box.cx, far * 2, lim[0], Math.max(lim[0], y0), g[0]);
            for (k = 0; k < nb; k++) tr1_band(F, 'row ' + (i + 1) + ' ' + (k + 1), G.box.cx, far * 2, Math.max(lim[0], y0 + k * bh), Math.min(lim[1], y0 + (k + 1) * bh + 0.5), jzMixHex(g[0], g[1], (k + 0.5) / nb));
            tr1_band(F, 'row ' + (i + 1) + ' bottom', G.box.cx, far * 2, Math.min(lim[1], y1), lim[1], g[1]);
        }
        var gb = jzEffect(F, 'ADBE Gaussian Blur 2', 'JZ Gradient smooth'); jzEP(gb, 1, bh * 1.6 * tr1_k(L)); jzEP(gb, 2, 3); jzEP(gb, 3, 1);
    }
});

/* ---- splitColor \u2014 \u4E0A\u4E0B\u4E8C\u8272: hard two-colour split across every glyph row (one row: hard Gradient Ramp; several rows: fill layer through the lyric) */
jzReg('treat', 'splitColor', {
    plan: function (rng, st) { return { sp: rng.range(0.5, 0.57), top: rng.chance(0.35) }; },
    apply: function (ctx, L, P, o) {
        if (!tr1_alive(L)) return;
        var sc = ctx.sc, col = jzTextColor(L), c2 = tr1_twoTone(sc, col), a = P.top ? c2 : col, b = P.top ? col : c2, i;
        var G = tr1_lay(ctx, L), s = G.fs, R = G.rows, sp = P.sp == null ? 0.53 : P.sp, hw = 0.5 / tr1_k(L);
        if (!R.length) return;
        if (R.length === 1) { var y = R[0].cy - s * 0.5 + s * sp; tr1_ramp(L, G.box.cx, y - hw, a, y + hw, b); return; }
        var far = tr1_far(ctx, L), F = tr1_fillLayer(ctx, L, 'JZ Split fill');
        for (i = 0; i < R.length; i++) {
            var lim = tr1_rowLim(G, i, far), ys = jzClamp(R[i].cy - s * 0.5 + s * sp, lim[0], lim[1]);
            tr1_band(F, 'row ' + (i + 1) + ' upper', G.box.cx, far * 2, lim[0], ys, a);
            tr1_band(F, 'row ' + (i + 1) + ' lower', G.box.cx, far * 2, ys, lim[1], b);
        }
    }
});

// ---- fill patterns: shape layer in the lyric's layer space, shown through the lyric's glyphs (the lyric is its alpha track matte)
function tr1_rep(g, n, dx, dy) {
    var r = jzVecs(g).addProperty('ADBE Vector Filter - Repeater');
    r.property('ADBE Vector Repeater Copies').setValue(Math.max(1, n));
    r.property('ADBE Vector Repeater Transform').property('ADBE Vector Repeater Position').setValue([dx, dy]);
    return r;
}
function tr1_sub(g, name) { var s = jzVecs(g).addProperty('ADBE Vector Group'); s.name = name; return s; }
// parallel bands (thickness th, spacing sp) covering the box, rotated by rot degrees about its centre
function tr1_bandsShape(S, name, bx, th, sp, rot, hex) {
    var D = Math.sqrt(Math.pow(bx.x1 - bx.x0, 2) + Math.pow(bx.y1 - bx.y0, 2)), n = Math.ceil(D / sp) + 1;
    var g = jzGrp(S, name), inner = tr1_sub(g, name + ' band');
    jzAddRect(inner, D, th, 0, 0, -D / 2); jzAddFill(inner, hex); tr1_rep(inner, n, 0, sp);
    jzGX(g).property('ADBE Vector Position').setValue([(bx.x0 + bx.x1) / 2, (bx.y0 + bx.y1) / 2]);
    if (rot) jzGX(g).property('ADBE Vector Rotation').setValue(rot);
    return g;
}
function tr1_patternShape(ctx, S, kind, hex, s, bx) {
    var w = bx.x1 - bx.x0, h = bx.y1 - bx.y0, c, nx, ny;
    if (kind === 'dots') {
        c = Math.max(3 * ctx.u, s * 0.075); nx = Math.ceil(w / c) + 1; ny = Math.ceil(h / c) + 1;
        if (nx * ny > 6000) { c *= Math.sqrt(nx * ny / 6000); nx = Math.ceil(w / c) + 1; ny = Math.ceil(h / c) + 1; }
        var g = jzGrp(S, 'dots'), row = tr1_sub(g, 'dot row');
        jzAddEllipse(row, c * 0.68, c * 0.68, bx.x0, bx.y0); jzAddFill(row, hex); tr1_rep(row, nx, c, 0);
        tr1_rep(g, ny, 0, c);
    } else if (kind === 'lines') {
        c = Math.max(3 * ctx.u, s * 0.06);
        tr1_bandsShape(S, 'lines', bx, c * 0.45, c, 0, hex);
    } else if (kind === 'hatch') {
        c = Math.max(3 * ctx.u, s * 0.06);
        tr1_bandsShape(S, 'hatch a', bx, Math.max(ctx.u, c * 0.16), c / Math.SQRT2, 45, hex);
        tr1_bandsShape(S, 'hatch b', bx, Math.max(ctx.u, c * 0.16), c / Math.SQRT2, -45, hex);
    } else {                                                            // 'stripes': diagonal "/" bands
        c = Math.max(3 * ctx.u, s * 0.06);
        tr1_bandsShape(S, 'stripes', bx, c * 0.38, c / Math.SQRT2, -45, hex);
    }
}
function tr1_pattern(ctx, L, P, kind, alt) {
    if (!tr1_alive(L)) return;
    var sc = ctx.sc, col = jzTextColor(L), pk = P.v === 'lines' && alt ? alt : kind, duo = P.v === 'duo';
    var bgc = duo ? tr1_accentFor(sc, col, 1.6) : jzMixHex(col, sc.bg, 0.62);
    var G = tr1_lay(ctx, L), s = G.fs, m = s * 0.6, bx = { x0: G.box.x0 - m, x1: G.box.x1 + m, y0: G.box.y0 - m, y1: G.box.y1 + m }, far = tr1_far(ctx, L);
    // 'tone': outline in the text colour on top \u2014 a stroke-only copy, only when the motion has no per-layer randomness
    // (a duplicate would move its glyphs differently); otherwise the glyphs stay tint + pattern without the thin outline
    if (!duo && !tr1_rnd(L)) { var D = tr1_copy(L, 'JZ Pattern outline', L, col, Math.max(1.2 * ctx.u / tr1_k(L), s * 0.018), 0, 0, false); D.moveBefore(L); }
    // pattern + base colour, seen through the lyric's glyphs (first groups draw on top)
    var F = tr1_fillLayer(ctx, L, 'JZ Pattern ' + pk);
    tr1_patternShape(ctx, F, pk, col, s, bx);
    var g = jzGrp(F, 'base'); jzAddRect(g, far * 2, far * 2, 0, G.box.cx, G.box.cy); jzAddFill(g, bgc);
}
/* ---- halftone \u2014 \u7DB2\u70B9 / stripes \u2014 \u30B9\u30C8\u30E9\u30A4\u30D7 / hatch \u2014 \u659C\u7DDA: pattern-filled glyphs (tint + outline, or on an accent) */
jzReg('treat', 'halftone', { plan: function (rng, st) { return { v: rng.pick(['tone', 'tone', 'duo']) }; }, apply: function (ctx, L, P, o) { tr1_pattern(ctx, L, P, 'dots'); } });
jzReg('treat', 'stripes', { plan: function (rng, st) { return { v: rng.pick(['tone', 'duo', 'lines']) }; }, apply: function (ctx, L, P, o) { tr1_pattern(ctx, L, P, 'stripes', 'lines'); } });
jzReg('treat', 'hatch', { plan: function (rng, st) { return { v: rng.pick(['tone', 'tone', 'duo']) }; }, apply: function (ctx, L, P, o) { tr1_pattern(ctx, L, P, 'hatch'); } });

/* ---- dotted \u2014 \u70B9\u7DDA\u8F2A\u90ED: outline-only letters broken into dashes (diagonal bands as the lyric's track matte; they crawl when spd) */
jzReg('treat', 'dotted', {
    plan: function (rng, st) { return { k: rng.range(0.03, 0.04), spd: rng.pick([0, 1, 1]) }; },
    apply: function (ctx, L, P, o) {
        if (!tr1_alive(L, 0.5)) return;
        var col = jzTextColor(L), G = tr1_lay(ctx, L), s = G.fs, m = s * 1.5;
        jzTextDoc(L, function (td) {
            td.applyFill = false; td.applyStroke = true; td.strokeColor = jzHex(col);
            td.strokeWidth = Math.max(1.6 * ctx.u, s * (P.k || 0.035));
        });
        // dashes: diagonal bands (dash 0.05, gap 0.036 of the size along horizontal / vertical strokes) used as the lyric's alpha matte
        var bx = { x0: G.box.x0 - m, x1: G.box.x1 + m, y0: G.box.y0 - m, y1: G.box.y1 + m }, per = Math.max(3 * ctx.u, s * 0.086) / Math.SQRT2;
        var S = tr1_shapeOn(ctx, L, 'JZ Dash matte', true, false), g = tr1_bandsShape(S, 'dashes', bx, per * 0.58, per, -45, '#FFFFFF');
        if (P.spd) jzGX(g.property('ADBE Vectors Group').property(1)).property('ADBE Vector Position').expression = 'var C=' + jzN(per) + ',V=' + jzN(s * 0.22 / Math.SQRT2) + ';var X=((time*V)%C+C)%C;[value[0],value[1]+X]';
        tr1_matte(S, L);
    }
});

/* ---- alternate \u2014 \u4EA4\u4E92\u8272: every other glyph (or the kanji / katakana of a mixed line) in the accent colour */
jzReg('treat', 'alternate', {
    plan: function (rng, st) { return { v: rng.pick(['alt', 'alt', 'kanji']) }; },
    apply: function (ctx, L, P, o) {
        if (!tr1_alive(L)) return;
        var sc = ctx.sc, col = jzTextColor(L), c2 = tr1_accentFor(sc, col, 1.6), gl = jzGlyphs(tr1_doc(L).text), off = (o && o.mi) | 0, nK = 0, nO = 0, i, flags = [];
        var isK = function (c) { return jzIsKanji(c) || jzIsKata(c); };
        for (i = 0; i < gl.length; i++) { if (/[\s\u3000]/.test(gl[i])) continue; if (isK(gl[i])) nK++; else nO++; }
        var byK = P.v === 'kanji' && ((nK > 0 && nO > 0) || nK + nO === 1);
        for (i = 0; i < gl.length; i++) flags.push(byK ? isK(gl[i]) : (i + off) % 2 === 1);
        jzCharColors(L, c2, flags, 'JZ Alternate');
    }
});

// ================================================================ shape of the letters
/* ---- italic \u2014 \u659C\u4F53: the whole block slanted (Transform effect skew about the text centre); vertical text tilts each glyph */
jzReg('treat', 'italic', {
    plan: function (rng, st) { return { a: rng.range(10, 15) }; },
    apply: function (ctx, L, P, o) {
        var a = P.a == null ? 12 : P.a;
        if (!jzTrim(String(tr1_doc(L).text || ''))) return;
        if (tr1_isVert(L)) { jzAnimator(L, 'JZ Italic', [['ADBE Text Rotation', a * 0.65]], '100'); return; }
        var bx = tr1_lay(ctx, L).box, c = [bx.cx, bx.cy], tf = jzEffect(L, 'ADBE Geometry2', 'JZ Italic');
        jzEP(tf, 1, c); jzEP(tf, 2, c); jzEP(tf, 6, a); jzEP(tf, 7, 0);
    }
});

/* ---- wide \u2014 \u5E73\u4F53 / tall \u2014 \u9577\u4F53: the block squashed / stretched (layer scale; the motion expressions multiply it) */
function tr1_rescale(L, fx, fy) { var s = tr1_sv(L, 'ADBE Scale'); jzXf(L, 'ADBE Scale').setValue([s[0] * fx, s[1] * fy]); }
jzReg('treat', 'wide', {
    plan: function (rng, st) { return {}; },
    apply: function (ctx, L, P, o) {
        if (!jzTrim(String(tr1_doc(L).text || ''))) return;
        var fx = 1, fy = 0.84;
        if (!tr1_isVert(L)) {           // keep lines that fitted the frame inside it
            var bx = tr1_lay(ctx, L).box, w0 = (bx.x1 - bx.x0) * Math.abs(tr1_sv(L, 'ADBE Scale')[0]) / 100, lim = ctx.W * 0.9;
            fx = 1.1;
            if (w0 <= lim && w0 * 1.1 > lim) { var k = lim / (w0 * 1.1); fx *= k; fy *= k; }
        }
        tr1_rescale(L, fx, fy);
    }
});
jzReg('treat', 'tall', {
    plan: function (rng, st) { return {}; },
    apply: function (ctx, L, P, o) {
        if (!jzTrim(String(tr1_doc(L).text || ''))) return;
        if (tr1_isVert(L)) tr1_rescale(L, 0.8, 1.02);
        else tr1_rescale(L, 0.8 * 1.05, 1.05 * 1.05);
    }
});

/* ---- echoOutline \u2014 \u8F2A\u90ED\u306E\u6B8B\u97FF: outline copies stepping away behind the lyric (diagonal / zoom / rise / side), breathing */
jzReg('treat', 'echoOutline', {
    plan: function (rng, st) { return { v: rng.pick(['diag', 'diag', 'zoom', 'rise', 'side']), n: rng.int(3, 4), d: rng.range(0.045, 0.07) }; },
    apply: function (ctx, L, P, o) {
        if (!tr1_alive(L)) return;
        var c = tr1_accentFor(ctx.sc, jzTextColor(L), 1.4), s = jzFontSize(L), HD = tr1_head(ctx, L, o), bx = tr1_lay(ctx, L).box, k;
        var n = tr1_budget(ctx, P.n || 3), d = s * (P.d || 0.055), dx = 0, dy = 0, zoom = P.v === 'zoom';
        if (P.v === 'rise') dy = -d * 1.2; else if (P.v === 'side') dx = d * 1.3; else if (!zoom) { dx = d; dy = d; }
        var ctr = [bx.cx, bx.cy], prev = L, op = tr1_sv(L, 'ADBE Opacity');
        // copies are duplicates: if the motion uses per-layer randomness their glyphs would stray while moving, so the
        // echo then opens after the entrance and folds away at the start of the exit
        var rnd = tr1_rnd(L), t0 = rnd ? 'IN*1.05' : '0.05';
        var A = HD + 'var A=oc(cl((time-DL-' + t0 + ')/0.45))*(1-ic(PO))*(0.85+0.15*Math.sin(time*3.4))' + (rnd ? '*(1-cl(PO*4))' : '') + ';';
        for (k = 1; k <= n; k++) {
            var D = tr1_copy(L, 'JZ Echo ' + k, prev, c, Math.max(ctx.u, s * 0.012), 0, 0, false);
            jzXf(D, 'ADBE Opacity').setValue(op * 0.8 * Math.pow(0.72, k - 1));
            var tf = jzEffect(D, 'ADBE Geometry2', 'JZ Echo Step');
            jzEP(tf, 1, ctr);
            jzEX(tf, 2, A + '[' + jzN(ctr[0]) + '+' + jzN(dx * k) + '*A,' + jzN(ctr[1]) + '+' + jzN(dy * k) + '*A]');
            if (zoom) jzEX(tf, 4, A + '100*Math.pow(1+0.035*A,' + k + ')');
            jzEX(tf, 9, A + '(A>0.01?100:0)');
            prev = D;
        }
    }
});

/* ---- emphasisDots \u2014 \u508D\u70B9: a dot (sesame / ring) above every glyph (right of it in vertical text), popping in one by one */
jzReg('treat', 'emphasisDots', {
    plan: function (rng, st) { return { v: rng.pick(['dot', 'dot', 'sesame', 'ring']) }; },
    apply: function (ctx, L, P, o) {
        if (!tr1_alive(L, 0.9)) return;
        var dc = tr1_markCol(ctx.sc, jzTextColor(L)), G = tr1_lay(ctx, L), s = G.fs, HD = tr1_head(ctx, L, o), r = s * 0.07, k, n = 0;
        var S = jzNoGhost(tr1_shapeOn(ctx, L, 'JZ Emphasis Dots', true, HD));      // drawn on the main pass only in the browser
        for (k = 0; k < G.glyphs.length; k++) {
            var gl = G.glyphs[k];
            if (jzIsPunct(gl.ch) || gl.ch === '\u30FC') continue;
            var g = jzGrp(S, 'dot ' + (gl.i + 1)), X = jzGX(g);
            if (P.v === 'ring') { jzAddEllipse(g, r * 1.7, r * 1.7); jzAddStroke(g, dc, Math.max(ctx.u, r * 0.42)); }
            else if (P.v === 'sesame') { jzAddEllipse(g, r * 2.5, r * 1.2); jzAddFill(g, dc); X.property('ADBE Vector Rotation').setValue(-40); }
            else { jzAddEllipse(g, r * 2, r * 2); jzAddFill(g, dc); }
            X.property('ADBE Vector Position').setValue(G.vert ? [gl.cx + s * 0.64, gl.cy] : [gl.cx, gl.cy - s * 0.64]);
            X.property('ADBE Vector Scale').expression = HD + 'var q=ob(cl((time-DL-IN*0.45-' + jzN(gl.i * 0.04) + ')/0.22),2.2)*(1-ic(PO));[100*q,100*q]';
            n++;
        }
        if (!n) S.remove();
    }
});

// ================================================================ text treatments part 2 (AE port of the treat entries in src/11p_treattrans.js)
// treat.apply(ctx, L, P, o) runs for every MAIN lyric layer after its motion is set. P = the cut's treatP (browser plan keys).
// Tools used here (same ideas as part 1, own tr2_ helpers):
//  - effects on the lyric itself (Drop Shadows for glows / rings / flat offsets, Wave Warp, Transform) \u2014 they follow every
//    per-glyph motion exactly;
//  - text animators with Expression Selectors for per-glyph colour / size / tilt / blur / opacity;
//  - shape layers parented to the lyric (its layer space) for marks (rings, plates, brackets, grid lines);
//  - the lyric (or a filled copy) as the alpha track matte of a fill layer for multi-stop / animated fills;
//  - duplicates of the lyric (L.duplicate() keeps text, animators, effects and motion) for copies drawn with another
//    style. AE's random() / wiggle() depend on the layer, so when the motion uses them (tr2_rnd) a duplicate is only
//    shown while the glyphs rest (after the entrance, before the exit) or a duplicate-free fallback is used.
// Layers the browser draws on the main pass only (copies, paper, grid, brackets, cut line, liquid, tone) are marked
// jzNoGhost so the tinted ghost copies of the content comp leave them out; effects on the lyric itself stay in the ghosts.

// ---------------------------------------------------------------- shared helpers (tr2_)
function tr2_p(P, k, d) { return (P && P[k] != null) ? P[k] : d; }
function tr2_first(list, test, fb) { for (var i = 0; i < list.length; i++) if (list[i] && test(list[i])) return list[i]; return fb; }
function tr2_best(list, against) { var b = list[0], bv = -1; for (var i = 0; i < list.length; i++) { if (!list[i]) continue; var v = jzContrast(list[i], against); if (v > bv) { bv = v; b = list[i]; } } return b; }
function tr2_dark(c) { return jzLum(c) < 0.45; }
function tr2_accentFor(sc, col, min) {
    if (min == null) min = 1.6;
    return tr2_first([sc.accent, sc.accent2, sc.ghostA, sc.ghostB], function (c) { return jzContrast(c, col) >= min && jzContrast(c, sc.bg) >= 1.5; }, jzFitContrast(sc.accent, col, min + 0.3));
}
function tr2_textOn(box, pref, sc) { return jzContrast(pref, box) >= 3 ? pref : tr2_best([sc.bg, sc.fg, sc.ink, '#111111', '#FFFFFF'], box); }
function tr2_markCol(sc, col) { return tr2_first([sc.accent, sc.accent2, col], function (c) { return jzContrast(c, sc.bg) >= 2; }, col); }
function tr2_onPlate(sc, col) { return jzContrast(col, sc.bg) < 1.5; }
function tr2_isSp(ch) { return /^[\s\u3000]$/.test(ch); }

function tr2_sv(L, mn) { var p = jzXf(L, mn); try { return p.valueAtTime(0, true); } catch (e) { return p.value; } }
function tr2_doc(L) { return L.property('ADBE Text Properties').property('ADBE Text Document').value; }
function tr2_alive(L, amin) {
    var td; try { td = tr2_doc(L); } catch (e) { return false; }
    if (!td || td.applyFill === false || !jzTrim(String(td.text || '')) || !(td.fontSize > 1)) return false;
    return tr2_sv(L, 'ADBE Opacity') >= (amin == null ? 0.9 : amin) * 100 - 0.01;
}
function tr2_head(ctx, L, o) { return jzHead(ctx, { mi: (o && o.mi) || 0, size: jzFontSize(L) }); }
function tr2_mi(o) { return (o && o.mi) | 0; }
function tr2_k(L) { var s = tr2_sv(L, 'ADBE Scale'); return Math.max(0.01, (Math.abs(s[0]) + Math.abs(s[1])) / 200); }
function tr2_many(ctx) { return ctx.comp.numLayers > 30; }
function tr2_budget(ctx, want) { var n = ctx.comp.numLayers; return n > 30 ? Math.min(want, 2) : (n > 16 ? Math.min(want, 3) : want); }
function tr2_arr(a) { var s = []; for (var i = 0; i < a.length; i++) s.push(a[i] == null ? 'null' : jzN(a[i])); return '[' + s.join(',') + ']'; }
// does the lyric's motion use per-layer randomness (random / wiggle)? then duplicates stray while the glyphs move
function tr2_rnd(L) {
    var re = /random|wiggle/, i, j, an, fx, mns = ['ADBE Position', 'ADBE Scale', 'ADBE Rotate Z', 'ADBE Opacity'];
    var chk = function (p) { try { return !!(p && p.expression && re.test(p.expression)); } catch (e) { return false; } };
    try {
        an = L.property('ADBE Text Properties').property('ADBE Text Animators');
        for (i = 1; i <= an.numProperties; i++) {
            var sels = an.property(i).property('ADBE Text Selectors'), pr = an.property(i).property('ADBE Text Animator Properties');
            for (j = 1; j <= sels.numProperties; j++) if (sels.property(j).matchName === 'ADBE Text Expressible Selector' && chk(sels.property(j).property('ADBE Text Expressible Amount'))) return true;
            for (j = 1; j <= pr.numProperties; j++) if (chk(pr.property(j))) return true;
        }
        fx = L.property('ADBE Effect Parade');
        for (i = 1; i <= fx.numProperties; i++) for (j = 1; j <= fx.property(i).numProperties; j++) if (chk(fx.property(i).property(j))) return true;
        for (i = 0; i < mns.length; i++) if (chk(jzXf(L, mns[i]))) return true;
    } catch (e2) { return true; }
    return false;
}
// "glyphs at rest" factor (after the entrance, before the exit) for duplicates of a randomly moving lyric
var TR2_REST = 'cl((time-DL-IN*1.02)/0.15)*(1-cl(PO*6))';
function tr2_gate(D, HD) { var tf = jzEffect(D, 'ADBE Geometry2', 'JZ At rest'); jzEX(tf, 9, HD + '100*' + TR2_REST); return tf; }

function tr2_dup(L, name, after) { var D = L.duplicate(); try { D.name = name; } catch (e) {} D.moveAfter(after || L); return D; }
function tr2_recolor(D, hex) {
    var an = D.property('ADBE Text Properties').property('ADBE Text Animators'), i, c = jzHex(hex);
    for (i = 1; i <= an.numProperties; i++) {
        var pr = an.property(i).property('ADBE Text Animator Properties'), f = pr.property('ADBE Text Fill Color'), s = pr.property('ADBE Text Stroke Color');
        if (f) f.setValue(c);
        if (s) s.setValue(c);
    }
}
// copy of L: fill `hex` (fill=false: none), stroke `stroke` px in `shex` (default hex, under the fill), shifted (dx, dy) in its own frame
function tr2_copy(L, name, after, hex, stroke, dx, dy, fill, shex) {
    var D = tr2_dup(L, name, after);
    jzTextDoc(D, function (td) {
        td.applyFill = fill !== false; if (td.applyFill) td.fillColor = jzHex(hex);
        if (stroke > 0) { td.applyStroke = true; td.strokeColor = jzHex(shex || hex); td.strokeWidth = stroke; try { td.strokeOverFill = false; } catch (e) {} }
        else td.applyStroke = false;
    });
    tr2_recolor(D, hex);
    if (dx || dy) tr2_shift(D, dx, dy);
    return D;
}
// move a layer's content by (dx, dy) layer px through its anchor point (the offset scales / rotates with the layer)
function tr2_shift(D, dx, dy) {
    var a = tr2_sv(D, 'ADBE Anchor Point');
    jzXf(D, 'ADBE Anchor Point').setValue([a[0] - (dx || 0), a[1] - (dy || 0)]);
}
// Drop Shadow on L (offset in layer px; direction from the vector)
function tr2_shadow(L, name, hex, dx, dy, soft, alpha) {
    var e = jzEffect(L, 'ADBE Drop Shadow', name);
    jzEP(e, 1, jzHex(hex)); jzEP(e, 2, Math.round(jzClamp(alpha == null ? 1 : alpha, 0, 1) * 255));
    jzEP(e, 3, (dx || dy) ? ((Math.atan2(dx, -dy) * 180 / Math.PI) + 360) % 360 : 135); jzEP(e, 4, Math.sqrt(dx * dx + dy * dy));
    if ((soft || 0) <= 250) jzEP(e, 5, soft || 0); else jzEX(e, 5, jzN(soft));
    return e;
}
// outer ring of radius r (layer px) grown from the layer's alpha: 8 stacked hard shadows at 45deg steps (octagon zonotope)
function tr2_ring(L, name, hex, r) {
    var d = Math.max(0.3, r) * 0.4142, i;
    for (i = 0; i < 8; i++) tr2_shadow(L, name + ' ' + (i + 1), hex, Math.cos(i * Math.PI / 4) * d, Math.sin(i * Math.PI / 4) * d, 0, 1);
}
// alpha track matte: F shows only inside matte M's alpha (inv: outside it); M hidden
function tr2_matte(M, F, inv) {
    F.moveAfter(M);
    var ok = false, tp = inv ? TrackMatteType.ALPHA_INVERTED : TrackMatteType.ALPHA;
    if (typeof F.setTrackMatte === 'function') { try { F.setTrackMatte(M, tp); ok = true; } catch (e) { ok = false; } }
    if (!ok) F.trackMatteType = tp;
    M.enabled = false;
}
// shape layer in L's layer space (parented, identity transform); opx: opacity expression (null = none)
function tr2_shapeOn(ctx, L, name, above, opx) {
    var S = ctx.comp.layers.addShape(); S.name = name;
    S.parent = L;
    jzXf(S, 'ADBE Anchor Point').setValue([0, 0]); jzXf(S, 'ADBE Position').setValue([0, 0]);
    jzXf(S, 'ADBE Scale').setValue([100, 100]); jzXf(S, 'ADBE Rotate Z').setValue(0);
    if (above) S.moveBefore(L); else S.moveAfter(L);
    if (opx) jzSetExpr(jzXf(S, 'ADBE Opacity'), opx);
    return S;
}
var TR2_POP = 'value*(hasParent?parent.transform.opacity/100:1)';
function tr2_far(ctx, L) { return (ctx.W + ctx.H) * 2 / tr2_k(L); }
// keep the anchor at the same relative spot of the text box while fn() changes the text document
function tr2_reanchor(L, fn) {
    var r0 = jzRect(L), a = tr2_sv(L, 'ADBE Anchor Point');
    var fx = r0.width > 0.5 ? (a[0] - r0.left) / r0.width : 0.5, fy = r0.height > 0.5 ? (a[1] - r0.top) / r0.height : 0.5;
    fn();
    var r1 = jzRect(L);
    jzXf(L, 'ADBE Anchor Point').setValue([r1.left + fx * r1.width, r1.top + fy * r1.height]);
}

// ---- text geometry in the layer's own space
function tr2_rectOf(ctx, L, str) {
    var td = tr2_doc(L), P = ctx.comp.layers.addText(str), r = null;
    try {
        td.text = str; td.applyStroke = false;
        P.property('ADBE Text Properties').property('ADBE Text Document').setValue(td);
        r = P.sourceRectAtTime(0, false);
    } catch (e) { r = null; }
    try { P.remove(); } catch (e2) {}
    if (!r || !(r.width >= 0)) { var n = jzChars(str).length; r = { left: -td.fontSize * n / 2, top: -td.fontSize * 0.88, width: td.fontSize * n, height: td.fontSize }; }
    return { left: r.left, top: r.top, width: r.width, height: r.height };
}
function tr2_adv(ch) {
    var c = ch.charCodeAt(0);
    if (ch === '\u3000' || c >= 0x2E80 || (c >= 0xFF01 && c <= 0xFF60)) return 1;
    if (ch === ' ') return 0.3;
    if (/[MWmw@%]/.test(ch)) return 0.86;
    if (/[A-Z]/.test(ch)) return 0.66;
    if (/[a-z0-9]/.test(ch)) return 0.55;
    return 0.34;
}
// rows with every character (spaces too: ci / n like the browser's layout), glyph centres (textIndex order), line spans
// (one per row; one per column for vertical text = one glyph per row), and the glyph box
function tr2_lay(ctx, L) {
    var td = tr2_doc(L), fs = td.fontSize, lead = (td.autoLeading === false && td.leading > 0) ? td.leading : fs * 1.2;
    var trk = (td.tracking || 0) / 1000 * fs, just = td.justification;
    var LEFT = ParagraphJustification.LEFT_JUSTIFY, RIGHT = ParagraphJustification.RIGHT_JUSTIFY;
    var rowsT = String(td.text).split(/\r\n|\r|\n/), rows = [], glyphs = [], chars = [], gi = 0, vert = rowsT.length > 1, i, k;
    for (i = 0; i < rowsT.length; i++) {
        var cs = jzChars(rowsT[i]), n = cs.length, vis = 0;
        for (k = 0; k < n; k++) if (!tr2_isSp(cs[k])) vis++;
        if (vis > 1) vert = false;
        if (!vis) { gi += n; continue; }
        var r = tr2_rectOf(ctx, L, rowsT[i]), adv = [], tot = 0;
        for (k = 0; k < n; k++) { adv.push(tr2_adv(cs[k]) * fs); tot += adv[k] + (k < n - 1 ? trk : 0); }
        var kf = r.width > 1 && tot > 1 ? r.width / tot : 1; if (kf < 0.8 || kf > 1.25) kf = 1;
        var w = tot * kf, x0 = just === LEFT ? r.left : (just === RIGHT ? r.left + r.width - w : r.left + r.width / 2 - w / 2);
        var base = i * lead, cy = base - 0.38 * fs, x = x0, row = { li: i, x0: x0, x1: x0 + w, cy: cy, base: base, g: [], c: [], trk: trk * kf };
        for (k = 0; k < n; k++) {
            var gd = { ch: cs[k], i: gi + k, row: i, ci: k, n: n, cx: x + adv[k] * kf / 2, cy: cy, w: adv[k] * kf, h: fs, sp: tr2_isSp(cs[k]) };
            row.c.push(gd); chars[gd.i] = gd;
            if (!gd.sp) { glyphs.push(gd); row.g.push(gd); }
            x += (adv[k] + trk) * kf;
        }
        rows.push(row); gi += n;
    }
    var spans = [];
    if (vert && glyphs.length > 1) {
        var cx = 0; for (k = 0; k < glyphs.length; k++) { cx += glyphs[k].cx; glyphs[k].w = fs; glyphs[k].h = Math.min(fs, lead); }
        spans.push({ vert: true, a0: glyphs[0].cy - fs / 2, a1: glyphs[glyphs.length - 1].cy + fs / 2, c: cx / glyphs.length, g: glyphs.slice(0) });
    } else {
        vert = false;
        for (i = 0; i < rows.length; i++) spans.push({ vert: false, a0: rows[i].x0, a1: rows[i].x1, c: rows[i].cy, g: rows[i].g });
    }
    var bx0 = 1e9, bx1 = -1e9, by0 = 1e9, by1 = -1e9;
    for (k = 0; k < glyphs.length; k++) { var G = glyphs[k]; bx0 = Math.min(bx0, G.cx - G.w / 2); bx1 = Math.max(bx1, G.cx + G.w / 2); by0 = Math.min(by0, G.cy - fs / 2); by1 = Math.max(by1, G.cy + fs / 2); }
    if (bx0 > bx1) { var rr = jzRect(L); bx0 = rr.left; bx1 = rr.left + rr.width; by0 = rr.top; by1 = rr.top + rr.height; }
    return { fs: fs, lead: lead, rows: rows, glyphs: glyphs, chars: chars, spans: spans, vert: vert, n: gi, just: just,
        box: { x0: bx0, x1: bx1, y0: by0, y1: by1, cx: (bx0 + bx1) / 2, cy: (by0 + by1) / 2 } };
}
// the browser's text "lines" for per-line glyph order: the rows (with spaces), or the one column of vertical text
function tr2_lines(G) {
    if (!G.vert) { var o = []; for (var i = 0; i < G.rows.length; i++) o.push(G.rows[i].c); return o; }
    return [G.glyphs];
}
function tr2_isVert(L) {
    var rows = String(tr2_doc(L).text).split(/\r\n|\r|\n/), i;
    if (rows.length < 2) return false;
    for (i = 0; i < rows.length; i++) if (jzCount(rows[i]) > 1) return false;
    return true;
}
// row limits: each row owns the space half way to its neighbours; the first / last rows reach far out
function tr2_rowLim(G, i, far) { var R = G.rows; return [i === 0 ? G.box.y0 - far : (R[i - 1].cy + R[i].cy) / 2, i === R.length - 1 ? G.box.y1 + far : (R[i].cy + R[i + 1].cy) / 2]; }
function tr2_band(F, name, cx, w, y0, y1, hex, op) { if (y1 - y0 < 0.01) return null; var g = jzGrp(F, name); jzAddRect(g, w, y1 - y0, 0, cx, (y0 + y1) / 2); jzAddFill(g, hex, op); return g; }
// fill layer in L's layer space shown through L's glyphs (L becomes its matte)
function tr2_fillLayer(ctx, L, name) { var F = tr2_shapeOn(ctx, L, name, false, null); tr2_matte(L, F); return F; }
// multi-stop vertical gradient over every row's em box (stops [[0..1, hex], ...] like the browser's per-glyph gradient;
// equal offsets = hard edge), built from thin flat bands
function tr2_gradRows(F, G, stops, far) {
    var R = G.rows, s = G.fs, cx = G.box.cx, i, j, k, ns = stops.length;
    for (i = 0; i < R.length; i++) {
        var lim = tr2_rowLim(G, i, far), y0 = R[i].cy - s * 0.5, nm = 'row ' + (i + 1);
        var Y = function (p) { return jzClamp(y0 + p * s, lim[0], lim[1]); };
        tr2_band(F, nm + ' top', cx, far * 2, lim[0], Y(stops[0][0]) + 0.5, stops[0][1]);
        for (j = 0; j + 1 < ns; j++) {
            var p0 = stops[j][0], p1 = stops[j + 1][0], c0 = stops[j][1], c1 = stops[j + 1][1];
            if (p1 - p0 < 0.0005) continue;
            var m = c0 === c1 ? 1 : Math.max(2, Math.ceil((p1 - p0) * 30));
            for (k = 0; k < m; k++) tr2_band(F, nm + ' ' + (j + 1) + '.' + (k + 1), cx, far * 2, Y(p0 + (p1 - p0) * k / m), Y(p0 + (p1 - p0) * (k + 1) / m) + (k < m - 1 ? 0.5 : 0), m === 1 ? c0 : jzMixHex(c0, c1, (k + 0.5) / m));
        }
        tr2_band(F, nm + ' bottom', cx, far * 2, Y(stops[ns - 1][0]), lim[1], stops[ns - 1][1]);
    }
}
// ---- screen-tone patterns (dots / lines / hatch / stripes) as shape groups
function tr2_rep(g, n, dx, dy) {
    var r = jzVecs(g).addProperty('ADBE Vector Filter - Repeater');
    r.property('ADBE Vector Repeater Copies').setValue(Math.max(1, n));
    r.property('ADBE Vector Repeater Transform').property('ADBE Vector Repeater Position').setValue([dx, dy]);
    return r;
}
function tr2_sub(g, name) { var s = jzVecs(g).addProperty('ADBE Vector Group'); s.name = name; return s; }
function tr2_bandsShape(S, name, bx, th, sp, rot, hex) {
    var D = Math.sqrt(Math.pow(bx.x1 - bx.x0, 2) + Math.pow(bx.y1 - bx.y0, 2)), n = Math.ceil(D / sp) + 1;
    var g = jzGrp(S, name), inner = tr2_sub(g, name + ' band');
    jzAddRect(inner, D, th, 0, 0, -D / 2); jzAddFill(inner, hex); tr2_rep(inner, n, 0, sp);
    jzGX(g).property('ADBE Vector Position').setValue([(bx.x0 + bx.x1) / 2, (bx.y0 + bx.y1) / 2]);
    if (rot) jzGX(g).property('ADBE Vector Rotation').setValue(rot);
    return g;
}
function tr2_pattern(ctx, S, kind, hex, s, bx, kk) {
    var w = bx.x1 - bx.x0, h = bx.y1 - bx.y0, u = ctx.u / kk, c, nx, ny;
    if (kind === 'dots') {
        c = Math.max(3 * u, s * 0.075); nx = Math.ceil(w / c) + 1; ny = Math.ceil(h / c) + 1;
        if (nx * ny > 6000) { c *= Math.sqrt(nx * ny / 6000); nx = Math.ceil(w / c) + 1; ny = Math.ceil(h / c) + 1; }
        var g = jzGrp(S, 'dots'), row = tr2_sub(g, 'dot row');
        jzAddEllipse(row, c * 0.68, c * 0.68, bx.x0, bx.y0); jzAddFill(row, hex); tr2_rep(row, nx, c, 0);
        tr2_rep(g, ny, 0, c);
    } else if (kind === 'hatch') {
        c = Math.max(3 * u, s * 0.06);
        tr2_bandsShape(S, 'hatch a', bx, Math.max(u, c * 0.16), c / Math.SQRT2, 45, hex);
        tr2_bandsShape(S, 'hatch b', bx, Math.max(u, c * 0.16), c / Math.SQRT2, -45, hex);
    } else {
        c = Math.max(3 * u, s * 0.06);
        tr2_bandsShape(S, 'stripes', bx, c * 0.38, c / Math.SQRT2, -45, hex);
    }
}
// tracking (em) for horizontal text / extra leading for vertical text (one glyph per row)
function tr2_addTrack(td, em, vert) {
    if (vert) { var ld = (td.autoLeading === false && td.leading > 0) ? td.leading : td.fontSize * 1.2; td.autoLeading = false; td.leading = ld + em * td.fontSize; }
    else td.tracking = Math.round((td.tracking || 0) + em * 1000);
}

// ================================================================ glow / metal / colour
/* ---- neonOutline \u2014 \u30CD\u30AA\u30F3\u7BA1: bright thin outline (stroke only) + coloured bloom (two soft zero-distance shadows), rare flicker */
jzReg('treat', 'neonOutline', {
    plan: function (rng, st) { return { k: rng.range(0.024, 0.032), fl: rng.chance(0.75) }; },
    apply: function (ctx, L, P, o) {
        if (!tr2_alive(L, 0.5)) return;
        var sc = ctx.sc, col = jzTextColor(L), dk = tr2_dark(sc.bg), s = jzFontSize(L), kk = tr2_k(L);
        var cand = [sc.accent, sc.accent2, sc.ghostA, sc.ghostB];
        var gc = dk ? tr2_first(cand, function (c) { return jzLum(c) > jzLum(sc.bg) + 0.15 && jzContrast(c, col) >= 1.2; }, col)
            : tr2_first(cand, function (c) { return jzContrast(c, sc.bg) >= 1.8; }, col);
        var tube = dk ? jzMixHex(col, '#FFFFFF', 0.4) : col;
        jzTextDoc(L, function (td) {
            td.applyFill = false; td.applyStroke = true; td.strokeColor = jzHex(tube);
            td.strokeWidth = Math.max(1.4 * ctx.u / kk, s * tr2_p(P, 'k', 0.028));
        });
        tr2_recolor(L, tube);
        // tube halo (browser: shadow blur 0.07s) + wide bloom (browser: 4.5x stroke at ~20% under a 0.16s blur)
        tr2_shadow(L, 'JZ Neon glow', gc, 0, 0, s * 0.16, dk ? 1 : 0.75);
        tr2_shadow(L, 'JZ Neon bloom', gc, 0, 0, s * 0.42, dk ? 0.8 : 0.55);
        if (P.fl) jzAnimator(L, 'JZ Neon flicker', [['ADBE Text Opacity', 0]], tr2_head(ctx, L, o) +
            'var r=hh(SD*0.37+textIndex*13.1+Math.floor(time*24+1e-6)*7.7);r<0.03?78:(r<0.05?40:0)');
    }
});

/* ---- chrome \u2014 \u30AF\u30ED\u30FC\u30E0: multi-stop metallic fill with a hard horizon (banded fill through the lyric) + dark keyline (stroke copy) */
jzReg('treat', 'chrome', {
    plan: function (rng, st) { return { v: rng.pick(['silver', 'silver', 'sunset']), h: rng.range(0.5, 0.56) }; },
    apply: function (ctx, L, P, o) {
        if (!tr2_alive(L)) return;
        var sc = ctx.sc, col = jzTextColor(L), light = jzLum(col) > 0.42, h = tr2_p(P, 'h', 0.53), s = jzFontSize(L);
        var acc = tr2_accentFor(sc, col, 1.3), sun = P.v === 'sunset';
        var top0 = light ? '#FFFFFF' : jzMixHex(col, '#FFFFFF', 0.55), top1 = light ? jzMixHex(col, '#000000', 0.42) : col;
        var band = jzMixHex(col, '#000000', light ? 0.62 : 0.45);
        var low0 = sun ? jzMixHex(acc, '#FFFFFF', 0.5) : jzMixHex(col, '#FFFFFF', light ? 0.75 : 0.42);
        var low1 = sun ? acc : jzMixHex(col, '#000000', light ? 0.12 : 0.25);
        var key = light ? jzMixHex(col, '#000000', 0.7) : jzMixHex(col, '#000000', 0.35);
        var G = tr2_lay(ctx, L);
        if (!G.rows.length) return;
        jzTextDoc(L, function (td) { td.applyStroke = false; });
        // keyline: a stroke-only copy on top (only when copies follow the motion exactly)
        if (!tr2_rnd(L) && !tr2_many(ctx)) { var D = tr2_copy(L, 'JZ Chrome keyline', L, key, Math.max(1.2 * ctx.u / tr2_k(L), s * 0.022), 0, 0, false); D.moveBefore(L); }
        var F = tr2_fillLayer(ctx, L, 'JZ Chrome fill');
        tr2_gradRows(F, G, [[0.08, top0], [h, top1], [h, band], [h + 0.03, band], [h + 0.03, low0], [0.94, low1]], tr2_far(ctx, L));
    }
});

/* ---- rainbow \u2014 \u8679\u8272: colours drift across the glyphs through the scheme palette (chained Fill Colour animators) */
jzReg('treat', 'rainbow', {
    plan: function (rng, st) { return { v: rng.pick(['drift', 'drift', 'steps']), sp: rng.range(0.28, 0.45), dir: rng.chance(0.5) ? 1 : -1 }; },
    apply: function (ctx, L, P, o) {
        if (!tr2_alive(L)) return;
        var sc = ctx.sc, col = jzTextColor(L), i, j;
        if (tr2_onPlate(sc, col)) return;
        var pal = [col], cand = [sc.accent, sc.accent2, sc.ghostA, sc.ghostB, sc.sub];
        for (i = 0; i < cand.length; i++) {
            var c = cand[i], ok = !!c && jzContrast(c, sc.bg) >= 1.8;
            for (j = 0; ok && j < pal.length; j++) if (jzContrast(pal[j], c) < 1.25) ok = false;
            if (ok) pal.push(c);
        }
        if (pal.length < 3) pal.push(jzFitContrast(jzMixHex(sc.accent, sc.accent2 || sc.fg, 0.5), sc.bg, 2));
        var n = pal.length, HD = tr2_head(ctx, L, o);
        var core = HD + 'var N=' + n + ',OF=' + tr2_mi(o) + ',DR=' + (P.dir === -1 ? -1 : 1) + ';' + (P.v === 'steps'
            ? 'var a=((textIndex-1+OF+Math.floor(time*2.2)*DR)%N+N)%N,f=0;'
            : 'var u=(textIndex-1+OF)*' + jzN(tr2_p(P, 'sp', 0.36)) + '-time*0.75*DR,k=((u%N)+N)%N,a=Math.floor(k),x=cl((k-a-0.3)/0.4),f=x*x*(3-2*x);');
        // animator J recolours towards pal[J % N]: J <= a fully, J = a+1 by f -> mix(pal[a], pal[a+1], f)
        for (j = 1; j <= n; j++) jzAnimator(L, 'JZ Rainbow ' + j, [['ADBE Text Fill Color', jzHex(pal[j % n])]], core + 'var J=' + j + ';J<=a?100:(J===a+1?f*100:0)');
    }
});

/* ---- glitchSplit \u2014 \u8272\u7248\u30BA\u30EC: two tinted copies split sideways (Drop Shadows), jolting and slicing (Wave Warp) on glitch steps */
jzReg('treat', 'glitchSplit', {
    plan: function (rng, st) { return { d: rng.range(0.06, 0.08), up: rng.chance(0.3) }; },
    apply: function (ctx, L, P, o) {
        if (!tr2_alive(L)) return;
        var sc = ctx.sc, col = jzTextColor(L), s = jzFontSize(L), HD = tr2_head(ctx, L, o), i;
        var cands = [], cc = [sc.ghostA, sc.ghostB, sc.accent, sc.accent2];
        for (i = 0; i < cc.length; i++) if (cc[i] && jzContrast(cc[i], sc.bg) >= 1.4 && jzContrast(cc[i], col) >= 1.15) cands.push(cc[i]);
        var cA = cands[0] || tr2_accentFor(sc, col, 1.4), cB = tr2_first(cands, function (c) { return jzContrast(c, cA) >= 1.3; }, jzMixHex(cA, sc.bg, 0.4));
        // step clock 24 fps; hit on ~20% of the steps after half the entrance
        var H2 = HD + 'var st=Math.floor(time*24+1e-6),hit=(hh(SD*0.71+st*3.17)<0.2&&time>' + jzN((ctx.cut.inDur || 0.3) * 0.5) + ');' +
            'var d=' + jzN(s * tr2_p(P, 'd', 0.07)) + '*(hit?1.8+hh(SD*0.71+st*5.3):1),dy=' + (P.up ? 'd*0.45' : '0') + '+(hit?(hh(SD*0.71+st*9.1)*2-1)*' + jzN(s * 0.02) + ':0);';
        var mk = function (name, hex, sg) {
            var e = tr2_shadow(L, name, hex, sg * s * 0.07, 0, 0, 0.95);
            jzEX(e, 3, H2 + 'var x=' + sg + '*d,y=' + sg + '*dy;(Math.atan2(x,-y)*180/Math.PI+360)%360');
            jzEX(e, 4, H2 + 'Math.sqrt(d*d+dy*dy)');
        };
        mk('JZ Split A', cA, -1);
        mk('JZ Split B', cB, 1);
        // slice: horizontal bands shifted sideways on hit steps
        var ww = jzEffect(L, 'ADBE Wave Warp', 'JZ Split slice');
        jzEP(ww, 1, 2); jzEP(ww, 4, 0); jzEP(ww, 5, 0); jzEP(ww, 6, 1);
        jzEX(ww, 2, H2 + 'hit&&hh(SD*0.71+st*2.3)<0.7?(hh(SD*0.71+st*4.7)*2-1)*' + jzN(s * 0.16) + ':0');
        jzEX(ww, 3, H2 + jzN(s * 0.55) + '*(0.7+0.6*hh(SD*0.71+st*6.1))');
        jzEX(ww, 7, H2 + 'hh(SD*0.71+st*8.3)*360');
    }
});

/* ---- shadowStack \u2014 \u591A\u91CD\u5F71: separated copies in scheme colours stepping away (bg-outlined copies; hard shadows when copies can't follow) */
jzReg('treat', 'shadowStack', {
    plan: function (rng, st) { return { d: rng.range(0.04, 0.055), dir: rng.pick([[1, 1], [1, 1], [-1, 1], [1, 0.5], [0, 1]]), n: rng.pick([3, 3, 4]) }; },
    apply: function (ctx, L, P, o) {
        if (!tr2_alive(L)) return;
        var sc = ctx.sc, col = jzTextColor(L), s = jzFontSize(L), n = tr2_p(P, 'n', 3), dir = P.dir || [1, 1], d = s * tr2_p(P, 'd', 0.047), i, j, k;
        var cols = [], cand = [sc.accent, sc.accent2, sc.ghostB, sc.ghostA, sc.ink, sc.sub, sc.fg];
        for (i = 0; i < cand.length; i++) {
            var c = cand[i], ok = !!c && jzContrast(c, sc.bg) >= 1.4 && jzContrast(c, col) >= 1.12;
            for (j = 0; ok && j < cols.length; j++) if (jzContrast(cols[j], c) < 1.12) ok = false;
            if (ok) cols.push(c);
        }
        while (cols.length < n) cols.push(jzMixHex(cols.length ? cols[cols.length - 1] : tr2_accentFor(sc, col), sc.bg, 0.35));
        var sw = Math.max(1.5 * ctx.u / tr2_k(L), s * 0.028);
        jzTextDoc(L, function (td) { td.applyStroke = true; td.strokeColor = jzHex(sc.bg); td.strokeWidth = sw; try { td.strokeOverFill = false; } catch (e) {} });
        if (!tr2_rnd(L)) {
            var prev = L;
            n = tr2_budget(ctx, n);
            for (k = 1; k <= n; k++) prev = jzNoGhost(tr2_copy(L, 'JZ Stack ' + k, prev, cols[k - 1], sw, dir[0] * d * k, dir[1] * d * k, true, sc.bg));
        } else {
            for (k = 1; k <= n; k++) tr2_shadow(L, 'JZ Stack ' + k, cols[k - 1], dir[0] * d, dir[1] * d, 0, 1);
        }
    }
});

// ================================================================ cuts / fills that move
/* ---- stencilGap \u2014 \u30B9\u30C6\u30F3\u30B7\u30EB\u5B57: bridges cut through every glyph row (inverted matte of thin bands), opening in, closing out */
jzReg('treat', 'stencilGap', {
    plan: function (rng, st) { return { v: rng.pick(['one', 'one', 'two']), at: rng.range(-0.05, 0.06), g: rng.range(0.034, 0.048) }; },
    apply: function (ctx, L, P, o) {
        if (!tr2_alive(L)) return;
        var G = tr2_lay(ctx, L), s = G.fs, HD = tr2_head(ctx, L, o), far = tr2_far(ctx, L), i, k;
        if (!G.rows.length) return;
        var cuts = P.v === 'two' ? [-0.13, 0.13] : [tr2_p(P, 'at', 0)];
        var M = tr2_shapeOn(ctx, L, 'JZ Stencil gaps', true, null);
        var gx = HD + 'var g=' + jzN(tr2_p(P, 'g', 0.04) * 2 * s) + '*oc(cl((time-DL-0.04)/0.4))*(1-ic(PO));';
        for (i = 0; i < G.rows.length; i++) for (k = 0; k < cuts.length; k++) {
            // (the rect is configured before the fill is added \u2014 adding the fill invalidates r in AE)
            var g = jzGrp(M, 'gap ' + (i + 1) + '.' + (k + 1)), r = jzAddRect(g, far * 2, 1, 0, G.box.cx, G.rows[i].cy + cuts[k] * s);
            r.property('ADBE Vector Rect Size').expression = gx + '[' + jzN(far * 2) + ',g<' + jzN(0.006 * s) + '?0:g]';
            jzAddFill(g, '#FFFFFF');
        }
        tr2_matte(M, L, true);
    }
});

/* ---- waterline \u2014 \u6C34\u4F4D: outlined glyphs fill up with liquid (filled copy = matte of animated bands), surface bobs, drains on exit */
jzReg('treat', 'waterline', {
    plan: function (rng, st) { return { lvl: rng.range(0.46, 0.58), c: rng.int(0, 1), k: rng.range(0.02, 0.026) }; },
    apply: function (ctx, L, P, o) {
        if (!tr2_alive(L, 0.5)) return;
        var sc = ctx.sc, col = jzTextColor(L), liq = P.c ? tr2_accentFor(sc, col, 1.3) : col, surf = jzMixHex(liq, '#FFFFFF', tr2_dark(liq) ? 0.35 : 0.5);
        var G = tr2_lay(ctx, L), s = G.fs, HD = tr2_head(ctx, L, o), far = tr2_far(ctx, L), rnd = tr2_rnd(L), i;
        if (!G.rows.length) return;
        var sw = Math.max(1.3 * ctx.u / tr2_k(L), s * tr2_p(P, 'k', 0.023));
        var D = tr2_dup(L, 'JZ Water matte', L);
        jzTextDoc(D, function (td) { td.applyFill = true; td.applyStroke = false; });
        jzTextDoc(L, function (td) { td.applyFill = false; td.applyStroke = true; td.strokeColor = jzHex(col); td.strokeWidth = sw; });
        tr2_recolor(L, col);
        // liquid: when copies can't follow a random motion, it rises after the entrance and is gone once the exit starts
        var F = tr2_shapeOn(ctx, L, 'JZ Water', false, rnd ? HD + '100*' + TR2_REST : null);
        tr2_matte(D, F); jzNoGhost(F);
        var LV = HD + 'function ioc(x){x=cl(x);return x<0.5?4*x*x*x:1-Math.pow(-2*x+2,3)/2;}' +
            'var q=ioc(cl((time-DL-' + (rnd ? 'IN*1.02' : '0') + '-0.05)/0.8))*(1-ic(PO));' +
            'var Lv=1.02+(' + jzN(tr2_p(P, 'lvl', 0.52)) + '-1.02)*q+Math.sin(time*2.6+' + tr2_mi(o) + ')*0.022*q;';
        for (i = 0; i < G.rows.length; i++) {
            var lim = tr2_rowLim(G, i, far), y0 = G.rows[i].cy - s * 0.5, g = jzGrp(F, 'row ' + (i + 1));
            var E = LV + 'var B=' + jzN(lim[1]) + ',y=Math.min(B,' + jzN(y0) + '+Lv*' + jzN(s) + '),y2=Math.min(B,y+' + jzN(s * 0.025) + ');';
            var rl = jzAddRect(g, 1, 1);
            rl.property('ADBE Vector Rect Size').expression = E + '[' + jzN(far * 2) + ',B-y2]';
            rl.property('ADBE Vector Rect Position').expression = E + '[' + jzN(G.box.cx) + ',(y2+B)/2]';
            jzAddFill(g, liq);
            // the lighter surface band right above the liquid
            var g2 = jzGrp(F, 'surface ' + (i + 1)), rs = jzAddRect(g2, 1, 1);
            rs.property('ADBE Vector Rect Size').expression = E + '[' + jzN(far * 2) + ',y2-y]';
            rs.property('ADBE Vector Rect Position').expression = E + '[' + jzN(G.box.cx) + ',(y+y2)/2]';
            jzAddFill(g2, surf);
        }
    }
});

/* ---- karaoke \u2014 \u30AB\u30E9\u30AA\u30B1: a colour wipe runs through the words over the cut (hot copy through growing line rects) */
jzReg('treat', 'karaoke', {
    plan: function (rng, st) { return { sp: rng.range(0.7, 0.85), ol: rng.chance(0.55) }; },
    apply: function (ctx, L, P, o) {
        if (!tr2_alive(L, 0.9)) return;
        var sc = ctx.sc, col = jzTextColor(L), hot = tr2_accentFor(sc, col, 1.8), cut = ctx.cut, s = jzFontSize(L), k, i;
        if (tr2_onPlate(sc, col)) return;
        if (P.ol) {
            var oc = tr2_best([sc.bg, sc.ink, '#111111', '#FFFFFF'], col);
            jzTextDoc(L, function (td) { td.applyStroke = true; td.strokeColor = jzHex(oc); td.strokeWidth = Math.max(1.5 * ctx.u / tr2_k(L), s * 0.06); try { td.strokeOverFill = false; } catch (e) {} });
        }
        var G = tr2_lay(ctx, L), HD = tr2_head(ctx, L, o);
        if (!G.spans.length) return;
        var t0 = (cut.inDur || 0.3) * 0.6, T = Math.max(0.3, (cut.dur - (cut.outDur || 0) - t0) * tr2_p(P, 'sp', 0.78)), tot = 0;
        for (k = 0; k < G.spans.length; k++) tot += G.spans[k].a1 - G.spans[k].a0;
        var Q = HD + 'var q=cl((time-DL-' + jzN(t0) + ')/' + jzN(T) + ');q=-(Math.cos(Math.PI*q)-1)/2;';
        if (tr2_rnd(L) || tr2_many(ctx) || G.glyphs.length < 2) {
            // glyphs move randomly (or one-glyph items / crowded comps): per-glyph colour sweep (each glyph turns hot while the wipe passes it)
            var GS = [], GW = [], st0 = 0;
            for (k = 0; k < G.spans.length; k++) {
                var sp = G.spans[k];
                for (i = 0; i < sp.g.length; i++) { var gl = sp.g[i], a = sp.vert ? gl.cy - G.fs / 2 : gl.cx - gl.w / 2; GS[gl.i] = st0 + a - sp.a0; GW[gl.i] = Math.max(1, sp.vert ? G.fs : gl.w); }
                st0 += sp.a1 - sp.a0;
            }
            for (i = 0; i < G.n; i++) { if (GS[i] == null) GS[i] = null; if (GW[i] == null) GW[i] = null; }
            jzAnimator(L, 'JZ Karaoke', [['ADBE Text Fill Color', jzHex(hot)]], Q + 'var S=' + tr2_arr(GS) + ',W=' + tr2_arr(GW) + ';var a=S[textIndex-1];a==null?0:cl((q*' + jzN(tot) + '-a)/W[textIndex-1])*100');
            return;
        }
        var D = tr2_dup(L, 'JZ Karaoke hot', L); D.moveBefore(L); jzNoGhost(D);
        jzTextDoc(D, function (td) { td.fillColor = jzHex(hot); });
        tr2_recolor(D, hot);
        if (P.ol) { var an = D.property('ADBE Text Properties').property('ADBE Text Animators'); for (i = 1; i <= an.numProperties; i++) { var sc2 = an.property(i).property('ADBE Text Animator Properties').property('ADBE Text Stroke Color'); if (sc2) sc2.setValue(jzHex(oc)); } }
        var M = tr2_shapeOn(ctx, L, 'JZ Karaoke wipe', true, null), pad = s * 0.12, cr = s * 0.72, acc = 0;
        for (k = 0; k < G.spans.length; k++) {
            // (the rect is configured before the fill is added \u2014 adding the fill invalidates r in AE)
            var Sp = G.spans[k], len = Sp.a1 - Sp.a0, g = jzGrp(M, 'line ' + (k + 1)), r = jzAddRect(g, 1, 1);
            var E = Q + 'var LN=' + jzN(len) + ',A0=' + jzN(Sp.a0) + ',PD=' + jzN(pad) + ';var take=Math.min(LN,Math.max(0,q*' + jzN(tot) + '-' + jzN(acc) + '));' +
                'var a0=A0-PD,a1=A0+take+(take>=LN-0.01?PD:0),w=take>0?a1-a0:0;';
            r.property('ADBE Vector Rect Size').expression = E + (Sp.vert ? '[' + jzN(cr * 2) + ',w]' : '[w,' + jzN(cr * 2) + ']');
            r.property('ADBE Vector Rect Position').expression = E + (Sp.vert ? '[' + jzN(Sp.c) + ',a0+w/2]' : '[a0+w/2,' + jzN(Sp.c) + ']');
            jzAddFill(g, '#FFFFFF');
            acc += len;
        }
        tr2_matte(M, D);
    }
});

// ================================================================ per-glyph arrangement
/* ---- sizeWave \u2014 \u5927\u5C0F\u30EA\u30BA\u30E0: glyph sizes alternate / follow script / ramp / wave, re-spaced tightly on a shared baseline */
jzReg('treat', 'sizeWave', {
    plan: function (rng, st) { return { v: rng.pick(['alt', 'kanji', 'kanji', 'ramp', 'wave']), k: rng.range(0.64, 0.74), rev: rng.chance(0.4) }; },
    apply: function (ctx, L, P, o) {
        if (!tr2_alive(L, 0.5)) return;
        var G = tr2_lay(ctx, L), N = G.n, off = tr2_mi(o), lines = tr2_lines(G), fs = G.fs, kk = tr2_p(P, 'k', 0.69), i, j;
        if (!N || !G.glyphs.length) return;
        var isK = function (c) { return jzIsKanji(c) || jzIsKata(c) || jzIsLatin(c); }, nK = 0, nO = 0;
        for (i = 0; i < G.glyphs.length; i++) { if (isK(G.glyphs[i].ch)) nK++; else nO++; }
        var v = P.v || 'alt';
        if (v === 'kanji' && !(nK && nO)) v = 'alt';
        if ((v === 'ramp' || v === 'wave') && G.glyphs.length < 3) v = 'alt';
        var S = [], off2 = [];
        for (i = 0; i < N; i++) { S.push(1); off2.push([0, 0]); }
        for (j = 0; j < lines.length; j++) {
            var ln = lines[j], n = ln.length;
            for (i = 0; i < n; i++) {
                var g = ln[i], u = n > 1 ? i / (n - 1) : 0.5, s = 1;
                if (v === 'alt') s = (i + (G.vert ? 0 : g.row) + off) % 2 ? kk : 1.04;
                else if (v === 'kanji') s = isK(g.ch) ? 1.08 : kk + 0.04;
                else if (v === 'ramp') s = jzLerp(1.1, kk, P.rev ? 1 - u : u);
                else s = 0.87 + 0.17 * Math.sin(i * 1.25 + off);
                if (jzIsPunct(g.ch)) s = Math.min(s, 0.9);
                S[g.i] = s;
            }
            // re-space along the line so smaller glyphs close up (centred / left / right like the paragraph)
            var ext = function (g2) { return G.vert ? G.lead : g2.w; }, pos = function (g2) { return G.vert ? g2.cy : g2.cx; };
            var tr = G.vert ? 0 : G.rows[0].trk, tot = tr * (n - 1);
            for (i = 0; i < n; i++) tot += ext(ln[i]) * S[ln[i].i];
            var o0 = pos(ln[0]) - ext(ln[0]) / 2, o1 = pos(ln[n - 1]) + ext(ln[n - 1]) / 2;
            var p = G.just === ParagraphJustification.LEFT_JUSTIFY ? o0 : (G.just === ParagraphJustification.RIGHT_JUSTIFY && !G.vert ? o1 - tot : (o0 + o1) / 2 - tot / 2);
            for (i = 0; i < n; i++) {
                var gg = ln[i], w = ext(gg) * S[gg.i], dd = p + w / 2 - pos(gg);
                off2[gg.i] = G.vert ? [0, dd] : [dd, (1 - S[gg.i]) * 0.4 * fs];
                p += w + tr;
            }
        }
        jzCharScales(L, S, 'JZ Size wave');
        jzCharOffsets(L, off2, 'JZ Size wave place');
    }
});

/* ---- rotateAlt \u2014 \u63FA\u308C\u5B57: glyphs lean left / right like hand-set type */
jzReg('treat', 'rotateAlt', {
    plan: function (rng, st) { return { a: rng.range(9, 14), v: rng.pick(['alt', 'alt', 'rand']) }; },
    apply: function (ctx, L, P, o) {
        if (!tr2_alive(L, 0.5)) return;
        var gl = jzGlyphs(tr2_doc(L).text), off = tr2_mi(o), a = tr2_p(P, 'a', 11), rots = [], scs = [], i;
        for (i = 0; i < gl.length; i++) {
            if (jzIsPunct(gl[i]) || tr2_isSp(gl[i])) { rots.push(0); scs.push(1); continue; }
            var sg = (i + off) % 2 ? 1 : -1;
            rots.push(P.v === 'rand' ? sg * a * (0.45 + 0.8 * jzR(ctx.cut.seed, off + 9, 47, i)) : sg * a);
            scs.push(0.94);
        }
        jzCharRotations(L, rots, 'JZ Lean');
        jzCharScales(L, scs, 'JZ Lean size');
    }
});

/* ---- baselineShift \u2014 \u6BB5\u9055\u3044: alternate up / down, stairs or an arch */
jzReg('treat', 'baselineShift', {
    plan: function (rng, st) { return { v: rng.pick(['alt', 'alt', 'stairs', 'arc']), k: rng.range(0.08, 0.11), dir: rng.chance(0.5) ? 1 : -1 }; },
    apply: function (ctx, L, P, o) {
        if (!tr2_alive(L, 0.5)) return;
        var G = tr2_lay(ctx, L), lines = tr2_lines(G), off = tr2_mi(o), kk = (!G.vert && G.rows.length > 1) ? 0.7 : 1, K = tr2_p(P, 'k', 0.095), offs = [], i, j;
        for (i = 0; i < G.n; i++) offs.push([0, 0]);
        for (j = 0; j < lines.length; j++) {
            var ln = lines[j], n = ln.length;
            for (i = 0; i < n; i++) {
                var u = n > 1 ? i / (n - 1) : 0.5, v;
                if (P.v === 'stairs') v = (u - 0.5) * K * Math.min(3.2, n * 0.55) * (P.dir === -1 ? -1 : 1);
                else if (P.v === 'arc') v = (Math.sin(Math.PI * u) - 0.6) * K * 2.2;
                else v = ((i + off) % 2 ? 1 : -1) * K;
                offs[ln[i].i] = G.vert ? [v * kk * G.fs, 0] : [0, -v * kk * G.fs];
            }
        }
        jzCharOffsets(L, offs, 'JZ Baseline');
    }
});

/* ---- fauxBold \u2014 \u6975\u592A: same-colour stroke thickens every stem, a little more tracking */
jzReg('treat', 'fauxBold', {
    plan: function (rng, st) { return { k: rng.range(0.035, 0.05) }; },
    apply: function (ctx, L, P, o) {
        if (!tr2_alive(L)) return;
        var col = jzTextColor(L), s = jzFontSize(L), k = tr2_p(P, 'k', 0.042), vert = tr2_isVert(L);
        tr2_reanchor(L, function () {
            jzTextDoc(L, function (td) {
                td.applyStroke = true; td.strokeColor = jzHex(col); td.strokeWidth = Math.max(ctx.u / tr2_k(L), s * k);
                try { td.strokeOverFill = false; } catch (e) {}
                tr2_addTrack(td, k, vert);
            });
        });
    }
});

// ================================================================ marks drawn with the text
/* ---- circled \u2014 \u4E38\u56F2\u307F: each glyph shrinks into its own ring (or disc) popping in one by one */
jzReg('treat', 'circled', {
    plan: function (rng, st) { return { v: rng.pick(['ring', 'ring', 'disc']), k: rng.range(0.68, 0.74) }; },
    apply: function (ctx, L, P, o) {
        if (!tr2_alive(L, 0.9)) return;
        var sc = ctx.sc, col = jzTextColor(L), disc = P.v === 'disc', k = tr2_p(P, 'k', 0.71), i;
        if (tr2_onPlate(sc, col)) return;
        var rc = disc ? tr2_first([sc.accent, sc.accent2, sc.ink, sc.fg], function (c) { return jzContrast(c, sc.bg) >= 2 && jzContrast(c, col) >= 1.3; }, sc.fg) : tr2_markCol(sc, col);
        var tc = disc ? tr2_textOn(rc, col, sc) : col;
        var G = tr2_lay(ctx, L), s = G.fs, HD = tr2_head(ctx, L, o), scs = [], pu = [];
        if (!G.glyphs.length) return;
        var Q = function (gi) { return 'var q=ob(cl((time-DL-' + gi + '*0.04)/0.26),1.6)*(1-ic(cl(PO*1.4-' + gi + '*0.03)));'; };
        var gl = jzGlyphs(tr2_doc(L).text);
        for (i = 0; i < gl.length; i++) { var p = jzIsPunct(gl[i]) || tr2_isSp(gl[i]); scs.push(p ? 1 : k); pu.push(p ? 1 : 0); }
        jzCharScales(L, scs, 'JZ Circled size');
        if (disc && tc !== col) jzAnimator(L, 'JZ Circled text', [['ADBE Text Fill Color', jzHex(tc)]], HD + Q('(textIndex-1)') + 'var PU=' + tr2_arr(pu) + ';PU[textIndex-1]?0:(q>0.55?100:0)');
        var S = tr2_shapeOn(ctx, L, 'JZ Circles', false, HD + TR2_POP), n = 0;
        for (i = 0; i < G.glyphs.length; i++) {
            var g = G.glyphs[i];
            if (jzIsPunct(g.ch)) continue;
            var grp = jzGrp(S, 'circle ' + (g.i + 1));
            if (disc) { jzAddEllipse(grp, s * 0.98, s * 0.98); jzAddFill(grp, rc); }
            else { jzAddEllipse(grp, s * 0.98 * 0.97, s * 0.98 * 0.97); jzAddStroke(grp, rc, Math.max(1.5 * ctx.u / tr2_k(L), s * 0.045)); }
            var X = jzGX(grp);
            X.property('ADBE Vector Position').setValue([g.cx, g.cy]);
            X.property('ADBE Vector Scale').expression = HD + Q(g.i) + '[100*q,100*q]';
            n++;
        }
        if (!n) S.remove();
    }
});

/* ---- bracketsQuote \u2014 \u304B\u304E\u62EC\u5F27: \u300C\u300D corner brackets drawn on around the whole lyric (single / double) */
jzReg('treat', 'bracketsQuote', {
    plan: function (rng, st) { return { v: rng.pick(['single', 'single', 'double']), k: rng.range(0.05, 0.065) }; },
    apply: function (ctx, L, P, o) {
        if (!tr2_alive(L, 0.9)) return;
        var norm = function (t) { return String(t || '').replace(/[\s\u3000]/g, ''); }, whole = norm(ctx.cut.text), mine = norm(tr2_doc(L).text);
        if (!mine || !whole || mine.length > whole.length) return;
        var open = whole.substr(0, mine.length) === mine, close = whole.substr(whole.length - mine.length) === mine;
        if (!open && !close) return;
        if (mine === whole) ctx.tr2Quoted = true;
        else if (jzChars(mine).length === 1) {
            var nW = jzChars(whole).length, mi = o ? o.mi : null;
            if (ctx.tr2Quoted || mi == null || mi !== Math.round(mi)) return;
            open = open && mi === 0; close = close && mi === nW - 1;
            if (!open && !close) return;
        }
        var lc = tr2_markCol(ctx.sc, jzTextColor(L)), vert = tr2_isVert(L), scl = tr2_sv(L, 'ADBE Scale'), kk = tr2_k(L);
        // leave room for the brackets
        var r0 = jzRect(L), along = vert ? r0.height * Math.abs(scl[1]) / 100 : r0.width * Math.abs(scl[0]) / 100;
        var room = jzFontSize(L) * kk * 1.1, cap = (vert ? ctx.H : ctx.W) * 0.92;
        if (along + room > cap && along < cap * 1.05) { var f = Math.max(0.72, cap / (along + room)); tr2_reanchor(L, function () { jzTextDoc(L, function (td) { td.fontSize *= f; if (td.autoLeading === false) td.leading *= f; }); }); }
        var G = tr2_lay(ctx, L), sp = G.spans, s = G.fs, HD = tr2_head(ctx, L, o);
        if (!sp.length) return;
        var L0 = sp[0], L1 = sp[sp.length - 1], lw = Math.max(1.5 * ctx.u / kk, s * tr2_p(P, 'k', 0.057)), gp = s * 0.3, arm = s * 0.36, leg = s * 0.74;
        var S = jzNoGhost(tr2_shapeOn(ctx, L, 'JZ Brackets', true, HD + TR2_POP)), n = 0;
        var draw = function (pts) { var g = jzGrp(S, 'bracket ' + (++n)); jzAddPath(g, pts, false); jzAddStroke(g, lc, lw); jzAddTrimPaths(g, HD + 'oc(cl((time-DL-IN*0.45)/0.35))*(1-ic(PO))*100'); };
        var one = function (d) {
            if (!L0.vert) {
                var xL = L0.a0 - gp - d, yT = L0.c - s * 0.52 - d, xR = L1.a1 + gp + d, yB = L1.c + s * 0.52 + d;
                if (open) draw([[xL + arm, yT], [xL, yT], [xL, yT + leg]]);
                if (close) draw([[xR - arm, yB], [xR, yB], [xR, yB - leg]]);
            } else {
                var xR2 = L0.c + s * 0.52 + d, yT2 = L0.a0 - gp - d, xL2 = L1.c - s * 0.52 - d, yB2 = L1.a1 + gp + d;
                if (open) draw([[xR2, yT2 + arm], [xR2, yT2], [xR2 - leg, yT2]]);
                if (close) draw([[xL2, yB2 - arm], [xL2, yB2], [xL2 + leg, yB2]]);
            }
        };
        one(0);
        if (P.v === 'double') one(-lw * 2.2);
    }
});

/* ---- reflection \u2014 \u6620\u308A\u8FBC\u307F: a flipped, squashed copy of the last line on an imaginary floor, fading away from it */
jzReg('treat', 'reflection', {
    plan: function (rng, st) { return { k: rng.range(0.6, 0.78), a: rng.range(0.34, 0.46), gap: rng.range(0.04, 0.09) }; },
    apply: function (ctx, L, P, o) {
        if (!tr2_alive(L, 0.9)) return;
        var col = jzTextColor(L), G = tr2_lay(ctx, L), s = G.fs, k = tr2_p(P, 'k', 0.7), HD = tr2_head(ctx, L, o);
        if (!G.rows.length) return;
        var last = G.rows[G.rows.length - 1], yb = last.cy + s * 0.5, gap = s * tr2_p(P, 'gap', 0.065);
        var D = jzNoGhost(tr2_dup(L, 'JZ Reflection', L));
        jzTextDoc(D, function (td) { td.applyFill = true; td.fillColor = jzHex(col); td.applyStroke = false; });
        tr2_recolor(D, col);
        // flip about the floor: anchor on the floor line, scale y by -k, position one gap below the lyric's floor
        var A = tr2_sv(L, 'ADBE Anchor Point'), Pp = tr2_sv(L, 'ADBE Position'), Sc = tr2_sv(L, 'ADBE Scale'), rot = tr2_sv(L, 'ADBE Rotate Z') * Math.PI / 180;
        var sx = Sc[0] / 100, sy = Sc[1] / 100, vx = 0, vy = (yb - A[1]) * sy + gap * Math.abs(sy);
        jzXf(D, 'ADBE Anchor Point').setValue([A[0], yb]);
        jzXf(D, 'ADBE Position').setValue([Pp[0] + vx * Math.cos(rot) - vy * Math.sin(rot), Pp[1] + vx * Math.sin(rot) + vy * Math.cos(rot)]);
        jzXf(D, 'ADBE Scale').setValue([Sc[0], -Sc[1] * k]);
        jzXf(D, 'ADBE Opacity').setValue(tr2_sv(L, 'ADBE Opacity') * tr2_p(P, 'a', 0.4));
        // only the last line, strongest at the floor (feathered mask in the copy's own space)
        var far = tr2_far(ctx, L), m = jzMaskRect(D, G.box.cx - far, last.cy + s * 0.12, G.box.cx + far, yb + s * 0.55);
        m.property('ADBE Mask Feather').setValue([0, s * 0.76]);
        if (tr2_rnd(L)) tr2_gate(D, HD);
    }
});

/* ---- inline \u2014 \u30A4\u30F3\u30E9\u30A4\u30F3: a hairline in the background colour runs just inside every stroke (two stroke-only copies on top) */
jzReg('treat', 'inline', {
    plan: function (rng, st) { return { a: rng.range(0.017, 0.022), b: rng.range(0.016, 0.021), c: rng.chance(0.3) }; },
    apply: function (ctx, L, P, o) {
        if (!tr2_alive(L)) return;
        var sc = ctx.sc, col = jzTextColor(L), s = jzFontSize(L), a = tr2_p(P, 'a', 0.019), b = tr2_p(P, 'b', 0.018);
        var line = P.c ? tr2_first([sc.accent, sc.accent2], function (c) { return jzContrast(c, col) >= 2; }, sc.bg) : sc.bg;
        var D1 = tr2_copy(L, 'JZ Inline cut', L, line, s * (a + b) * 2, 0, 0, false); D1.moveBefore(L); jzNoGhost(D1);
        var D2 = tr2_copy(L, 'JZ Inline rim', L, col, s * a * 2, 0, 0, false); D2.moveBefore(D1); jzNoGhost(D2);
        if (tr2_rnd(L)) { var HD = tr2_head(ctx, L, o); tr2_gate(D1, HD); tr2_gate(D2, HD); }
    }
});

/* ---- sticker \u2014 \u30B7\u30FC\u30EB\u7E01: thick paper border (stroke under the fill / ring shadows), thin edge, soft drop shadow, slight tilt */
jzReg('treat', 'sticker', {
    plan: function (rng, st) { return { k: rng.range(0.13, 0.17), rot: rng.range(2, 4) * (rng.chance(0.5) ? 1 : -1), sh: rng.range(0.035, 0.05) }; },
    apply: function (ctx, L, P, o) {
        if (!tr2_alive(L, 0.9)) return;
        var sc = ctx.sc, col = jzTextColor(L), dk = tr2_dark(sc.bg), s = jzFontSize(L), k = tr2_p(P, 'k', 0.15);
        var paper = tr2_first([sc.ink, sc.fg, sc.sub], function (c) { return jzLum(c) > 0.78; }, '#FFFFFF');
        var key = jzContrast(col, paper) < 2.5 ? tr2_first([sc.accent, sc.accent2, sc.ghostB, sc.ghostA], function (c) { return jzContrast(c, paper) >= 2.2 && jzContrast(c, col) >= 1.8; }, '#111111') : null;
        var edge = jzMixHex(paper, '#000000', 0.16);
        if (!tr2_isVert(L) || jzCount(tr2_doc(L).text) <= 4) jzXf(L, 'ADBE Rotate Z').setValue(tr2_sv(L, 'ADBE Rotate Z') + tr2_p(P, 'rot', 3) * (tr2_mi(o) % 2 ? -0.7 : 1));
        // the paper (keyline / paper ring / edge / shadow) is drawn on the main pass only: a copy under the lyric kept out of
        // the ghosts; when copies can't follow a random motion it is grown on the lyric itself
        var H2 = L, pc = key || paper;
        if (!tr2_rnd(L)) { H2 = tr2_dup(L, 'JZ Sticker paper', L); jzNoGhost(H2); tr2_recolor(H2, pc); }
        jzTextDoc(H2, function (td) {
            if (H2 !== L) { td.applyFill = true; td.fillColor = jzHex(pc); }
            td.applyStroke = true; td.strokeColor = jzHex(pc); td.strokeWidth = key ? s * 0.07 : s * k;
            try { td.strokeOverFill = false; } catch (e) {}
        });
        if (key) tr2_ring(H2, 'JZ Sticker paper', paper, s * (k / 2 - 0.035));
        tr2_ring(H2, 'JZ Sticker edge', edge, s * 0.008);
        tr2_shadow(H2, 'JZ Sticker shadow', '#000000', s * 0.015, s * tr2_p(P, 'sh', 0.042), s * 0.1, dk ? 0.6 : 0.32);
    }
});

/* ---- gradientSweep \u2014 \u5149\u6CA2\u30B9\u30A4\u30FC\u30D7: a glint band sweeps down through the letters, or a tide line bobs (animated bands through the lyric) */
jzReg('treat', 'gradientSweep', {
    plan: function (rng, st) { return { v: rng.pick(['glint', 'glint', 'tide']), per: rng.range(1.5, 2.3), ph: rng.range(0, 1) }; },
    apply: function (ctx, L, P, o) {
        if (!tr2_alive(L)) return;
        var sc = ctx.sc, col = jzTextColor(L), G = tr2_lay(ctx, L), s = G.fs, far = tr2_far(ctx, L), cx = G.box.cx, i, k;
        if (!G.rows.length) return;
        var T = 'var t=time/' + jzN(tr2_p(P, 'per', 1.9)) + '+' + jzN(tr2_p(P, 'ph', 0)) + ';';
        var F = tr2_fillLayer(ctx, L, 'JZ Sweep fill');
        if (P.v === 'tide') {
            var c2 = tr2_accentFor(sc, col, 1.5), line = jzMixHex(c2, '#FFFFFF', 0.45);
            for (i = 0; i < G.rows.length; i++) {
                var lim = tr2_rowLim(G, i, far), y0 = G.rows[i].cy - s * 0.5;
                var E = T + 'var u=0.52+0.3*Math.sin(t*Math.PI*2),B=' + jzN(lim[1]) + ',y=Math.min(B,' + jzN(y0) + '+(u-0.02)*' + jzN(s) + '),y2=Math.min(B,y+' + jzN(s * 0.03) + ');';
                // (each rect is configured before its fill is added \u2014 adding the fill invalidates the rect reference in AE)
                var gl = jzGrp(F, 'tide line ' + (i + 1)), rl = jzAddRect(gl, 1, 1);
                rl.property('ADBE Vector Rect Size').expression = E + '[' + jzN(far * 2) + ',y2-y]';
                rl.property('ADBE Vector Rect Position').expression = E + '[' + jzN(cx) + ',(y+y2)/2]';
                jzAddFill(gl, line);
                var gw = jzGrp(F, 'tide ' + (i + 1)), rw = jzAddRect(gw, 1, 1);
                rw.property('ADBE Vector Rect Size').expression = E + '[' + jzN(far * 2) + ',B-y2]';
                rw.property('ADBE Vector Rect Position').expression = E + '[' + jzN(cx) + ',(y2+B)/2]';
                jzAddFill(gw, c2);
            }
        } else {
            var hl = jzLum(col) > 0.6 ? tr2_first([sc.accent, sc.accent2, sc.ghostA, sc.ghostB], function (c) { return jzLum(c) > 0.3 && jzContrast(c, col) >= 1.3; }, jzMixHex(col, sc.bg, 0.45)) : jzMixHex(col, '#FFFFFF', 0.72);
            var w = 0.13, m = 5, U = T + 'var u=(t%1)*1.7-0.35;';
            for (i = 0; i < G.rows.length; i++) {
                var g = jzGrp(F, 'glint ' + (i + 1)), y00 = G.rows[i].cy - s * 0.5;
                // soft rise (u-w..u), flat highlight (u..u+0.35w), soft fall (..u+1.2w) \u2014 offsets relative to the band origin
                for (k = 0; k < m; k++) {
                    var sub = tr2_sub(g, 'rise ' + (k + 1)); jzAddRect(sub, far * 2, w * s / m + 0.5, 0, cx, (-w + w * (k + 0.5) / m) * s); jzAddFill(sub, jzMixHex(col, hl, (k + 0.5) / m));
                    var sub2 = tr2_sub(g, 'fall ' + (k + 1)); jzAddRect(sub2, far * 2, 0.85 * w * s / m + 0.5, 0, cx, (0.35 * w + 0.85 * w * (k + 0.5) / m) * s); jzAddFill(sub2, jzMixHex(hl, col, (k + 0.5) / m));
                }
                var sub3 = tr2_sub(g, 'flat'); jzAddRect(sub3, far * 2, 0.35 * w * s + 0.5, 0, cx, 0.175 * w * s); jzAddFill(sub3, hl);
                jzGX(g).property('ADBE Vector Position').expression = U + '[0,' + jzN(y00) + '+u*' + jzN(s) + ']';
                jzGX(g).property('ADBE Vector Group Opacity').expression = U + '(u-' + w + '>1.02||u+' + jzN(1.2 * w) + '<-0.02)?0:100';
            }
        }
        var gb = jzGrp(F, 'base'); jzAddRect(gb, far * 2, far * 2, 0, cx, G.box.cy); jzAddFill(gb, col);
    }
});

// ================================================================ spacing / grid
/* ---- kerningWide \u2014 \u5B57\u9593\u5E83\u3081: letter-spaced, a size smaller (tracking; leading for vertical text) */
jzReg('treat', 'kerningWide', {
    plan: function (rng, st) { return { t: rng.range(0.32, 0.55), grow: rng.range(1.04, 1.14) }; },
    apply: function (ctx, L, P, o) {
        var txt = tr2_doc(L).text;
        if (!jzTrim(String(txt || '')) || jzCount(txt) < 2) return;
        var vert = tr2_isVert(L), Sc = tr2_sv(L, 'ADBE Scale'), kx = Math.abs(Sc[0]) / 100, ky = Math.abs(Sc[1]) / 100, t = tr2_p(P, 't', 0.43), grow = tr2_p(P, 'grow', 1.09);
        var r0 = jzRect(L), a0 = vert ? r0.height * ky : r0.width * kx;
        tr2_reanchor(L, function () {
            jzTextDoc(L, function (td) { tr2_addTrack(td, vert ? t * 0.7 : t, vert); });
            var r1 = jzRect(L), a1 = vert ? r1.height * ky : r1.width * kx, cap = (vert ? ctx.H : ctx.W) * 0.9;
            var k = Math.min(1, a0 * grow / Math.max(1, a1));
            if (a1 * k > cap && a0 <= cap) k = Math.min(k, cap / a1);
            k = Math.max(0.55, k);
            if (k < 0.999) jzTextDoc(L, function (td) { td.fontSize = td.fontSize * k; if (td.autoLeading === false) td.leading = td.leading * k; });
        });
    }
});

/* ---- monoGrid \u2014 \u539F\u7A3F\u7528\u7D19\u98A8: glyphs snap into equal square cells of a ruled grid (tint row, rules, ruby gutter, cell dividers) */
jzReg('treat', 'monoGrid', {
    plan: function (rng, st) { return { pitch: rng.range(1.18, 1.26), c: rng.chance(0.65) ? 1 : 0 }; },
    apply: function (ctx, L, P, o) {
        if (!tr2_alive(L, 0.9)) return;
        var sc = ctx.sc, col = jzTextColor(L), vert = tr2_isVert(L), pitch = tr2_p(P, 'pitch', 1.22), kk = tr2_k(L), i, j;
        var lc = P.c ? tr2_first([sc.accent, sc.accent2], function (c) { return jzContrast(c, sc.bg) >= 1.5; }, sc.sub || sc.fg) : (sc.sub || sc.fg);
        var td0 = tr2_doc(L), rowsT = String(td0.text).split(/\r\n|\r|\n/), nMax = 1, Sc = tr2_sv(L, 'ADBE Scale');
        if (vert) nMax = jzCount(td0.text); else for (i = 0; i < rowsT.length; i++) nMax = Math.max(nMax, jzChars(rowsT[i]).length);
        var r0 = jzRect(L), along0 = vert ? r0.height * Math.abs(Sc[1]) / 100 : r0.width * Math.abs(Sc[0]) / 100;
        var along1 = nMax * pitch * td0.fontSize * Math.abs(vert ? Sc[1] : Sc[0]) / 100;
        tr2_reanchor(L, function () {
            jzTextDoc(L, function (td) {
                if (along1 > along0 * 1.06) td.fontSize = td.fontSize * along0 * 1.06 / along1;
                var f = td.fontSize, cur = (td.autoLeading === false && td.leading > 0) ? td.leading : f * 1.2;
                td.autoLeading = false; td.leading = vert ? pitch * f : Math.max(cur, (pitch + 0.16) * f);
            });
        });
        var G = tr2_lay(ctx, L), s = G.fs, cell = pitch * s, lines = tr2_lines(G), offs = [], scs = [];
        var LEFT = ParagraphJustification.LEFT_JUSTIFY, RIGHT = ParagraphJustification.RIGHT_JUSTIFY;
        var tgt = function (ci, n) { return G.just === LEFT ? (ci + 0.5) * cell : (G.just === RIGHT ? -(n - ci - 0.5) * cell : (ci - (n - 1) / 2) * cell); };
        for (i = 0; i < G.n; i++) { offs.push([0, 0]); scs.push(0.86); }
        if (!vert) for (j = 0; j < lines.length; j++) for (i = 0; i < lines[j].length; i++) offs[lines[j][i].i] = [tgt(i, lines[j].length) - lines[j][i].cx, 0];
        jzCharScales(L, scs, 'JZ Grid size');
        if (!vert) jzCharOffsets(L, offs, 'JZ Grid snap');
        if (G.glyphs.length < 2 || tr2_onPlate(sc, col)) return;
        var HD = tr2_head(ctx, L, o), Q = HD + 'var q=oc(cl((time-DL)/0.45))*(1-ic(PO));';
        var tint = jzMixHex(sc.bg, lc, tr2_dark(sc.bg) ? 0.1 : 0.08), lw = Math.max(ctx.u / kk, s * 0.013), gut = cell * 0.24;
        var S = jzNoGhost(tr2_shapeOn(ctx, L, 'JZ Grid', false, Q + TR2_POP + '*(q>0.01?q:0)'));
        // one cell row per text row (vertical: one column); u = along the row, v = across it
        var runs = [];
        if (vert) { var c0v = G.glyphs[0].cy - cell / 2; runs.push({ n: G.glyphs.length, c0: c0v, cross: G.spans[0].c }); }
        else for (j = 0; j < G.rows.length; j++) { var n = G.rows[j].c.length; runs.push({ n: n, c0: tgt(0, n) - cell / 2, cross: G.rows[j].cy }); }
        var P2 = function (u, v) { return vert ? [v, u] : [u, v]; };
        for (j = 0; j < runs.length; j++) {
            var R = runs[j], len = R.n * cell, v0 = R.cross - cell / 2, v1 = R.cross + cell / 2, nm = ' ' + (j + 1);
            var gr = jzGrp(S, 'rules' + nm);
            jzAddPath(gr, [P2(R.c0, v0), P2(R.c0 + len, v0)], false); jzAddPath(gr, [P2(R.c0, v1), P2(R.c0 + len, v1)], false);
            jzAddStroke(gr, lc, lw, 85); jzAddTrimPaths(gr, Q + 'q*100');
            var gg = jzGrp(S, 'gutter' + nm), vg = vert ? v1 + gut : v0 - gut;
            jzAddPath(gg, [P2(R.c0 - cell * 0.1, vg), P2(R.c0 + len + cell * 0.1, vg)], false);
            jzAddStroke(gg, lc, lw, 55); jzAddTrimPaths(gg, Q + 'q*100');
            var gd = jzGrp(S, 'cells' + nm);
            jzAddPath(gd, [P2(R.c0, v0), P2(R.c0, v1)], false); jzAddStroke(gd, lc, lw, 85);
            var rp = tr2_rep(gd, R.n + 1, vert ? 0 : cell, vert ? cell : 0);
            rp.property('ADBE Vector Repeater Copies').expression = Q + 'Math.min(' + (R.n + 1) + ',Math.floor(' + R.n + '*q+' + jzN(0.5 / cell) + ')+1)';
        }
        // tinted rows under all rules (later groups draw below)
        for (j = 0; j < runs.length; j++) {
            // (the rect is configured before the fill is added \u2014 adding the fill invalidates rt in AE)
            var R2 = runs[j], len2 = R2.n * cell, gt = jzGrp(S, 'tint ' + (j + 1)), rt = jzAddRect(gt, 1, 1);
            rt.property('ADBE Vector Rect Size').expression = Q + 'var l=' + jzN(len2) + '*q;' + (vert ? '[' + jzN(cell) + ',l]' : '[l,' + jzN(cell) + ']');
            rt.property('ADBE Vector Rect Position').expression = Q + 'var l=' + jzN(len2) + '*q;' + (vert ? '[' + jzN(R2.cross) + ',' + jzN(R2.c0) + '+l/2]' : '[' + jzN(R2.c0) + '+l/2,' + jzN(R2.cross) + ']');
            jzAddFill(gt, tint, 90);
        }
    }
});

// ================================================================ offset copies
/* ---- outlineOffset \u2014 \u7248\u30BA\u30EC\u888B\u6587\u5B57: hollow outline on top, solid accent fill knocked off-register behind (filled copy) */
jzReg('treat', 'outlineOffset', {
    plan: function (rng, st) { return { d: rng.range(0.055, 0.08), dir: rng.pick([[1, 1], [1, 1], [-1, 1], [1, 0.35], [0.4, 1]]), k: rng.range(0.022, 0.03) }; },
    apply: function (ctx, L, P, o) {
        if (!tr2_alive(L)) return;
        var sc = ctx.sc, col = jzTextColor(L), fc = tr2_accentFor(sc, col, 1.4), s = jzFontSize(L), dir = P.dir || [1, 1], d = s * tr2_p(P, 'd', 0.065);
        var D = jzNoGhost(tr2_copy(L, 'JZ Offset fill', L, fc, 0, dir[0] * d, dir[1] * d, true));
        jzTextDoc(L, function (td) { td.applyFill = false; td.applyStroke = true; td.strokeColor = jzHex(col); td.strokeWidth = Math.max(1.4 * ctx.u / tr2_k(L), s * tr2_p(P, 'k', 0.026)); });
        tr2_recolor(L, col);
        if (tr2_rnd(L)) tr2_gate(D, tr2_head(ctx, L, o));
    }
});

/* ---- toneShadow \u2014 \u30C8\u30FC\u30F3\u5F71: an offset shadow printed as dots / hatching / stripes (pattern through a shifted filled copy) */
jzReg('treat', 'toneShadow', {
    plan: function (rng, st) { return { v: rng.pick(['dots', 'dots', 'hatch', 'stripes']), d: rng.range(0.09, 0.12), dir: rng.pick([[1, 1], [1, 1], [-1, 1], [1, 0.6]]) }; },
    apply: function (ctx, L, P, o) {
        if (!tr2_alive(L)) return;
        var sc = ctx.sc, col = jzTextColor(L), s = jzFontSize(L), dir = P.dir || [1, 1], d = s * tr2_p(P, 'd', 0.105), kk = tr2_k(L);
        var tc = tr2_first([sc.accent, sc.accent2, sc.fg, sc.ink], function (c) { return jzContrast(c, sc.bg) >= 2.2 && jzContrast(c, col) >= 1.3; }, tr2_markCol(sc, col));
        var G = tr2_lay(ctx, L), m = s * 0.6, bx = { x0: G.box.x0 + dir[0] * d - m, x1: G.box.x1 + dir[0] * d + m, y0: G.box.y0 + dir[1] * d - m, y1: G.box.y1 + dir[1] * d + m };
        var M = tr2_copy(L, 'JZ Tone matte', L, '#FFFFFF', 0, dir[0] * d, dir[1] * d, true);
        var F = tr2_shapeOn(ctx, L, 'JZ Tone shadow', false, tr2_rnd(L) ? tr2_head(ctx, L, o) + '100*' + TR2_REST : null);
        tr2_pattern(ctx, F, P.v || 'dots', tc, s, bx, kk);
        tr2_matte(M, F); jzNoGhost(F);
    }
});

// ================================================================ per-glyph light
/* ---- fadeChars \u2014 \u4F59\u97FB: glyph opacity trails off along the line (tail / head / both ends) */
jzReg('treat', 'fadeChars', {
    plan: function (rng, st) { return { v: rng.pick(['tail', 'tail', 'both', 'head']), lo: rng.range(0.3, 0.4) }; },
    apply: function (ctx, L, P, o) {
        if (!tr2_alive(L, 0.9)) return;
        var G = tr2_lay(ctx, L), lines = tr2_lines(G), lo = tr2_p(P, 'lo', 0.35), amt = [], i, j, any = false;
        for (i = 0; i < G.n; i++) amt.push(0);
        for (j = 0; j < lines.length; j++) {
            var n = lines[j].length; if (n < 3) continue;
            for (i = 0; i < n; i++) {
                var u = i / (n - 1), f = P.v === 'both' ? Math.pow(Math.abs(u - 0.5) * 2, 1.4) : (P.v === 'head' ? (1 - u) * (1 - u) : u * u);
                amt[lines[j][i].i] = (1 - lo) * f * 100; any = true;
            }
        }
        if (any) jzAnimator(L, 'JZ Fade chars', [['ADBE Text Opacity', 0]], 'var a=' + tr2_arr(amt) + ';a[textIndex-1]||0');
    }
});

/* ---- cutShift \u2014 \u65AD\u3061\u5207\u308A: every glyph sliced through, the lower (vertical: right) half slides off; optional accent cut line */
jzReg('treat', 'cutShift', {
    plan: function (rng, st) { return { at: rng.range(-0.08, 0.06), d: rng.range(0.11, 0.16) * (rng.chance(0.5) ? 1 : -1), line: rng.chance(0.6) }; },
    apply: function (ctx, L, P, o) {
        if (!tr2_alive(L)) return;
        var sc = ctx.sc, col = jzTextColor(L), lc = tr2_accentFor(sc, col, 1.6), G = tr2_lay(ctx, L), s = G.fs, vert = G.vert, at = tr2_p(P, 'at', 0), dd = s * tr2_p(P, 'd', 0.13);
        var HD = tr2_head(ctx, L, o), far = tr2_far(ctx, L), rnd = tr2_rnd(L), i;
        if (!G.rows.length) return;
        var Q = HD + 'var q=ob(cl((time-DL-' + jzN((ctx.cut.inDur || 0.3) * 0.55) + ')/0.3),2.2)*(1-ic(PO));';
        // bottom (vertical: right) halves: a copy cut by masks, slid by a Transform effect
        var D = tr2_dup(L, 'JZ Cut half', L), bx = G.box, cuts = [], mk;
        if (vert) cuts.push([bx.cx - far, bx.cx + far, G.spans[0].c + at * s]);
        else for (i = 0; i < G.rows.length; i++) { var lim = tr2_rowLim(G, i, far); cuts.push([lim[0], lim[1], G.rows[i].cy + at * s]); }
        for (i = 0; i < cuts.length; i++) {
            var c = cuts[i];
            if (vert) { jzMaskRect(D, c[2], bx.y0 - far, c[2] + far, bx.y1 + far); mk = jzMaskRect(L, c[2], bx.y0 - far, c[2] + far, bx.y1 + far); }
            else { jzMaskRect(D, bx.cx - far, c[2], bx.cx + far, c[1]); mk = jzMaskRect(L, bx.cx - far, c[2], bx.cx + far, c[1]); }
            mk.maskMode = MaskMode.SUBTRACT;
            if (rnd) jzSetExpr(mk.property('ADBE Mask Opacity'), HD + '(time>DL+IN*1.02&&PO<=0)?100:0');
        }
        var tf = jzEffect(D, 'ADBE Geometry2', 'JZ Cut slide');
        jzEP(tf, 1, [bx.cx, bx.cy]);
        jzEX(tf, 2, Q + (vert ? '[' + jzN(bx.cx) + ',' + jzN(bx.cy) + '+' + jzN(dd) + '*q]' : '[' + jzN(bx.cx) + '+' + jzN(dd) + '*q,' + jzN(bx.cy) + ']'));
        if (rnd) jzEX(tf, 9, HD + '(time>DL+IN*1.02&&PO<=0)?100:0');
        if (P.line) {
            var S = jzNoGhost(tr2_shapeOn(ctx, L, 'JZ Cut line', true, HD + TR2_POP)), lw = Math.max(1.2 * ctx.u / tr2_k(L), s * 0.012);
            for (i = 0; i < G.spans.length; i++) {
                // (the rect is configured before the fill is added \u2014 adding the fill invalidates r in AE)
                var Sp = G.spans[i], cp = Sp.c + at * s, g = jzGrp(S, 'cut ' + (i + 1)), r = jzAddRect(g, 1, 1);
                var E = Q + 'var ex=' + jzN(s * 0.35) + '*q,d=' + jzN(dd) + '*q,a0=' + jzN(Sp.a0) + '-ex,a1=' + jzN(Sp.a1) + '+ex+d,l=Math.max(0,a1-a0);';
                r.property('ADBE Vector Rect Size').expression = E + (vert ? '[' + jzN(lw) + ',l]' : '[l,' + jzN(lw) + ']');
                r.property('ADBE Vector Rect Position').expression = E + (vert ? '[' + jzN(cp) + ',(a0+a1)/2]' : '[(a0+a1)/2,' + jzN(cp) + ']');
                jzAddFill(g, lc);
                jzGX(g).property('ADBE Vector Group Opacity').expression = Q + 'q>0.02?Math.min(1,q)*100:0';
            }
        }
    }
});

/* ---- focusPull \u2014 \u307C\u304B\u3057\u9001\u308A: a band of sharpness travels through the line, the rest is soft (Blur + Opacity animator) */
jzReg('treat', 'focusPull', {
    plan: function (rng, st) { return { b: rng.range(0.035, 0.05), rev: rng.chance(0.3) }; },
    apply: function (ctx, L, P, o) {
        if (!tr2_alive(L, 0.9)) return;
        var cut = ctx.cut, N = jzGlyphs(tr2_doc(L).text).length, whole = jzCount(cut.text), mi = o ? o.mi : null, pos;
        if (N >= 3) pos = '(textIndex-1)/Math.max(1,textTotal-1)';
        else if (whole >= 3 && mi != null && mi === Math.round(mi) && mi < whole) pos = jzN(mi / (whole - 1));
        else return;
        // AE Blur = 2x the browser's CSS blur radius (the same sigma)
        var B = 2 * jzFontSize(L) * tr2_p(P, 'b', 0.042), t0 = (cut.inDur || 0.3) * 0.4, T = Math.max(0.4, cut.dur - (cut.outDur || 0) - t0);
        jzAnimator(L, 'JZ Focus pull', [['ADBE Text Blur', [B, B]], ['ADBE Text Opacity', 65]], tr2_head(ctx, L, o) +
            'var f0=cl((time-' + jzN(t0) + ')/' + jzN(T) + '),f=' + (P.rev ? '1.15-1.3*f0' : '-0.15+1.3*f0') + ';var d=cl(Math.abs(' + pos + '-f)*2.4-0.2);d<=0.02?0:d*100');
    }
});

/* ---- spotChar \u2014 \u4E00\u5B57\u30DE\u30FC\u30AF: one character (a kanji first) set larger in an accent disc / square / diamond */
jzReg('treat', 'spotChar', {
    plan: function (rng, st) { return { v: rng.pick(['disc', 'disc', 'square', 'diamond']), k: rng.range(1.06, 1.14), r: rng.range(0, 1) }; },
    apply: function (ctx, L, P, o) {
        if (!tr2_alive(L, 0.9) || ctx.tr2Spot) return;
        var gl = jzGlyphs(tr2_doc(L).text), gs = [], i, k = tr2_p(P, 'k', 1.1), rr = tr2_p(P, 'r', 0.5);
        for (i = 0; i < gl.length; i++) if (!tr2_isSp(gl[i]) && !jzIsPunct(gl[i])) gs.push({ ch: gl[i], i: i });
        if (!gs.length) return;
        var choose = function (list) {
            var ka = [], kt = [], j;
            for (j = 0; j < list.length; j++) { if (jzIsKanji(list[j].ch)) ka.push(list[j]); else if (jzIsKata(list[j].ch)) kt.push(list[j]); }
            var pool = ka.length ? ka : (kt.length ? kt : list);
            return pool[Math.floor(rr * pool.length) % pool.length];
        };
        var T;
        if (gs.length === 1) {
            // one-glyph items: only the one holding the chosen glyph of the whole lyric
            var all = [], cs = jzChars(String(ctx.cut.text)), idx = 0;
            for (i = 0; i < cs.length; i++) { if (tr2_isSp(cs[i])) continue; if (!jzIsPunct(cs[i])) all.push({ ch: cs[i], i: idx }); idx++; }
            if (!all.length) return;
            var pick = choose(all);
            if (!o || o.mi !== pick.i || gs[0].ch !== pick.ch) return;
            T = gs[0].i;
        } else T = choose(gs).i;
        ctx.tr2Spot = true;
        var sc = ctx.sc, col = jzTextColor(L);
        if (tr2_onPlate(sc, col)) return;
        var pc = tr2_first([sc.accent, sc.accent2, sc.ink, sc.fg], function (c) { return jzContrast(c, sc.bg) >= 2 && jzContrast(c, col) >= 1.4; }, tr2_accentFor(sc, col, 1.6));
        var tc = tr2_textOn(pc, col, sc), HD = tr2_head(ctx, L, o);
        var Q = HD + 'var q=ob(cl((time-DL-' + jzN((ctx.cut.inDur || 0.3) * 0.5) + ')/0.3),1.8)*(1-ic(cl(PO*1.3)));';
        jzAnimator(L, 'JZ Spot size', [['ADBE Text Scale 3D', [k * 100, k * 100, 100]]], 'textIndex===' + (T + 1) + '?100:0');
        if (tc !== col) jzAnimator(L, 'JZ Spot text', [['ADBE Text Fill Color', jzHex(tc)]], Q + '(textIndex===' + (T + 1) + '&&q>0.5)?100:0');
        var G = tr2_lay(ctx, L), g = G.chars[T];
        if (!g) return;
        var s = G.fs * k, S = tr2_shapeOn(ctx, L, 'JZ Spot', false, HD + TR2_POP), grp = jzGrp(S, 'spot'), X = jzGX(grp);
        if (P.v === 'disc' || !P.v) { jzAddEllipse(grp, s * 1.28, s * 1.28); jzAddFill(grp, pc); }
        else { var h = s * (P.v === 'diamond' ? 0.68 : 0.6); jzAddRect(grp, h * 2, h * 2); jzAddFill(grp, pc); X.property('ADBE Vector Rotation').expression = Q + jzN(P.v === 'diamond' ? 45 : -6) + '+(1-q)*40'; }
        X.property('ADBE Vector Position').setValue([g.cx, g.cy]);
        X.property('ADBE Vector Scale').expression = Q + '[100*q,100*q]';
    }
});

/* ---- ransom \u2014 \u5207\u308A\u8CBC\u308A\u6587\u5B57: every glyph on its own scrap of paper, tilted and resized, popping in one by one */
jzReg('treat', 'ransom', {
    plan: function (rng, st) { return { s: rng.int(1, 1e6) }; },
    apply: function (ctx, L, P, o) {
        if (!tr2_alive(L, 0.9)) return;
        var sc = ctx.sc, col = jzTextColor(L), s0 = jzHash(tr2_p(P, 's', 1), tr2_mi(o), 53), i, j, q;
        if (tr2_onPlate(sc, col)) return;
        var plates = [], cand = [sc.ink, sc.fg, sc.accent, sc.accent2, sc.sub, sc.ghostB];
        for (i = 0; i < cand.length; i++) {
            var c = cand[i], ok = !!c && jzContrast(c, sc.bg) >= 1.5;
            for (j = 0; ok && j < plates.length; j++) if (jzContrast(plates[j], c) < 1.2) ok = false;
            if (ok) plates.push(c);
        }
        if (!plates.length) plates.push(jzFitContrast(sc.accent, sc.bg, 2));
        var G = tr2_lay(ctx, L), N = G.n, fs = G.fs, HD = tr2_head(ctx, L, o), R = [], rots = [], scs = [], offs = [], tcs = [];
        var rs = function (a, b) { return jzR(s0, a, b) * 2 - 1; };
        for (i = 0; i < N; i++) {
            var pc = plates[jzHash(s0, i, 1) % plates.length], altL = [sc.bg, col, sc.fg, sc.ink, '#111111', '#FFFFFF'], alt2 = [];
            for (j = 0; j < altL.length; j++) if (altL[j] && altL[j] !== pc) alt2.push(altL[j]);
            var r = { pc: pc, tc: jzContrast(col, pc) >= 3 && jzR(s0, i, 2) < 0.5 ? col : tr2_best(alt2, pc), rot: rs(i, 3) * 8, s: 0.84 + 0.18 * jzR(s0, i, 4), dy: rs(i, 5) * 0.05,
                pad: [0.06 + 0.1 * jzR(s0, i, 6), 0.06 + 0.1 * jzR(s0, i, 7), 0.06 + 0.1 * jzR(s0, i, 8), 0.06 + 0.1 * jzR(s0, i, 9)], j: [rs(i, 10) * 0.06, rs(i, 11) * 0.06, rs(i, 12) * 0.06, rs(i, 13) * 0.06] };
            var sp = !G.chars[i] || G.chars[i].sp;
            R.push(sp ? null : r);
            rots.push(sp ? 0 : r.rot); scs.push(sp ? 1 : r.s); offs.push([0, sp ? 0 : r.dy * fs]);
        }
        var Qf = function (gi) { return 'var q=ob(cl((time-DL-' + gi + '*0.03)/0.22),1.5)*(1-ic(cl(PO*1.4-' + gi + '*0.03)));'; };
        jzCharRotations(L, rots, 'JZ Ransom tilt');
        jzCharScales(L, scs, 'JZ Ransom size');
        jzCharOffsets(L, offs, 'JZ Ransom place');
        // text colour per glyph once its scrap is up (one animator per colour)
        for (i = 0; i < N; i++) if (R[i] && R[i].tc !== col && jzIndexOf(tcs, R[i].tc) < 0) tcs.push(R[i].tc);
        for (j = 0; j < tcs.length; j++) {
            var fl = []; for (i = 0; i < N; i++) fl.push(R[i] && R[i].tc === tcs[j] ? 1 : 0);
            jzAnimator(L, 'JZ Ransom ink ' + (j + 1), [['ADBE Text Fill Color', jzHex(tcs[j])]], HD + Qf('(textIndex-1)') + 'var F=' + tr2_arr(fl) + ';(F[textIndex-1]&&q>0.5)?100:0');
        }
        var S = tr2_shapeOn(ctx, L, 'JZ Ransom scraps', false, HD + TR2_POP), S2 = fs;
        for (i = 0; i < N; i++) {
            var rr = R[i], g = G.chars[i]; if (!rr || !g) continue;
            var w = Math.max(g.w, fs * 0.55) / 2, h = fs / 2, p = rr.pad, jj = rr.j, grp = jzGrp(S, 'scrap ' + (i + 1));
            jzAddPath(grp, [[-w - p[0] * S2, -h - p[1] * S2 + jj[0] * S2], [w + p[2] * S2, -h - p[1] * S2 + jj[1] * S2], [w + p[2] * S2 + jj[2] * S2, h + p[3] * S2], [-w - p[0] * S2 + jj[3] * S2, h + p[3] * S2]], true);
            jzAddFill(grp, rr.pc);
            q = jzGX(grp);
            q.property('ADBE Vector Position').setValue([g.cx, g.cy + rr.dy * fs]);
            q.property('ADBE Vector Rotation').setValue(rr.rot);
            q.property('ADBE Vector Scale').expression = HD + Qf(i) + '[' + jzN(rr.s * 100) + '*q,' + jzN(rr.s * 100) + '*q]';
        }
    }
});

// ================================================================ pack typo part 1 (AE port of the typo set): shared helpers + layouts 1-9
// tyKeySplit tyCropGiant tyCross tyBandHide tyRuby tyBaseline tyScaleSteps tyJustify tyIndexTable
//
// Techniques:
// - glyph positions are computed like the browser's layoutText (full-width glyphs = 1 em, latin estimated), and every text
//   layer is anchored on the browser item's origin (aty_text), so rules / marks / labels line up with the AE text.
// - many small labels at arbitrary spots live in ONE text layer (aty_multi: one label per line, moved by a Position animator).
// - helper graphics the browser draws with ghost off are kept out of the tinted ghosts (jzNoGhost).

var ATY_CY = 0.38;     // glyph centre above the baseline (em)
// compact expression header for secondary layers: DUR, OS, OD, PO (exit progress), K (exit fade) + easings
var ATY_FNS = 'function cl(x){return Math.max(0,Math.min(1,x));}function oe(x){x=cl(x);return x>=1?1:1-Math.pow(2,-10*x);}' +
    'function oc(x){x=cl(x);return 1-Math.pow(1-x,3);}function ic(x){x=cl(x);return x*x*x;}function ie(x){x=cl(x);return x<=0?0:Math.pow(2,10*x-10);}' +
    'function ob(x,s){x=cl(x);var c=s+1;return 1+c*Math.pow(x-1,3)+s*Math.pow(x-1,2);}' +
    'function ioc(x){x=cl(x);return x<0.5?4*x*x*x:1-Math.pow(-2*x+2,3)/2;}';
function aty_hd(ctx) {
    var c = ctx.cut, hard = c.outDur != null && c.outDur <= 0, od = hard ? 0.05 : Math.max(0.12, c.outDur || 0.15);
    return 'var DUR=' + jzN(c.dur) + ',OS=' + jzN(hard ? c.dur : c.dur - od) + ',OD=' + jzN(od) + ';' + ATY_FNS + 'var PO=cl((time-OS)/OD),K=1-ic(PO);\n';
}

/* ---------------------------------------------------------------- small helpers (ports of the browser pack's helpers) */
function aty_k(ctx) { return jzU(ctx) / 1080; }                    // browser design px -> comp px
function aty_port(ctx) { return ctx.W < ctx.H; }
function aty_clean(t) { return String(t || '').replace(/[\s\u3000]+/g, ''); }
function aty_slots(t) { return jzChars(jzTrim(String(t || '')).replace(/[\s\u3000]+/g, ' ')); }
function aty_isSp(c) { return c === ' ' || c === '\u3000'; }
function aty_isSmall(c) { return !!c && '\u3041\u3043\u3045\u3047\u3049\u3063\u3083\u3085\u3087\u308E\u30A1\u30A3\u30A5\u30A7\u30A9\u30C3\u30E3\u30E5\u30E7\u30EE\u30F5\u30F6'.indexOf(c) >= 0; }
function aty_isLatinT(t) { return /[A-Za-z]/.test(t) && !/[\u3040-\u30FF\u3400-\u9FFF\uFF00-\uFFEF]/.test(t); }
function aty_roma(t) { var c = aty_clean(t); if (!/[\u3041-\u30FF]/.test(c)) return null; var r = jzRomaji(c); return r ? r.toUpperCase() : null; }
function aty_romaCh(ch) { if (!(jzIsHira(ch) || jzIsKata(ch))) return null; var r = jzRomaji(ch); return r ? r : null; }
function aty_p2(n) { return jzPad(n, 2); }
function aty_str(s) { return '"' + String(s).replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"'; }
function aty_arr(a) { var s = []; for (var i = 0; i < a.length; i++) s.push(jzN(a[i])); return '[' + s.join(',') + ']'; }
function aty_acc(sc) { return jzContrast(sc.accent, sc.bg) >= 1.8 ? sc.accent : sc.fg; }
function aty_subc(sc) { return jzContrast(sc.sub, sc.bg) >= 1.5 ? sc.sub : sc.fg; }
function aty_onCol(sc, fill) {
    var c = [sc.bg, sc.fg, sc.ink, sc.accent, sc.sub], best = null, bv = 0;
    for (var i = 0; i < c.length; i++) { if (!c[i] || String(c[i]).toUpperCase() === String(fill).toUpperCase()) continue; var k = jzContrast(c[i], fill); if (k > bv) { bv = k; best = c[i]; } }
    return bv >= 2.4 ? best : (jzLum(fill) > 0.5 ? '#111111' : '#FFFFFF');
}
function aty_plateCol(sc, pref) { for (var i = 0; i < pref.length; i++) if (pref[i] && jzContrast(pref[i], sc.bg) >= 1.6) return pref[i]; return sc.fg; }
function aty_hair(ctx) { return Math.max(aty_k(ctx), jzU(ctx) * 0.0014); }
function aty_ls(ctx) { var k = aty_k(ctx); return jzClamp(jzU(ctx) * 0.018, 11 * k, 22 * k); }
// J.splitLines, but never leave a line of punctuation only ('\n' separated)
function aty_splitL(t, per) {
    var ls = String(jzSplitLines(String(t), Math.max(1, per))).split('\r'), out = [], i, j, ok;
    for (i = 0; i < ls.length; i++) {
        var cs = jzChars(ls[i]); ok = out.length > 0;
        for (j = 0; j < cs.length && ok; j++) if (!(jzIsPunct(cs[j]) || cs[j] === ' ')) ok = false;
        if (ok) out[out.length - 1] += ls[i]; else out.push(ls[i]);
    }
    return out.join('\n');
}
// main-text lines for a width budget: portrait -> short lines
function aty_mainLines(text, W, H, perL, perP) {
    var t = jzTrim(String(text || '')), n = jzCount(t), per = W < H ? perP : perL;
    if (n <= per) return t;
    return aty_splitL(t, Math.ceil(n / Math.ceil(n / per)));
}
function aty_okCh(c) { return !!c && !aty_isSp(c) && !jzIsPunct(c) && !aty_isSmall(c); }
// index of the key glyph: first kanji of the longest kanji run, else the first plain glyph ('mid': the plain glyph nearest the middle)
function aty_keyIndex(arr, mode) {
    var i, j, d;
    if (mode === 'mid') {
        var mid = Math.floor((arr.length - 1) / 2);
        for (d = 0; d < arr.length; d++) { if (aty_okCh(arr[mid + d])) return mid + d; if (mid - d >= 0 && aty_okCh(arr[mid - d])) return mid - d; }
    }
    var best = -1, bl = 0;
    for (i = 0; i < arr.length; i++) {
        if (!jzIsKanji(arr[i])) continue;
        j = i; while (j < arr.length && jzIsKanji(arr[j])) j++;
        if (j - i > bl) { bl = j - i; best = i; }
        i = j;
    }
    if (best >= 0) return best;
    for (i = 0; i < arr.length; i++) if (aty_okCh(arr[i])) return i;
    return 0;
}
// browser groupLines: join units into L lines of similar glyph count
function aty_groupLines(units, L) {
    var lens = [], tot = 0, out = [], cur = '', acc = 0, i;
    for (i = 0; i < units.length; i++) { lens.push(jzCount(units[i])); tot += lens[i]; }
    for (i = 0; i < units.length; i++) {
        cur += units[i]; acc += lens[i];
        var target = tot * (out.length + 1) / L;
        if (out.length < L - 1 && acc >= target - lens[i] * 0.4 && i < units.length - 1) { out.push(jzTrim(cur)); cur = ''; }
    }
    if (jzTrim(cur)) out.push(jzTrim(cur));
    return out;
}
function aty_words(t) { var ws = jzTrim(String(t)).split(/\s+/), out = []; for (var i = 0; i < ws.length; i++) out.push((i ? ' ' : '') + ws[i]); return out; }

/* ---------------------------------------------------------------- text geometry (like the browser's layoutText) */
function aty_mono(font) { return /mono/.test(String(font || '')); }
function aty_adv(ch, mono) {
    var c = ch.charCodeAt(0);
    if (ch === ' ') return mono ? 0.6 : 0.3;
    if (c < 0x80) {
        if (mono) return 0.6;
        if (/[A-Z]/.test(ch)) return /[MW]/.test(ch) ? 0.9 : (ch === 'I' ? 0.32 : 0.7);
        if (/[a-z]/.test(ch)) return /[mw]/.test(ch) ? 0.88 : /[ijl]/.test(ch) ? 0.28 : /[frt]/.test(ch) ? 0.42 : 0.61;
        if (/[0-9]/.test(ch)) return 0.62;
        return /[.,:;!'|]/.test(ch) ? 0.28 : 0.42;
    }
    if (c < 0x250) return 0.56;
    if (c === 0x2014 || c === 0x2015 || c === 0x2500) return 1;
    if (c < 0x2E80 && c !== 0x2026) return 0.6;
    if (c >= 0xFF61 && c <= 0xFF9F) return 0.5;
    return 1;
}
// glyphs relative to the item origin: { ch, x, y (centre), w, h, li, ci, ti (AE textIndex-1), sp }; .W .H .nL .LD
function aty_lay(str, size, o) {
    o = o || {};
    var tr = o.track || 0, mono = aty_mono(o.font), out = [], ti = 0, j, li, s = String(str).replace(/\r\n|\r/g, '\n');
    if (o.vertical) {
        var arr = jzChars(s.replace(/\n/g, '')), n = arr.length, st = size * (1 + tr), h = n * st - tr * size;
        for (j = 0; j < n; j++) out.push({ ch: arr[j], x: 0, y: -h / 2 + j * st + size / 2, w: size, h: size, li: 0, ci: j, ti: ti++, sp: aty_isSp(arr[j]) });
        out.W = size; out.H = Math.max(size, h); out.nL = 1; out.LD = st;
        return out;
    }
    var lines = s.split('\n'), nL = lines.length, lead = (o.lead || 1.3) * size, maxW = 1;
    for (li = 0; li < nL; li++) {
        var a2 = jzChars(lines[li]), ws = [], w = 0;
        for (j = 0; j < a2.length; j++) { ws.push(aty_adv(a2[j], mono) * size); w += ws[j] + (j < a2.length - 1 ? tr * size : 0); }
        maxW = Math.max(maxW, w);
        var x = o.align === 'left' ? 0 : o.align === 'right' ? -w : -w / 2, y = (li - (nL - 1) / 2) * lead;
        for (j = 0; j < a2.length; j++) { out.push({ ch: a2[j], x: x + ws[j] / 2, y: y, w: ws[j], h: size, li: li, ci: j, ti: ti++, sp: aty_isSp(a2[j]) }); x += ws[j] + tr * size; }
    }
    out.W = maxW; out.H = nL * lead - (lead - size); out.nL = nL; out.LD = lead;
    return out;
}
function aty_meas(str, size, o) { var l = aty_lay(str, size, o); return { w: l.W, h: l.H }; }
function aty_fit(str, font, maxW, maxH, o) { var q = jzCopy(o || {}); q.font = font; var m = aty_meas(str, 100, q); return 100 * Math.min(maxW / Math.max(1, m.w), maxH / Math.max(1, m.h)); }
function aty_tw(str, font, size, track) { return aty_meas(str, size, { font: font, track: track == null ? 0.12 : track }).w; }
// item bbox in comp px (rotation: multiples of 90 only)
function aty_ibb(o) {
    var lay = aty_lay(o.text, o.size, o), x0 = 1e9, x1 = -1e9, y0 = 1e9, y1 = -1e9, i, r = Math.round((o.rot || 0) / 90) % 2 !== 0;
    for (i = 0; i < lay.length; i++) {
        var g = lay[i]; if (g.sp) continue;
        var ax = g.x, ay = g.y, hw = g.w / 2, hh = g.h / 2;
        if (r) { var t = ax; ax = -ay; ay = t; t = hw; hw = hh; hh = t; }
        x0 = Math.min(x0, ax - hw); x1 = Math.max(x1, ax + hw); y0 = Math.min(y0, ay - hh); y1 = Math.max(y1, ay + hh);
    }
    if (x0 > x1) { x0 = x1 = 0; y0 = y1 = 0; }
    return { x0: o.x + x0, x1: o.x + x1, y0: o.y + y0, y1: o.y + y1, cx: o.x + (x0 + x1) / 2, cy: o.y + (y0 + y1) / 2 };
}
// non-space glyph centres in comp px: { ch, x, y, w, h, li, i (non-space order), ti }
function aty_gpts(o) {
    var lay = aty_lay(o.text, o.size, o), out = [];
    for (var i = 0; i < lay.length; i++) { var g = lay[i]; if (g.sp) continue; out.push({ ch: g.ch, x: o.x + g.x, y: o.y + g.y, w: g.w, h: g.h, li: g.li, i: out.length, ti: g.ti }); }
    return out;
}

/* ---------------------------------------------------------------- layer builders */
// a text layer whose browser item origin (o.x, o.y) is the layer position. o: text font size x y align track lead vertical color fill stroke strokeColor alpha rot name
function aty_text(ctx, str, o) {
    var size = o.size, V = !!o.vertical, txt, nL, LD, i;
    if (V) {
        var cs = jzChars(String(str).replace(/[\r\n]/g, ''));
        for (i = 0; i < cs.length; i++) if (cs[i] === ' ') cs[i] = '\u3000';
        txt = cs.join('\r'); nL = cs.length; LD = size * (1 + (o.track || 0));
    } else { txt = String(str).replace(/\r\n|\n/g, '\r'); nL = txt.split('\r').length; LD = size * (o.lead || 1.3); }
    var L = jzText(ctx, txt, { font: o.font, size: size, color: o.color || ctx.sc.fg, x: o.x, y: o.y, align: V ? 'center' : o.align, track: V ? 0 : (o.track || 0), leading: LD,
        fill: o.fill, stroke: o.stroke, strokeColor: o.strokeColor, name: o.name, opacity: o.alpha });
    jzXf(L, 'ADBE Anchor Point').setValue([0, -ATY_CY * size + (nL - 1) / 2 * LD]);
    jzXf(L, 'ADBE Position').setValue([o.x, o.y]);
    if (o.rot) jzXf(L, 'ADBE Rotate Z').setValue(o.rot);
    return L;
}
// the lyric item: layer + the cut's motion + treatment; returns the bbox (o.L = the layer)
function aty_draw(ctx, o) {
    var L = aty_text(ctx, o.text, o);
    jzAnimate(ctx, L, { mi: o.mi || 0 });
    o.L = L;
    return aty_ibb(o);
}
// opacity factor expression (0..1) on a secondary layer (value keeps the static alpha); the layer leaves the ghosts
function aty_op(ctx, L, f) { jzSetExpr(jzXf(L, 'ADBE Opacity'), aty_hd(ctx) + 'value*cl(' + f + ')'); jzNoGhost(L); return L; }
// small annotation label (main pass only); o: font size align track color alpha op (opacity factor expression)
function aty_label(ctx, text, x, y, o) {
    o = o || {};
    var L = aty_text(ctx, String(text), { font: o.font || jzMonoF(ctx), size: o.size || aty_ls(ctx), x: x, y: y, align: o.align || 'left', track: o.track != null ? o.track : 0.12,
        color: o.color || ctx.sc.sub, alpha: o.alpha, name: o.name || ('label ' + String(text).substr(0, 20)) });
    jzNoGhost(L);
    if (o.op) aty_op(ctx, L, o.op);
    return L;
}
// many labels in ONE text layer: strs[j] is placed with its reference point at pts[j] (align: left edge / centre / right edge, vertical centre)
// returns { L, li: line index per AE glyph (textIndex-1) }
function aty_multi(ctx, strs, pts, o) {
    var size = o.size, LD = size * 1.2, li = [], offs = [], j, k;
    var L = jzText(ctx, strs.join('\r'), { font: o.font || jzMonoF(ctx), size: size, color: o.color || ctx.sc.sub, x: 0, y: 0, align: o.align || 'center', track: o.track != null ? o.track : 0.12,
        leading: LD, name: o.name || 'labels', opacity: o.alpha });
    jzXf(L, 'ADBE Anchor Point').setValue([0, 0]); jzXf(L, 'ADBE Position').setValue([0, 0]);
    for (j = 0; j < strs.length; j++) {
        var cs = jzChars(strs[j]);
        for (k = 0; k < cs.length; k++) { li.push(j); offs.push([pts[j][0], pts[j][1] + ATY_CY * size - j * LD]); }
    }
    if (offs.length) jzCharOffsets(L, offs, 'JZ Place');
    jzNoGhost(L);
    return { L: L, li: li };
}
// per-glyph fade of an aty_multi layer: a = factor expression using j (the label's index), e.g. 'oc((time-0.2-j*0.05)/0.3)*K'
function aty_multiFade(ctx, M, a, extra) {
    jzAnimator(M.L, 'JZ Label Fade', [['ADBE Text Opacity', 0]], aty_hd(ctx) + 'var LI=' + aty_arr(M.li) + ';var j=LI[textIndex-1]||0;' + (extra || '') + '(1-cl(' + a + '))*100');
}
// shape layer at 0,0 with one group of polylines; o: width alpha trim start closed fill dash gop op name ghost
function aty_lines(ctx, name, segs, col, o) {
    o = o || {};
    var S = jzShapeLayer(ctx, name, 0, 0);
    aty_grp(S, name, segs, col, o);
    if (o.op) aty_op(ctx, S, o.op);
    if (!o.ghost) jzNoGhost(S);
    return S;
}
// one group (finished before the caller adds the next one)
function aty_grp(S, name, segs, col, o) {
    var g = jzGrp(S, name), i;
    for (i = 0; i < segs.length; i++) jzAddPath(g, segs[i], !!o.closed);
    if (o.fill) jzAddFill(g, o.fill, o.fillOp);
    if (col) {
        var st = jzAddStroke(g, col, o.width || 1, (o.alpha != null ? o.alpha : 1) * 100);
        if (o.dash) {
            var D = st.property('ADBE Vector Stroke Dashes');
            D.addProperty('ADBE Vector Stroke Dash 1').setValue(o.dash[0]);
            D.addProperty('ADBE Vector Stroke Gap 1').setValue(o.dash[1]);
        }
    }
    if (o.trim || o.start) jzAddTrimPaths(g, o.trim, o.start);
    if (o.gop) jzSetExpr(jzGX(g).property('ADBE Vector Group Opacity'), o.gop);
    if (o.gsc) { jzGX(g).property('ADBE Vector Anchor').setValue(o.piv); jzGX(g).property('ADBE Vector Position').setValue(o.piv); jzSetExpr(jzGX(g).property('ADBE Vector Scale'), o.gsc); }
    if (o.gpos) jzSetExpr(jzGX(g).property('ADBE Vector Position'), o.gpos);
}
// filled rectangle (x0, y0 top-left) in a group; o.piv / o.gsc scale about a pivot
function aty_rectGrp(S, name, x0, y0, w, h, col, o) {
    o = o || {};
    var g = jzGrp(S, name);
    jzAddRect(g, w, h, 0, x0 + w / 2, y0 + h / 2);
    jzAddFill(g, col, o.fillOp);
    if (o.gop) jzSetExpr(jzGX(g).property('ADBE Vector Group Opacity'), o.gop);
    if (o.gsc) { jzGX(g).property('ADBE Vector Anchor').setValue(o.piv); jzGX(g).property('ADBE Vector Position').setValue(o.piv); jzSetExpr(jzGX(g).property('ADBE Vector Scale'), o.gsc); }
    if (o.gpos) jzSetExpr(jzGX(g).property('ADBE Vector Position'), o.gpos);
}

/* ================================================================ 1 tyKeySplit \u2014 \u5927\u5B57\u631F\u307F */
jzReg('layout', 'tyKeySplit', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), fs: rng.pick(jzFontsOf(st, ['serif', 'body', 'display'])), mode: rng.pick(['kanji', 'kanji', 'mid']), big: rng.pick(['fill', 'fill', 'accent', 'outline']), rule: rng.chance(0.75) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, port = aty_port(ctx), i;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), fs = jzP(ctx, 'fs', jzSerifF(ctx)), big = jzP(ctx, 'big', 'fill');
        var arr = aty_slots(c.text), ki = aty_keyIndex(arr, jzP(ctx, 'mode', 'kanji')), key = arr[ki] || '?';
        var before = jzTrim(arr.slice(0, ki).join('')), after = jzTrim(arr.slice(ki + 1).join(''));
        if (aty_isLatinT(c.text)) {            // latin: the longest word is the key
            var ws = jzTrim(c.text).split(/\s+/), wi = 0;
            for (i = 0; i < ws.length; i++) if (ws[i].length > ws[wi].length) wi = i;
            key = ws[wi]; before = ws.slice(0, wi).join(' '); after = ws.slice(wi + 1).join(' ');
            ki = ws.slice(0, wi).join(' ').length + (wi ? 1 : 0);
        }
        var bigS = port ? Math.min(W * 0.66, H * 0.36) : Math.min(H * 0.64, W * 0.34);
        bigS = Math.min(bigS, aty_fit(key, font, port ? W * 0.84 : W * 0.5, 1e6));
        var bw = aty_meas(key, bigS, { font: font }).w, gap = bigS * 0.12, per = port ? 6 : 5;
        var bt = before ? aty_splitL(before, per) : '', at = after ? aty_splitL(after, per) : '';
        var o = { track: 0.04, lead: 1.2 }, ss;
        if (port) {
            var avail = (H * 0.86 - bigS) / 2 - gap;
            ss = Math.min(bt ? aty_fit(bt, fs, W * 0.84, avail, o) : 1e9, at ? aty_fit(at, fs, W * 0.84, avail, o) : 1e9, bigS * 0.3);
        } else {
            var side = (W * 0.9 - bw - gap * 2) / ((bt ? 1 : 0) + (at ? 1 : 0) || 1);
            ss = Math.min(bt ? aty_fit(bt, fs, side, bigS * 0.62, o) : 1e9, at ? aty_fit(at, fs, side, bigS * 0.62, o) : 1e9, bigS * 0.3);
        }
        var mB = bt ? aty_meas(bt, ss, { font: fs, track: 0.04, lead: 1.2 }) : { w: 0, h: 0 }, mA = at ? aty_meas(at, ss, { font: fs, track: 0.04, lead: 1.2 }) : { w: 0, h: 0 };
        var bx, by, itB = null, itA = null;
        if (!port) {
            var tot = (bt ? mB.w + gap : 0) + bw + (at ? mA.w + gap : 0), x0 = W / 2 - tot / 2;
            bx = x0 + (bt ? mB.w + gap : 0) + bw / 2; by = H / 2;
            if (bt) itB = { text: bt, font: fs, size: ss, align: 'right', x: bx - bw / 2 - gap, y: by - bigS * 0.46 + mB.h / 2, color: sc.fg, mi: 0, track: 0.04, lead: 1.2 };
            if (at) itA = { text: at, font: fs, size: ss, align: 'left', x: bx + bw / 2 + gap, y: by + bigS * 0.46 - mA.h / 2, color: sc.fg, mi: 4, track: 0.04, lead: 1.2 };
        } else {
            var tot2 = (bt ? mB.h + gap : 0) + bigS + (at ? mA.h + gap : 0), y0 = H / 2 - tot2 / 2;
            bx = W / 2; by = y0 + (bt ? mB.h + gap : 0) + bigS / 2;
            var Lx = W / 2 - Math.max(bw, mB.w, mA.w) / 2, Rx = W / 2 + Math.max(bw, mB.w, mA.w) / 2;
            if (bt) itB = { text: bt, font: fs, size: ss, align: 'left', x: Math.max(W * 0.08, Math.min(Lx, bx - bw / 2)), y: by - bigS / 2 - gap - mB.h / 2, color: sc.fg, mi: 0, track: 0.04, lead: 1.2 };
            if (at) itA = { text: at, font: fs, size: ss, align: 'right', x: Math.min(W * 0.92, Math.max(Rx, bx + bw / 2)), y: by + bigS / 2 + gap + mA.h / 2, color: sc.fg, mi: 4, track: 0.04, lead: 1.2 };
        }
        var hd = aty_hd(ctx);
        if (jzP(ctx, 'rule', true)) {       // hairlines linking the small blocks to the key glyph
            var segs = [], y;
            if (itB) { y = port ? itB.y + mB.h / 2 + gap * 0.5 : itB.y + mB.h / 2 + ss * 0.35; var xa = port ? itB.x : itB.x - mB.w, xb = port ? itB.x + Math.max(mB.w, bw) : bx + bw * 0.5; segs.push([[xa, y], [xb, y]]); }
            if (itA) { y = port ? itA.y - mA.h / 2 - gap * 0.5 : itA.y - mA.h / 2 - ss * 0.35; var xb2 = port ? itA.x : itA.x + mA.w, xa2 = port ? itA.x - Math.max(mA.w, bw) : bx - bw * 0.5; segs.push([[xb2, y], [xa2, y]]); }
            if (segs.length) aty_lines(ctx, 'key rules', segs, sc.sub, { width: aty_hair(ctx), alpha: 0.7, trim: hd + '100*oc((time-0.2)/0.6)*K' });
        }
        var bigO = { text: key, font: font, size: bigS, x: bx, y: by, color: big === 'accent' ? aty_acc(sc) : sc.fg, mi: 2 };
        if (big === 'outline') { bigO.fill = false; bigO.stroke = Math.max(1.5 * aty_k(ctx), bigS * 0.014); bigO.strokeColor = sc.fg; }
        var bb = aty_draw(ctx, bigO);
        if (itB) bb = jzUnion(bb, aty_draw(ctx, itB));
        if (itA) bb = jzUnion(bb, aty_draw(ctx, itA));
        var nG = 0, kiG = 0;
        for (i = 0; i < arr.length; i++) if (!aty_isSp(arr[i])) { nG++; if (i < ki) kiG++; }
        aty_label(ctx, aty_p2(kiG + 1) + ' / ' + aty_p2(nG), bx - bw / 2, by + bigS * 0.56, { size: aty_ls(ctx) * 0.9, op: 'oc((time-0.3)/0.3)*K' });
        return bb;
    }
});

/* ================================================================ 2 tyCropGiant \u2014 \u898B\u5207\u308C\u5927\u6587\u5B57 */
jzReg('layout', 'tyCropGiant', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display'])), fm: rng.pick(jzFontsOf(st, ['display', 'serif'])), edge: rng.pick(['a', 'b']), style: rng.pick(['dim', 'dim', 'outline']), dir: rng.pick([1, -1]) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, port = aty_port(ctx), txt = jzTrim(c.text), k = aty_k(ctx);
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), fm = jzP(ctx, 'fm', font), low = jzP(ctx, 'edge', 'a') === 'a', dir = jzP(ctx, 'dir', 1);
        // the giant copy, cropped by the frame edge (slides in from the edge, drifts, sinks out with the exit)
        var gi = { text: port ? aty_clean(txt) : txt, font: font, name: 'giant copy' }, sg = low ? 1 : -1;
        if (!port) { gi.size = H * 0.66; gi.x = W / 2; gi.y = low ? H + gi.size * 0.03 : -gi.size * 0.03; }
        else { gi.size = W * 0.62; gi.vertical = true; gi.x = low ? W + gi.size * 0.03 : -gi.size * 0.03; gi.y = H / 2; }
        if (jzP(ctx, 'style', 'dim') === 'outline') { gi.fill = false; gi.stroke = Math.max(1.4 * k, gi.size * 0.006); gi.strokeColor = sc.sub; gi.color = sc.sub; gi.alpha = 0.65; }
        else gi.color = jzMixHex(sc.bg, sc.fg, 0.16);
        var G = aty_text(ctx, gi.text, gi); jzNoGhost(G);
        var mv = 'var u=cl(time/' + jzN(Math.max(0.5, c.dur)) + '),g=(1-oc(time/0.8))+ic(PO);';
        if (!port) jzSetExpr(jzXf(G, 'ADBE Position'), aty_hd(ctx) + mv + '[value[0]+(0.5-u)*' + jzN(W * 0.14 * dir) + ',value[1]+g*' + jzN(sg * gi.size * 0.45) + ']');
        else jzSetExpr(jzXf(G, 'ADBE Position'), aty_hd(ctx) + mv + '[value[0]+g*' + jzN(sg * gi.size * 0.45) + ',value[1]+(0.5-u)*' + jzN(H * 0.1 * dir) + ']');
        // the readable lyric on the free side
        var mt = aty_mainLines(txt, W, H, 10, 6), o = { lead: 1.18, track: 0.03 }, it;
        if (!port) it = { text: mt, font: fm, size: Math.min(aty_fit(mt, fm, W * 0.74, H * 0.34, o), H * 0.19), align: 'left', x: W * 0.08, y: low ? H * 0.38 : H * 0.62, color: sc.fg, lead: 1.18, track: 0.03 };
        else it = { text: mt, font: fm, size: Math.min(aty_fit(mt, fm, W * 0.58, H * 0.5, o), W * 0.2), align: low ? 'left' : 'right', x: low ? W * 0.08 : W * 0.92, y: H / 2, color: sc.fg, lead: 1.18, track: 0.03 };
        var bb = aty_draw(ctx, it), m = aty_meas(it.text, it.size, it), ls = aty_ls(ctx);
        var ty = it.y - m.h / 2 - ls * 1.6, x0 = it.align === 'right' ? it.x - m.w : it.x, la = 'oc((time-0.2)/0.35)*K';
        aty_label(ctx, 'No.' + aty_p2((c.line | 0) + 1), x0, ty, { color: aty_acc(sc), op: la });
        aty_lines(ctx, 'no rule', [[[x0 + ls * 4, ty], [x0 + ls * 4 + Math.min(m.w, W * 0.3), ty]]], sc.sub, { width: aty_hair(ctx), alpha: 0.7, trim: aty_hd(ctx) + '100*oe((time-0.25)/0.5)', op: la });
        return bb;
    }
});

/* ================================================================ 3 tyCross \u2014 \u5341\u5B57\u7D44 */
jzReg('layout', 'tyCross', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), mode: rng.pick(['kanji', 'mid', 'mid']), rules: rng.chance(0.8), key: rng.pick(['accent', 'accent', 'fg']) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, M = jzU(ctx), font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display'));
        var arr = aty_slots(c.text), n = arr.length;
        if (!n) return null;
        var ki = aty_keyIndex(arr, jzP(ctx, 'mode', 'mid')), tr = 0.08;
        var A = jzTrim(arr.slice(0, ki).join('')), Kc = arr[ki], B = jzTrim(arr.slice(ki + 1).join(''));
        function w100(s) { return s ? aty_meas(s, 100, { font: font, track: tr }).w : 0; }
        function h100(s) { return s ? aty_meas(s, 100, { font: font, track: tr, vertical: true }).h : 0; }
        var kw = w100(Kc), g100 = 100 * tr + 6;
        var Lx = (A ? w100(A) + g100 : 0) + kw / 2, Rx = (B ? w100(B) + g100 : 0) + kw / 2;
        var Ty = (A ? h100(A) + g100 : 0) + 50, By = (B ? h100(B) + g100 : 0) + 50;
        var size = Math.min(W * 0.86 / ((Lx + Rx) / 100), H * 0.86 / ((Ty + By) / 100), M * 0.2), k = size / 100;
        var cx = W / 2 - (Rx - Lx) * k / 2, cy = H / 2 - (By - Ty) * k / 2, lw = aty_hair(ctx), hd = aty_hd(ctx);
        if (jzP(ctx, 'rules', true)) {
            var e = 'oe((time-0.1)/0.7)*K';
            var S = jzShapeLayer(ctx, 'cross rules', 0, 0);
            aty_grp(S, 'cross', [[[cx - W, cy], [cx + W, cy]], [[cx, cy - H], [cx, cy + H]]], sc.sub, { width: lw, alpha: 0.28, trim: hd + '50+50*' + e, start: hd + '50-50*' + e });
            aty_grp(S, 'circle', [], null, {});
            var gC = S.property('ADBE Root Vectors Group').property('circle');
            jzAddEllipse(gC, size * 1.56, size * 1.56, cx, cy);
            jzAddStroke(gC, sc.sub, lw, 100);
            gC = S.property('ADBE Root Vectors Group').property('circle');
            jzGX(gC).property('ADBE Vector Anchor').setValue([cx, cy]); jzGX(gC).property('ADBE Vector Position').setValue([cx, cy]);
            jzSetExpr(jzGX(gC).property('ADBE Vector Scale'), hd + 'var e=' + e + ';var s=(0.6+0.4*e)*100;[s,s]');
            jzSetExpr(jzGX(gC).property('ADBE Vector Group Opacity'), hd + '50*' + e);
            jzNoGhost(S);
        }
        var g = size * tr + 6 * k, bb = null, base = { font: font, size: size, track: tr, color: sc.fg };
        function it(q) { var r = jzCopy(base); for (var kk in q) if (q.hasOwnProperty(kk)) r[kk] = q[kk]; return r; }
        if (A) bb = jzUnion(bb, aty_draw(ctx, it({ text: A, align: 'right', x: cx - kw * k / 2 - g, y: cy, mi: 0 })));
        bb = jzUnion(bb, aty_draw(ctx, it({ text: Kc, x: cx, y: cy, color: jzP(ctx, 'key', 'accent') === 'accent' ? aty_acc(sc) : sc.fg, mi: 1 })));
        if (B) bb = jzUnion(bb, aty_draw(ctx, it({ text: B, align: 'left', x: cx + kw * k / 2 + g, y: cy, mi: 2 })));
        if (A) bb = jzUnion(bb, aty_draw(ctx, it({ text: A, vertical: true, x: cx, y: cy - size / 2 - g - h100(A) * k / 2, mi: 3 })));
        if (B) bb = jzUnion(bb, aty_draw(ctx, it({ text: B, vertical: true, x: cx, y: cy + size / 2 + g + h100(B) * k / 2, mi: 4 })));
        aty_label(ctx, aty_p2(ki + 1) + '\u00D7' + aty_p2(ki + 1), cx + size * 0.62, cy - size * 0.62, { size: aty_ls(ctx) * 0.9, op: 'oc((time-0.35)/0.3)*K' });
        return bb;
    }
});

/* ================================================================ 4 tyBandHide \u2014 \u5E2F\u96A0\u308C */
jzReg('layout', 'tyBandHide', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display'])), band: rng.pick(['ink', 'accent', 'fg']), pos: rng.pick(['low', 'low', 'high']), speed: rng.range(40, 90), dir: rng.pick([1, -1]), k: rng.range(0.3, 0.38) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, txt = jzTrim(c.text), font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display'));
        var mt = aty_mainLines(txt, W, H, 10, 5), nL = mt.split('\n').length, lead = 1.32;
        var size = Math.min(aty_fit(mt, font, W * 0.8, H * 0.66, { lead: lead, track: 0.02 }), H * 0.3);
        var bb = aty_draw(ctx, { text: mt, font: font, size: size, x: W / 2, y: H / 2, lead: lead, track: 0.02, color: sc.fg });
        var band = jzP(ctx, 'band', 'ink'), pk = jzP(ctx, 'k', 0.34), dir = jzP(ctx, 'dir', 1), speed = jzP(ctx, 'speed', 60) * aty_k(ctx);
        var bandC = aty_plateCol(sc, band === 'accent' ? [sc.accent, sc.ink, sc.fg] : band === 'ink' ? [sc.ink, sc.accent, sc.fg] : [sc.fg, sc.accent]);
        var tc = aty_onCol(sc, bandC), unit = jzTrim(String(c.lineText || txt).replace(/\s+/g, ' ')) + '\u3000\uFF0F\u3000', bf = jzBodyF(ctx);
        var hd = aty_hd(ctx);
        for (var li = 0; li < nL; li++) {
            var yl = H / 2 + (li - (nL - 1) / 2) * size * lead, h = size * (pk + 0.08);
            var y0 = jzP(ctx, 'pos', 'low') === 'low' ? yl + size * (0.5 - pk) : yl - size * 0.58, d = (li % 2 ? -1 : 1) * dir;
            var ex = hd + 'var e=oe((time-' + jzN(0.12 + li * 0.08) + ')/0.5)*(1-ie(PO));';
            // the band grows from its side edge
            var Bd = jzShapeLayer(ctx, 'band ' + (li + 1), d > 0 ? -10 : W + 10, y0 + h / 2), gb = jzGrp(Bd, 'band');
            jzAddRect(gb, W + 20, h, 0, d > 0 ? (W + 20) / 2 : -(W + 20) / 2, 0); jzAddFill(gb, bandC);
            jzSetExpr(jzXf(Bd, 'ADBE Scale'), ex + '[value[0]*e,value[1]]');
            jzNoGhost(Bd);
            // the lyric line running inside the band (clipped to the band by a matte copy of it)
            var fs = h * 0.44, per = aty_tw(unit, bf, fs, 0.12);
            if (per > 1) {
                var reps = Math.min(40, Math.ceil(W * 2 / per) + 2), s = '';
                for (var r = 0; r < reps; r++) s += unit;
                var T = aty_text(ctx, s, { font: bf, size: fs, align: 'left', track: 0.12, x: 0, y: y0 + h / 2, color: tc, name: 'band text ' + (li + 1) });
                jzSetExpr(jzXf(T, 'ADBE Position'), 'var per=' + jzN(per) + ';var off=((time*' + jzN(speed * d) + ')%per+per)%per;[value[0]-per+off,value[1]]');
                jzNoGhost(T);
                var Mt = Bd.duplicate(); Mt.name = 'band matte ' + (li + 1);
                Mt.moveBefore(T); T.trackMatteType = TrackMatteType.ALPHA; Mt.enabled = false; jzNoGhost(Mt);
            }
        }
        return bb;
    }
});

/* ================================================================ 5 tyRuby \u2014 \u30EB\u30D3\u632F\u308A */
jzReg('layout', 'tyRuby', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['serif', 'display'])), idx: rng.chance(0.7), up: rng.chance(0.5) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, txt = jzTrim(c.text), font = jzP(ctx, 'font', jzSerifF(ctx)), k = aty_k(ctx);
        var mt = aty_mainLines(txt, W, H, 9, 5), o = { track: 0.16, lead: 2.0 };
        var size = Math.min(aty_fit(mt, font, W * 0.84, H * 0.62, o), H * 0.2, W * 0.24);
        var it = { text: mt, font: font, size: size, x: W / 2, y: H / 2 + size * 0.1, color: sc.fg, track: 0.16, lead: 2.0 };
        var bb = aty_draw(ctx, it), gl = aty_gpts(it), mono = jzMonoF(ctx), lw = aty_hair(ctx), hd = aty_hd(ctx), i;
        var rs = Math.max(10 * k, size * 0.3), up = jzP(ctx, 'up', false);
        var rStr = [], rPts = [], rIdx = [], fsR = rs, brk = [];
        for (i = 0; i < gl.length; i++) {
            var g = gl[i], ry = g.y - size * 0.74, rt = aty_romaCh(g.ch);
            if (rt) {
                var w0 = aty_tw(rt, mono, rs, 0.02);
                if (w0 > g.w * 1.05) fsR = Math.min(fsR, rs * g.w * 1.05 / w0);
                rStr.push(up ? rt.toUpperCase() : rt); rPts.push([g.x, ry]); rIdx.push(i);
            } else if (jzIsKanji(g.ch)) brk.push(i);
        }
        var aI = 'oc((time-0.18-D*0.045)/0.3)*K';
        if (rStr.length) {     // readings above the kana: one layer, each reading fades in and drops a little into place
            var R = aty_multi(ctx, rStr, rPts, { font: mono, size: fsR, align: 'center', track: 0.02, color: sc.sub, name: 'ruby' });
            var DI = []; for (i = 0; i < R.li.length; i++) DI.push(rIdx[R.li[i]]);
            jzAnimator(R.L, 'JZ Ruby In', [['ADBE Text Opacity', 0], ['ADBE Text Position 3D', [0, -rs * 0.5, 0]]], hd + 'var DI=' + aty_arr(DI) + ';var D=DI[textIndex-1]||0;(1-cl(' + aI + '))*100');
        }
        var S = jzShapeLayer(ctx, 'ruby marks', 0, 0), ac = aty_acc(sc);
        for (var b = 0; b < brk.length; b++) {    // kanji: a small bracket mark
            var gk = gl[brk[b]], ry2 = gk.y - size * 0.74, w = gk.w * 0.5;
            aty_grp(S, 'mark ' + (b + 1), [[[gk.x - w, ry2 + rs * 0.2], [gk.x - w, ry2 - rs * 0.15], [gk.x + w, ry2 - rs * 0.15], [gk.x + w, ry2 + rs * 0.2]]], ac,
                { width: Math.max(1.2 * k, lw * 1.2), piv: [gk.x, ry2], gsc: hd + 'var D=' + brk[b] + ';var a=cl(' + aI + ');[100*a,100]', gop: hd + 'var D=' + brk[b] + ';100*cl(' + aI + ')' });
        }
        if (jzP(ctx, 'idx', true)) {       // glyph indices below
            var nStr = [], nPts = [];
            for (i = 0; i < gl.length; i++) {
                var iy = gl[i].y + size * 0.72;
                aty_grp(S, 'tick ' + (i + 1), [[[gl[i].x, iy - rs * 0.55], [gl[i].x, iy - rs * 0.2]]], sc.sub, { width: lw, alpha: 0.6, gop: hd + 'var D=' + i + ';100*cl(' + aI + ')' });
                nStr.push(aty_p2(i + 1)); nPts.push([gl[i].x, iy + rs * 0.12]);
            }
            var N = aty_multi(ctx, nStr, nPts, { font: mono, size: rs * 0.72, align: 'center', track: 0.12, color: sc.sub, alpha: 0.75, name: 'indices' });
            aty_multiFade(ctx, N, 'oc((time-0.18-j*0.045)/0.3)*K');
        }
        jzNoGhost(S);
        return bb;
    }
});

/* ================================================================ 6 tyBaseline \u2014 \u7F6B\u7DDA\u7D44 */
jzReg('layout', 'tyBaseline', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['serif', 'display', 'body'])), nl: rng.pick([2, 3, 3]), key: rng.chance(0.8), right: rng.chance(0.5) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, port = aty_port(ctx), txt = jzTrim(c.text), nG = jzCount(txt), i, j;
        var font = jzP(ctx, 'font', jzSerifF(ctx)), nl = jzP(ctx, 'nl', 3);
        var per = port ? 4 : Math.max(4, Math.ceil(nG / nl));
        var lines = (nG <= 4 ? txt : aty_splitL(txt, per)).split('\n').slice(0, 6);
        if (aty_isLatinT(txt)) { var ws = aty_words(txt); lines = aty_groupLines(ws, Math.min(ws.length, port ? 4 : nl)); }
        var L = lines.length, leadK = 1.62;
        var size = Math.min(H * 0.7 / (L * leadK), port ? W * 0.17 : H * 0.16);
        for (i = 0; i < L; i++) size = Math.min(size, aty_fit(lines[i], font, port ? W * 0.76 : W * 0.74, 1e6, { track: 0.04 }));
        var x0 = port ? W * 0.16 : W * 0.14, mx = W * 0.06, lw = aty_hair(ctx), mono = jzMonoF(ctx), ls = aty_ls(ctx), hd = aty_hd(ctx);
        var arr = jzChars(lines.join('')), ki = aty_keyIndex(arr, 'kanji'), kEnd = ki;
        while (kEnd + 1 < arr.length && jzIsKanji(arr[kEnd + 1]) && jzIsKanji(arr[ki])) kEnd++;
        var S = jzShapeLayer(ctx, 'baselines', 0, 0), acc = 0, bb = null, nS = [], nP = [], cS = [], cP = [];
        for (var li = 0; li < L; li++) {
            var ln = lines[li], y = H / 2 + (li - (L - 1) / 2) * size * leadK, yb = y + size * 0.6, e = 'oe((time-' + jzN(li * 0.1) + ')/0.6)*K';
            aty_grp(S, 'rule ' + (li + 1), [[[mx, yb], [W - mx, yb]]], sc.sub, { width: lw, alpha: 0.55, trim: hd + '100*' + e });
            nS.push(aty_p2(li + 1)); nP.push([mx, y + size * 0.2]);
            cS.push(String(jzCount(ln))); cP.push([W - mx, y + size * 0.2]);
            var it = { text: ln, font: font, size: size, align: 'left', x: x0, y: y, track: 0.04, color: sc.fg, mi: li * 4 };
            bb = jzUnion(bb, aty_draw(ctx, it));
            var lc = jzChars(ln), n = lc.length;
            if (jzP(ctx, 'key', true) && ki >= acc && ki < acc + n) {        // accent bar on the key word's baseline
                var gp = aty_gpts(it), idx0 = 0;
                for (j = 0; j < ki - acc; j++) if (!aty_isSp(lc[j])) idx0++;
                var idx1 = Math.min(gp.length - 1, idx0 + Math.min(kEnd, acc + n - 1) - ki);
                if (gp[idx0]) {
                    var a0 = gp[idx0].x - gp[idx0].w / 2, a1 = gp[idx1].x + gp[idx1].w / 2;
                    aty_rectGrp(S, 'key bar', a0, yb - Math.max(2, size * 0.035), a1 - a0, Math.max(3, size * 0.07), aty_acc(sc),
                        { piv: [a0, yb], gsc: hd + '[100*oe((time-' + jzN(0.35 + li * 0.1) + ')/0.45)*K,100]', gop: hd + '(' + e + ')>0?100:0' });
                }
            }
            acc += n;
        }
        S.moveToEnd(); jzNoGhost(S);
        var NL = aty_multi(ctx, nS, nP, { font: mono, size: ls * 0.9, align: 'left', name: 'line numbers' });
        aty_multiFade(ctx, NL, 'oe((time-j*0.1)/0.6)*K');
        if (jzP(ctx, 'right', false)) { var CL = aty_multi(ctx, cS, cP, { font: mono, size: ls * 0.9, align: 'right', alpha: 0.8, name: 'glyph counts' }); aty_multiFade(ctx, CL, 'oe((time-j*0.1)/0.6)*K'); }
        return bb;
    }
});

/* ================================================================ 7 tyScaleSteps \u2014 \u7D1A\u6570\u4E0A\u3052 */
jzReg('layout', 'tyScaleSteps', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), dir: rng.pick(['up', 'up', 'down']), labels: rng.chance(0.8), ratio: rng.range(2.0, 2.8) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, M = jzU(ctx), port = aty_port(ctx), txt = jzTrim(c.text), i;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), ratio = jzP(ctx, 'ratio', 2.4), up = jzP(ctx, 'dir', 'up') === 'up';
        var units = [], cw = c.words || [];
        for (i = 0; i < cw.length; i++) if (jzTrim(cw[i])) units.push(jzTrim(cw[i]));
        var sl = aty_slots(txt), chars = [];
        for (i = 0; i < sl.length; i++) if (!aty_isSp(sl[i])) chars.push(sl[i]);
        if (units.length < 2 || units.length > 7) {
            if (chars.length <= 7) units = chars;
            else { var kk = Math.min(5, Math.ceil(chars.length / 3)); units = aty_splitL(txt, Math.ceil(chars.length / kk)).split('\n'); }
        }
        var k = units.length, f = [], fmax = 0;
        for (i = 0; i < k; i++) { var t = k > 1 ? i / (k - 1) : 1; f.push(Math.pow(ratio, up ? t : 1 - t)); fmax = Math.max(fmax, f[i]); }
        var tr = 0.02, lw = aty_hair(ctx), ls = aty_ls(ctx), items = [], bb = null;
        function wOf(u, s) { return aty_meas(u, s, { font: font, track: tr }).w; }
        if (!port) {
            var gapK = 0.16, latin = aty_isLatinT(txt) && units.length > 1 && units.length < chars.length, tot = 0;
            for (i = 0; i < k; i++) tot += wOf(units[i], 100 * f[i]) + (i ? 100 * gapK * f[i] * (latin ? 1.6 : 0.6) : 0);
            var base = Math.min(W * 0.86 / tot * 100, H * 0.52 / fmax), x = W / 2 - tot * base / 100 / 2, yb = H / 2 + base * fmax * 0.36;
            for (i = 0; i < k; i++) {
                var s = base * f[i];
                if (i) x += s * gapK * (latin ? 1.6 : 0.6);
                items.push({ text: units[i], font: font, size: s, align: 'left', x: x, y: yb - s * 0.46, track: tr, color: sc.fg, mi: i * 2, _w: wOf(units[i], s), _yb: yb });
                x += wOf(units[i], s);
            }
            aty_lines(ctx, 'scale rule', [[[items[0].x, yb + base * 0.08], [x, yb + base * 0.08]]], sc.sub, { width: lw, alpha: 0.6, trim: aty_hd(ctx) + '100*oe((time-0.05)/0.7)*K' });
        } else {
            var tot2 = 0; for (i = 0; i < k; i++) tot2 += 100 * f[i] * 1.12;
            var base2 = H * 0.78 / tot2 * 100;
            for (i = 0; i < k; i++) base2 = Math.min(base2, W * 0.8 / Math.max(0.01, wOf(units[i], 100 * f[i]) / 100));
            base2 = Math.min(base2, W * 0.5 / fmax);
            var y = H / 2 - tot2 * base2 / 100 / 2;
            for (i = 0; i < k; i++) {
                var s2 = base2 * f[i];
                items.push({ text: units[i], font: font, size: s2, align: 'left', x: W * 0.1, y: y + s2 * 0.56, track: tr, color: sc.fg, mi: i * 2, _w: wOf(units[i], s2), _yb: y + s2 * 1.06 });
                y += s2 * 1.12;
            }
        }
        for (i = 0; i < items.length; i++) {
            var it = items[i];
            bb = jzUnion(bb, aty_draw(ctx, it));
            if (jzP(ctx, 'labels', true)) {
                var pt = Math.round(it.size / (M / 1080) * 0.75), a = 'oc((time-' + jzN(0.25 + i * 0.07) + ')/0.3)*K';
                if (!port) aty_label(ctx, pt + 'pt', it.x, it._yb + ls * 1.3, { size: ls * 0.85, op: a });
                else aty_label(ctx, pt + 'pt', it.x + it._w + ls * 0.8, it._yb - ls * 0.6, { size: ls * 0.85, op: a });
            }
        }
        return bb;
    }
});

/* ================================================================ 8 tyJustify \u2014 \u5E45\u63C3\u3048 */
jzReg('layout', 'tyJustify', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display'])), rules: rng.chance(0.7), acc: rng.int(0, 3), align: rng.pick(['center', 'left']) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, port = aty_port(ctx), txt = jzTrim(c.text), n = jzCount(txt), i;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), left = jzP(ctx, 'align', 'center') === 'left';
        var L = n <= 5 ? 2 : n <= 10 ? 3 : 4, units = [], cw = c.words || [];
        for (i = 0; i < cw.length; i++) if (jzTrim(cw[i])) units.push(String(cw[i]));
        if (aty_isLatinT(txt)) units = aty_words(txt);
        else {
            if (units.length < 2) units = jzChunk(txt);
            if (units.length < 2) units = jzChars(txt);
        }
        L = Math.min(L, units.length);
        var lines = aty_groupLines(units, L), out = [];
        for (i = 1; i < lines.length; i++) {
            var cs = jzChars(lines[i]);
            while (cs.length && jzIsPunct(cs[0])) { lines[i - 1] += cs[0]; cs.shift(); }
            lines[i] = jzTrim(cs.join(''));
        }
        for (i = 0; i < lines.length; i++) if (lines[i]) out.push(lines[i]);
        lines = out;
        if (lines.length < 2 && !aty_isLatinT(txt)) lines = aty_splitL(txt, Math.ceil(n / 2)).split('\n');
        var Wb = port ? W * 0.84 : Math.min(W * 0.62, H * 1.15), tr = 0.01, gapK = 0.1, sizes = [], tot = 0;
        for (i = 0; i < lines.length; i++) { sizes.push(Math.min(aty_fit(lines[i], font, Wb, 1e6, { track: tr }), H * 0.36)); tot += sizes[i] * (1 + gapK); }
        tot -= sizes[sizes.length - 1] * gapK;
        var k = Math.min(1, H * 0.84 / tot);
        for (i = 0; i < sizes.length; i++) sizes[i] *= k;
        tot *= k;
        var x0 = W / 2 - Wb * k / 2, y = H / 2 - tot / 2, bb = null, accI = jzP(ctx, 'acc', 0) % lines.length, hd = aty_hd(ctx), segs = [], S = null;
        for (var li = 0; li < lines.length; li++) {
            var s = sizes[li];
            bb = jzUnion(bb, aty_draw(ctx, { text: lines[li], font: font, size: s, x: left ? x0 : W / 2, align: left ? 'left' : 'center', y: y + s / 2, track: tr, color: li === accI && lines.length > 2 ? aty_acc(sc) : sc.fg, mi: li * 3 }));
            if (jzP(ctx, 'rules', true) && li < lines.length - 1) {
                var ry = y + s + s * gapK * k * 0.5, e = 'oe((time-' + jzN(0.15 + li * 0.08) + ')/0.6)*K';
                if (!S) S = jzShapeLayer(ctx, 'justify rules', 0, 0);
                aty_grp(S, 'rule ' + (li + 1), [[[W / 2 - Wb * k / 2, ry], [W / 2 + Wb * k / 2, ry]]], sc.sub, { width: aty_hair(ctx), alpha: 0.55, trim: hd + '50+50*' + e, start: hd + '50-50*' + e });
            }
            y += s * (1 + gapK);
        }
        if (S) { S.moveToEnd(); jzNoGhost(S); }
        return bb;
    }
});

/* ================================================================ 9 tyIndexTable \u2014 \u4E00\u89A7\u8868 */
jzReg('layout', 'tyIndexTable', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), info: rng.pick(['roma', 'roma', 'code']), head: rng.chance(0.8) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, M = jzU(ctx), port = aty_port(ctx), k = aty_k(ctx), i;
        var chars = jzChars(aty_clean(c.text)), n = chars.length, font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), code = jzP(ctx, 'info', 'roma') === 'code';
        if (!n) return null;
        var rowH = Math.min(H * 0.78 / n, M * 0.2), gs = rowH * 0.86, Wt = port ? W * 0.84 : Math.min(W * 0.56, H * 1.0);
        var x0 = W / 2 - Wt / 2, x1 = W / 2 + Wt / 2, xg = x0 + Wt * 0.24, top = H / 2 - n * rowH / 2;
        var lw = aty_hair(ctx), mono = jzMonoF(ctx), fs = jzClamp(rowH * 0.3, 11 * k, 34 * k), hd = aty_hd(ctx);
        var e0 = 'oe(time/0.6)*K', S = jzShapeLayer(ctx, 'table rules', 0, 0);
        aty_grp(S, 'frame', [[[x0, top], [x1, top]], [[x0, top + n * rowH], [x1, top + n * rowH]]], sc.fg, { width: lw * 2, alpha: 0.9, trim: hd + '100*' + e0 });
        var infos = [], nS = [], nP = [], iP = [];
        for (i = 0; i < n; i++) {
            var y = top + (i + 0.5) * rowH, ch = chars[i], info = null, a = 'oc((time-' + jzN(0.12 + i * 0.06) + ')/0.3)*K';
            if (!code && (jzIsHira(ch) || jzIsKata(ch))) { var r = jzRomaji(ch); if (r) info = r.toUpperCase(); }
            if (!info) { var cp = ch.length > 1 ? ((ch.charCodeAt(0) - 0xD800) * 0x400 + (ch.charCodeAt(1) - 0xDC00) + 0x10000) : ch.charCodeAt(0); info = 'U+' + jzPad(cp.toString(16).toUpperCase(), 4); }
            infos.push(info); nS.push(aty_p2(i + 1)); nP.push([x0, y]); iP.push([x1, y]);
            var iw = aty_tw(info, mono, fs, 0.12), la = xg + gs * 0.72, lb = x1 - iw - fs * 0.8;
            if (lb > la) aty_grp(S, 'leader ' + (i + 1), [[[la, y + fs * 0.2], [lb, y + fs * 0.2]]], sc.sub, { width: Math.max(2 * k, lw * 2), dash: [Math.max(2 * k, lw * 2), Math.max(5 * k, fs * 0.5)], trim: hd + '100*cl(' + a + ')', gop: hd + '100*cl(' + a + ')' });
            if (i < n - 1) aty_grp(S, 'row ' + (i + 1), [[[x0, top + (i + 1) * rowH], [x1, top + (i + 1) * rowH]]], sc.sub, { width: lw, alpha: 0.22, gop: hd + '100*cl(' + a + ')' });
        }
        jzNoGhost(S);
        if (jzP(ctx, 'head', true)) {
            aty_label(ctx, 'No.', x0, top - fs * 0.9, { size: fs * 0.8, op: e0 });
            aty_label(ctx, code ? 'CODE' : 'READING', x1, top - fs * 0.9, { size: fs * 0.8, align: 'right', op: e0 });
        }
        var bb = null;
        for (i = 0; i < n; i++) bb = jzUnion(bb, aty_draw(ctx, { text: chars[i], font: font, size: gs, x: xg, y: top + (i + 0.5) * rowH, color: sc.fg, mi: i }));
        var NL = aty_multi(ctx, nS, nP, { font: mono, size: fs, align: 'left', name: 'row numbers' });
        aty_multiFade(ctx, NL, 'oc((time-0.12-j*0.06)/0.3)*K');
        jzAnimator(NL.L, 'JZ First Row', [['ADBE Text Fill Color', jzHex(aty_acc(sc))]], 'textIndex<=2?100:0');
        var IL = aty_multi(ctx, infos, iP, { font: mono, size: fs, align: 'right', name: 'readings' });
        aty_multiFade(ctx, IL, 'oc((time-0.12-j*0.06)/0.3)*K');
        return bb;
    }
});

// ================================================================ pack typo part 2: layouts 10-18
// tySplitType tyErode tyVRuler tyFullTrack tyStatCount tyMargin tyRotBlock tySquare tyLineFocus (helpers: p_typo1)

/* ================================================================ 10 tySplitType \u2014 \u65AD\u3061\u5272\u308A */
jzReg('layout', 'tySplitType', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display'])), off: rng.range(0.08, 0.15) * rng.pick([1, -1]), cutY: rng.pick([0.02, -0.06, 0.08]), cap: rng.chance(0.75) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, txt = jzTrim(c.text), font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), li;
        var mt = aty_mainLines(txt, W, H, 11, 5), nL = mt.split('\n').length, lead = 1.16;
        var size = Math.min(aty_fit(mt, font, W * 0.76, H * 0.56, { lead: lead, track: 0.03 }), H * 0.3), LD = size * lead;
        var off = jzP(ctx, 'off', 0.1), cutY = jzP(ctx, 'cutY', 0.02), ys = [];
        for (li = 0; li < nL; li++) ys.push(H / 2 + (li - (nL - 1) / 2) * LD + size * cutY);
        var ay = -ATY_CY * size + (nL - 1) / 2 * LD, bb = null, hd = aty_hd(ctx);
        // the same lines twice: the upper halves shift one way, the lower halves the other (layer masks at the cut lines)
        for (var part = 0; part < 2; part++) {
            var top = part === 0, it = { text: mt, font: font, size: size, x: W / 2, y: H / 2, lead: lead, track: 0.03, color: sc.fg, mi: 0 };
            bb = jzUnion(bb, aty_draw(ctx, it));
            var L = it.L;
            for (li = 0; li < nL; li++) {
                var yc = ys[li] - H / 2 + ay, a = yc - LD / 2, b = yc + LD / 2;
                var mk = top ? jzMaskRect(L, -W * 2, li ? a : -H * 2, W * 2, yc) : jzMaskRect(L, -W * 2, yc, W * 2, li < nL - 1 ? b : H * 2);
                mk.property('ADBE Mask Offset').expression = hd + 'oe((time-0.12)/0.7)*' + jzN(size * 0.05);
            }
            var s = top ? 1 : -1;
            jzSetExpr(jzXf(L, 'ADBE Anchor Point'), hd + 'var e=oe((time-0.12)/0.7);[value[0]-e*' + jzN(s * size * off) + ',value[1]+e*' + jzN(s * size * 0.05) + ']');
        }
        var S = jzShapeLayer(ctx, 'cut lines', 0, 0), lw = aty_hair(ctx);
        for (li = 0; li < nL; li++) aty_grp(S, 'cut ' + (li + 1), [[[W * 0.04, ys[li]], [W * 0.96, ys[li]]]], aty_acc(sc), { width: Math.max(1.2 * aty_k(ctx), lw * 1.2), alpha: 0.9, trim: hd + '100*oe((time-' + jzN(0.2 + li * 0.08) + ')/0.6)*K' });
        jzNoGhost(S);
        if (jzP(ctx, 'cap', true)) {
            var ls = aty_ls(ctx) * 0.85, cap = jzChars(aty_roma(txt) || String(c.lineText || txt)).slice(0, 48).join('');
            aty_label(ctx, cap, W * 0.96, ys[nL - 1] + ls * 0.9, { align: 'right', size: ls, track: 0.2, op: 'K*oc((time-0.4)/0.3)' });
        }
        return bb;
    }
});

/* ================================================================ 11 tyErode \u2014 \u524A\u308A\u53CD\u5FA9 */
jzReg('layout', 'tyErode', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['serif', 'display'])), mode: rng.pick(['grow', 'grow', 'erode']), idx: rng.chance(0.7) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, font = jzP(ctx, 'font', jzSerifF(ctx)), erode = jzP(ctx, 'mode', 'grow') === 'erode', i, r;
        var arr = aty_slots(c.text), nS = arr.length, plain = 0;
        for (i = 0; i < nS; i++) if (!aty_isSp(arr[i])) plain++;
        var R = Math.min(5, Math.max(2, plain)), lens = [];
        for (r = 0; r < R; r++) { var l = Math.max(1, Math.round(nS * (r + 1) / R)); if (jzIndexOf(lens, l) < 0) lens.push(l); }
        if (erode) lens.reverse();
        var rows = [];
        for (r = 0; r < lens.length; r++) { var t = jzTrim(arr.slice(0, lens[r]).join('')); if (t) rows.push(t); }
        var Rn = rows.length, full = arr.join(''), leadK = 1.34;
        var size = Math.min(aty_fit(full, font, W * 0.74, 1e6, { track: 0.04 }), H * 0.8 / (Rn * leadK), H * 0.16);
        var fw = aty_meas(full, size, { font: font, track: 0.04 }).w, x0 = W / 2 - fw / 2, mainR = erode ? 0 : Rn - 1, bb = null, nStr = [], nPts = [];
        for (r = 0; r < Rn; r++) {
            var y = H / 2 + (r - (Rn - 1) / 2) * size * leadK, it = { text: rows[r], font: font, size: size, align: 'left', x: x0, y: y, track: 0.04 };
            if (r === mainR) { it.color = sc.fg; it.mi = r * 2.5; bb = aty_draw(ctx, it); }
            else {
                var dist = Math.abs(r - mainR) / Math.max(1, Rn - 1);
                it.color = sc.sub; it.alpha = 0.75 - 0.45 * dist; it.name = 'erode row ' + (r + 1);
                aty_op(ctx, aty_text(ctx, it.text, it), '((time-' + jzN(r * 0.11) + ')/0.12)*K');
            }
            nStr.push(aty_p2(jzCount(rows[r]))); nPts.push([x0 + fw + size * 0.5, y]);
        }
        if (jzP(ctx, 'idx', true)) {
            var N = aty_multi(ctx, nStr, nPts, { size: Math.min(aty_ls(ctx), size * 0.4), align: 'left', alpha: 0.85, name: 'row counts' });
            aty_multiFade(ctx, N, '(time-j*0.11)/0.12*K');
            jzAnimator(N.L, 'JZ Main Row', [['ADBE Text Fill Color', jzHex(aty_acc(sc))]], 'var LI=' + aty_arr(N.li) + ';LI[textIndex-1]===' + mainR + '?100:0');
        }
        return bb;
    }
});

/* ================================================================ 12 tyVRuler \u2014 \u7E26\u76EE\u76DB\u308A */
jzReg('layout', 'tyVRuler', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['serif', 'display'])), side: rng.pick([1, -1]), lab: rng.pick(['time', 'time', 'roma']) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, k = aty_k(ctx), i;
        var txt = jzTrim(c.text).replace(/[\s\u3000]+/g, '\u3000'), tr = 0.14, font = jzP(ctx, 'font', jzSerifF(ctx)), side = jzP(ctx, 'side', 1);
        var size = Math.min(aty_fit(txt, font, W * 0.3, H * 0.78, { vertical: true, track: tr }), W * 0.2, H * 0.2), cx = W / 2 - side * size * 0.9;
        var it = { text: txt, font: font, size: size, x: cx, y: H / 2, vertical: true, track: tr, color: sc.fg };
        var bb = aty_draw(ctx, it), gl = aty_gpts(it), n = gl.length;
        if (!n) return bb;
        var lw = aty_hair(ctx), hd = aty_hd(ctx), rx = cx + side * size * 0.85, y0 = gl[0].y - size * 0.7, y1 = gl[n - 1].y + size * 0.7;
        var S = jzShapeLayer(ctx, 'ruler', 0, 0), e = 'oe(time/0.7)*K', fs = jzClamp(size * 0.2, 10 * k, 24 * k), rom = jzP(ctx, 'lab', 'time') === 'roma', strs = [], pts = [];
        aty_grp(S, 'spine', [[[rx, y0], [rx, y1]]], sc.sub, { width: lw, alpha: 0.8, trim: hd + '100*' + e });
        for (i = 0; i < n; i++) {
            var g = gl[i], a = hd + '100*cl(oc((time-' + jzN(0.1 + i * 0.05) + ')/0.25)*K)', segs = [[[rx, g.y], [rx + side * size * 0.3, g.y]]];
            aty_grp(S, 'tick ' + (i + 1), segs, sc.sub, { width: lw, alpha: 0.9, gop: a });
            if (i < n - 1) { var ym = (g.y + gl[i + 1].y) / 2; aty_grp(S, 'half ' + (i + 1), [[[rx, ym], [rx + side * size * 0.14, ym]]], sc.sub, { width: lw, alpha: 0.5, gop: a }); }
            var t = null;
            if (rom) { var rr = aty_romaCh(g.ch); if (rr) t = rr.toUpperCase(); }
            if (!t) t = jzFmtTime(c.start + c.dur * i / n);
            strs.push(t); pts.push([rx + side * size * 0.45, g.y]);
        }
        jzNoGhost(S);
        var M = aty_multi(ctx, strs, pts, { size: fs, align: side > 0 ? 'left' : 'right', name: 'ruler labels' });
        aty_multiFade(ctx, M, 'oc((time-0.1-j*0.05)/0.25)*K');
        // playhead running down the ruler over the cut
        var ts = fs * 0.7, P = jzShapeLayer(ctx, 'playhead', 0, gl[0].y), gp = jzGrp(P, 'head');
        jzAddPath(gp, [[rx - side * 2 * k, 0], [rx - side * ts * 1.4, -ts], [rx - side * ts * 1.4, ts]], true); jzAddFill(gp, aty_acc(sc));
        jzSetExpr(jzXf(P, 'ADBE Position'), 'var u=Math.max(0,Math.min(1,time/' + jzN(Math.max(0.3, c.dur * 0.92)) + '));[value[0],value[1]+u*' + jzN(gl[n - 1].y - gl[0].y) + ']');
        aty_op(ctx, P, e);
        return bb;
    }
});

/* ================================================================ 13 tyFullTrack \u2014 \u5168\u5E45\u5B57\u9001\u308A */
jzReg('layout', 'tyFullTrack', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif', 'body'])), caps: rng.chance(0.8), rule: rng.chance(0.75) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, port = aty_port(ctx), font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), i;
        var s = aty_slots(c.text), k = s.length, m0 = port ? H * 0.08 : W * 0.06;
        var size = port ? Math.min(W * 0.24, (H - m0 * 2) / k * 0.8, H * 0.11) : Math.min(H * 0.2, (W - m0 * 2) / k * 0.8);
        var m = m0 + size * 0.5, span = (port ? H : W) - m * 2, bb = null, gi = 0, hd = aty_hd(ctx), e = 'oe((time-0.05)/0.8)*K';
        for (i = 0; i < k; i++) {
            if (aty_isSp(s[i])) continue;
            var u = k > 1 ? i / (k - 1) : 0.5;
            bb = jzUnion(bb, aty_draw(ctx, { text: s[i], font: font, size: size, x: port ? W / 2 : m + span * u, y: port ? m + span * u : H / 2, color: sc.fg, mi: gi++ }));
        }
        if (jzP(ctx, 'rule', true)) {
            var seg = !port ? [[W / 2 - span / 2, H / 2 + size * 0.68], [W / 2 + span / 2, H / 2 + size * 0.68]] : [[W / 2 + size * 0.7, H / 2 - span / 2], [W / 2 + size * 0.7, H / 2 + span / 2]];
            aty_lines(ctx, 'track rule', [seg], sc.sub, { width: aty_hair(ctx), alpha: 0.6, trim: hd + '50+50*' + e, start: hd + '50-50*' + e });
        }
        if (jzP(ctx, 'caps', true)) {
            var ls = aty_ls(ctx), cap = jzChars(jzTrim(String(c.lineText || c.text).replace(/\s+/g, ' '))).slice(0, 40).join(''), op = '0.9*' + e;
            if (!port) {
                aty_label(ctx, cap, m, H / 2 - size * 0.62 - ls, { size: ls, font: jzBodyF(ctx), track: 0.14, op: op });
                aty_label(ctx, 'No.' + aty_p2((c.line | 0) + 1) + '  ' + jzFmtTime(c.start), W - m, H / 2 - size * 0.62 - ls, { size: ls, align: 'right', op: op });
            } else {
                aty_label(ctx, 'No.' + aty_p2((c.line | 0) + 1), W / 2 - size * 0.7, m - size * 0.9, { size: ls, align: 'right', op: op });
                aty_label(ctx, jzFmtTime(c.start), W / 2 + size * 0.7, H - m + size * 0.9, { size: ls, op: op });
            }
        }
        return bb;
    }
});

/* ================================================================ 14 tyStatCount \u2014 \u5B57\u6570\u8868\u793A */
jzReg('layout', 'tyStatCount', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), nf: rng.pick(jzFontsOf(st, ['display', 'mono'])), acc: rng.chance(0.5) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, port = aty_port(ctx), txt = jzTrim(c.text), nG = jzCount(txt);
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), nf = jzP(ctx, 'nf', font), hd = aty_hd(ctx);
        var mt = aty_mainLines(txt, W, H, 8, 5), o = { lead: 1.2, track: 0.03 }, ls = aty_ls(ctx), lw = aty_hair(ctx);
        var latin = !/[^\x00-\x7f]/.test(txt), e = 'oe((time-0.05)/0.7)*K', it, nx, ny, ns;
        if (!port) {
            it = { text: mt, font: font, size: Math.min(aty_fit(mt, font, W * 0.5, H * 0.54, o), H * 0.2), align: 'left', x: W * 0.08, y: H / 2, color: sc.fg, lead: 1.2, track: 0.03 };
            var rx = W * 0.65;
            aty_lines(ctx, 'stat rule', [[[rx, H / 2 - H * 0.22], [rx, H / 2 + H * 0.22]]], sc.sub, { width: lw, alpha: 0.7, trim: hd + '50+50*' + e, start: hd + '50-50*' + e });
            nx = rx + W * 0.04; ny = H / 2 - H * 0.05; ns = H * 0.26;
        } else {
            it = { text: mt, font: font, size: Math.min(aty_fit(mt, font, W * 0.84, H * 0.42, o), W * 0.18), align: 'left', x: W * 0.08, y: H * 0.36, color: sc.fg, lead: 1.2, track: 0.03 };
            var ry = H * 0.62;
            aty_lines(ctx, 'stat rule', [[[W * 0.08, ry], [W * 0.92, ry]]], sc.sub, { width: lw, alpha: 0.7, trim: hd + '100*' + e });
            nx = W * 0.08; ny = ry + H * 0.1; ns = W * 0.3;
        }
        var bb = aty_draw(ctx, it);
        ns = Math.min(ns, (W * 0.93 - nx) / Math.max(0.3, aty_meas('00', 1, { font: nf }).w));
        // the glyph count ticks up to the cut's glyph count
        var N = aty_text(ctx, aty_p2(nG), { font: nf, size: ns, align: 'left', x: nx, y: ny, color: jzP(ctx, 'acc', false) ? aty_acc(sc) : sc.fg, name: 'glyph count' });
        try { N.property('ADBE Text Properties').property('ADBE Text Document').expression = ATY_FNS + 'var n=Math.round(' + nG + '*oc((time-0.1)/0.9));(n<10?"0":"")+n'; } catch (er) { jzWarn('count: ' + er.toString()); }
        aty_op(ctx, N, e);
        var y2 = ny + ns * 0.62;
        aty_label(ctx, latin ? 'CHARACTERS' : '\u6587\u5B57 / CHARACTERS', nx, y2, { size: ls, font: latin ? jzMonoF(ctx) : jzBodyF(ctx), op: e });
        aty_label(ctx, 'LINE ' + aty_p2((c.line | 0) + 1) + '  \u2500  ' + jzFmtTime(c.start), nx, y2 + ls * 1.8, { size: ls * 0.85, op: '0.8*' + e });
        return bb;
    }
});

/* ================================================================ 15 tyMargin \u2014 \u4F59\u767D */
jzReg('layout', 'tyMargin', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['serif', 'body', 'display'])), pos: rng.pick(['bl', 'bl', 'tr', 'br', 'lc']), mark: rng.chance(0.7) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, M = jzU(ctx), port = aty_port(ctx), txt = jzTrim(c.text), font = jzP(ctx, 'font', jzSerifF(ctx)), pos = jzP(ctx, 'pos', 'bl');
        var mt = jzCount(txt) > (port ? 9 : 14) ? aty_splitL(txt, port ? 8 : 12) : txt;
        var size = Math.min(M * 0.068, aty_fit(mt, font, W * 0.66, H * 0.22, { track: 0.1, lead: 1.4 }));
        var right = pos === 'tr' || pos === 'br', y = pos === 'tr' ? H * 0.2 : pos === 'lc' ? H * 0.5 : H * 0.8, x = right ? W * 0.92 : W * 0.08;
        var it = { text: mt, font: font, size: size, x: x, y: y, align: right ? 'right' : 'left', track: 0.1, lead: 1.4, color: sc.fg };
        var bb = aty_draw(ctx, it), m = aty_meas(mt, size, it), ls = aty_ls(ctx) * 0.9, hd = aty_hd(ctx), e = 'oe((time-0.2)/0.9)*K';
        var tx0 = right ? x - m.w : x, tx1 = right ? x : x + m.w;
        aty_label(ctx, aty_p2((c.line | 0) + 1) + '  \u2014  ' + jzFmtTime(c.start), x, y - m.h / 2 - ls * 1.4, { size: ls, align: right ? 'right' : 'left', op: '0.9*' + e });
        var a0 = right ? tx0 - size * 0.8 : tx1 + size * 0.8, a1 = right ? W * 0.08 : W * 0.92;
        if ((a1 - a0) * (right ? -1 : 1) > W * 0.05) {       // a line out to the far margin, a small square riding its tip
            aty_lines(ctx, 'margin line', [[[a0, y], [a1, y]]], sc.sub, { width: aty_hair(ctx), alpha: 0.6, trim: hd + '100*' + e });
            if (jzP(ctx, 'mark', true)) {
                var q = size * 0.16, Q = jzRectLayer(ctx, 'margin mark', a0, y, q, q, aty_acc(sc));
                jzSetExpr(jzXf(Q, 'ADBE Position'), hd + '[value[0]+' + jzN(a1 - a0) + '*' + e + ',value[1]]');
                aty_op(ctx, Q, e);
            }
        }
        var rom = aty_roma(txt);
        if (rom) aty_label(ctx, rom, x, y + m.h / 2 + ls * 1.3, { size: ls * 0.9, align: right ? 'right' : 'left', track: 0.3, op: '0.75*' + e });
        return bb;
    }
});

/* ================================================================ 16 tyRotBlock \u2014 \u56DE\u8EE2\u30D6\u30ED\u30C3\u30AF */
jzReg('layout', 'tyRotBlock', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display'])), fb: rng.pick(jzFontsOf(st, ['display', 'serif'])), rot: rng.pick([-90, -90, 90]), accent: rng.chance(0.5), rule: rng.chance(0.8) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, port = aty_port(ctx), i;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), fb = jzP(ctx, 'fb', font), rot = jzP(ctx, 'rot', -90);
        var arr = aty_slots(c.text), n = arr.length, ws = c.words || [], cutAt = Math.max(1, Math.round(n * 0.36));
        if (ws.length >= 2) { var l0 = jzChars(jzTrim(ws[0])).length; if (l0 >= 1 && l0 <= Math.ceil(n * 0.55)) cutAt = l0; }
        while (cutAt < n - 1 && (aty_isSmall(arr[cutAt]) || jzIsPunct(arr[cutAt]))) cutAt++;
        var A = jzTrim(arr.slice(0, cutAt).join('')) || arr[0], B = jzTrim(arr.slice(cutAt).join('')) || arr[n - 1], labelA = false;
        if (aty_isLatinT(c.text)) {       // latin: split at a word gap; one word -> the rotated block is a small line label
            var t = jzTrim(c.text), sp = t.indexOf(' ');
            if (sp > 0) { A = t.substr(0, sp); B = jzTrim(t.substr(sp + 1)); }
            else { A = 'LINE ' + aty_p2((c.line | 0) + 1); B = t; labelA = true; }
        }
        var Hb = port ? H * 0.44 : H * 0.62, fA = labelA ? jzMonoF(ctx) : font;
        var wA100 = aty_meas(A, 100, { font: fA, track: 0.02 }).w;
        var sizeA = Math.min(Hb / wA100 * 100, port ? W * 0.3 : W * 0.24, labelA ? jzU(ctx) * 0.06 : 1e9), gap = sizeA * 0.28;
        var Wb = (port ? W * 0.86 : Math.min(W * 0.84, Hb * 2.4)) - sizeA - gap, best = null, nB = jzCount(B);
        for (var Lc = 1; Lc <= 3; Lc++) {
            if (Lc > nB) break;
            var bt = Lc === 1 ? B : aty_splitL(B, Math.ceil(nB / Lc));
            if (Lc > 1 && aty_isLatinT(B)) { var bw2 = jzTrim(B).split(/\s+/); if (bw2.length < Lc) break; bt = aty_groupLines(aty_words(B), Lc).join('\n'); }
            var s = Math.min(aty_fit(bt, fb, Wb, Hb, { lead: 1.08, track: 0.02 }), Hb * 0.6);
            if (!best || s > best.s * 1.05) best = { bt: bt, s: s };
        }
        var mB = aty_meas(best.bt, best.s, { font: fb, lead: 1.08, track: 0.02 }), wA = wA100 * sizeA / 100;
        var tot = sizeA + gap + mB.w, xA = W / 2 - tot / 2 + sizeA / 2, xB = xA + sizeA / 2 + gap, bh = Math.max(wA, mB.h), hd = aty_hd(ctx);
        if (jzP(ctx, 'rule', true)) {
            var e = 'oe((time-0.1)/0.6)*K', rxl = xA + sizeA / 2 + gap / 2;
            aty_lines(ctx, 'block rule', [[[rxl, H / 2 - bh / 2], [rxl, H / 2 + bh / 2]]], sc.sub, { width: aty_hair(ctx), alpha: 0.7, trim: hd + '50+50*' + e, start: hd + '50-50*' + e });
        }
        var itA = { text: A, font: fA, size: sizeA, x: xA, y: H / 2, rot: rot, track: 0.02, color: jzP(ctx, 'accent', false) ? aty_acc(sc) : sc.fg, mi: 0 };
        if (labelA) { itA.color = sc.sub; aty_op(ctx, aty_text(ctx, A, itA), 'oc(time/0.4)*K'); }
        else aty_draw(ctx, itA);
        aty_draw(ctx, { text: best.bt, font: fb, size: best.s, x: xB, y: H / 2, align: 'left', lead: 1.08, track: 0.02, color: sc.fg, mi: 3 });
        return { x0: xA - sizeA / 2, y0: H / 2 - bh / 2, x1: xB + mB.w, y1: H / 2 + bh / 2, cx: (xA - sizeA / 2 + xB + mB.w) / 2, cy: H / 2 };
    }
});

/* ================================================================ 17 tySquare \u2014 \u65B9\u5F62\u7D44 */
jzReg('layout', 'tySquare', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['display', 'serif'])), order: rng.pick(['yoko', 'yoko', 'tate']), frame: rng.chance(0.75), acc: rng.chance(0.7) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, M = jzU(ctx), chars = jzChars(aty_clean(c.text)), n = chars.length, i;
        if (!n) return null;
        var font = jzP(ctx, 'font', jzFontKeyOf(ctx.st, 'display')), tate = jzP(ctx, 'order', 'yoko') === 'tate', accK = jzP(ctx, 'acc', true);
        var a = Math.ceil(Math.sqrt(n)), b = Math.ceil(n / a), cols = tate ? b : a, rows = tate ? a : b;
        var cell = Math.min(W * 0.78 / cols, H * 0.74 / rows, M * 0.3), x0 = W / 2 - cols * cell / 2, y0 = H / 2 - rows * cell / 2;
        var ki = aty_keyIndex(chars, 'kanji'), lw = aty_hair(ctx), ls = aty_ls(ctx), hd = aty_hd(ctx), bb = null, S = null;
        for (i = 0; i < cols * rows; i++) {
            var cc = tate ? cols - 1 - Math.floor(i / rows) : i % cols, rr = tate ? i % rows : Math.floor(i / cols);
            var x = x0 + (cc + 0.5) * cell, y = y0 + (rr + 0.5) * cell;
            if (i < n) bb = jzUnion(bb, aty_draw(ctx, { text: chars[i], font: font, size: cell * 0.9, x: x, y: y, color: accK && i === ki ? aty_acc(sc) : sc.fg, mi: i }));
            else {     // empty cells: small crosses pop in
                var q = cell * 0.08;
                if (!S) S = jzShapeLayer(ctx, 'grid marks', 0, 0);
                aty_grp(S, 'cross ' + (i + 1), [[[x - q, y], [x + q, y]], [[x, y - q], [x, y + q]]], sc.sub, { width: lw, alpha: 0.8, piv: [x, y], gsc: hd + 'var s=cl((time-' + jzN(0.2 + i * 0.03) + ')/0.3);s=s<=0?0:ob(s,2)*K*100;[s,s]' });
            }
        }
        if (S) jzNoGhost(S);
        if (jzP(ctx, 'frame', true)) {
            var xa = x0, xb = x0 + cols * cell, g = cell * 0.12, e = 'oe((time-0.05)/0.7)*K', yb = y0 + rows * cell + g;
            var F = jzShapeLayer(ctx, 'grid frame', 0, 0);
            aty_grp(F, 'top', [[[xa, y0 - g], [xb, y0 - g]]], sc.fg, { width: Math.max(2 * aty_k(ctx), lw * 2.4), alpha: 0.9, trim: hd + '100*' + e });
            aty_grp(F, 'bottom', [[[xb, yb], [xa, yb]]], sc.sub, { width: lw, alpha: 0.8, trim: hd + '100*' + e });
            jzNoGhost(F);
            aty_label(ctx, cols + '\u00D7' + rows, xb, yb + ls * 1.1, { align: 'right', size: ls * 0.9, op: e });
            aty_label(ctx, tate ? '\u7E26\u7D44' : 'YOKO', xa, yb + ls * 1.1, { size: ls * 0.9, font: tate ? jzBodyF(ctx) : jzMonoF(ctx), op: '0.8*' + e });
        }
        return bb;
    }
});

/* ================================================================ 18 tyLineFocus \u2014 \u884C\u4E2D\u5F37\u8ABF */
jzReg('layout', 'tyLineFocus', {
    plan: function (rng, cut, st) {
        return { font: rng.pick(jzFontsOf(st, ['serif', 'display', 'body'])), mark: rng.pick(['bar', 'bar', 'dot', 'box']) };
    },
    build: function (ctx) {
        var W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, M = jzU(ctx), port = aty_port(ctx), font = jzP(ctx, 'font', jzSerifF(ctx)), mark = jzP(ctx, 'mark', 'bar'), i;
        var cutT = jzTrim(c.text), line = jzTrim(String(c.lineText || cutT)), s0, s1, same = false;
        if (jzCount(line) > 36) line = cutT;
        function nsI(s, end) { var a = jzChars(s).slice(0, end), q = 0; for (var j = 0; j < a.length; j++) if (!/\s/.test(a[j])) q++; return q; }
        var at = line.indexOf(cutT);
        if (at < 0 || line === cutT) {
            line = cutT; same = true;
            var arr = jzChars(line), ki = aty_keyIndex(arr, 'kanji'), ke = ki;
            if (jzIsKanji(arr[ki])) { while (ke + 1 < arr.length && jzIsKanji(arr[ke + 1])) ke++; }
            else { while (ke + 1 < arr.length && ke - ki < 2 && !aty_isSp(arr[ke + 1]) && !jzIsPunct(arr[ke + 1])) ke++; }
            if (aty_isLatinT(line)) {
                var re = /\S+/g, mm, bst = null;
                while ((mm = re.exec(line))) if (!bst || mm[0].length > bst[0].length) bst = mm;
                if (bst) { ki = jzChars(line.substr(0, bst.index)).length; ke = ki + jzChars(bst[0]).length - 1; }
            }
            s0 = nsI(line, ki); s1 = nsI(line, ke + 1);
        } else { s0 = nsI(line, jzChars(line.substr(0, at)).length); s1 = s0 + jzCount(cutT); }
        var nAll = jzCount(line);
        var mt = nAll > (port ? 7 : 12) ? aty_splitL(line, Math.ceil(nAll / Math.ceil(nAll / (port ? 6 : 11)))) : line;
        var o = { lead: 1.7, track: 0.08 }, size = Math.min(aty_fit(mt, font, W * 0.82, H * 0.56, o), M * 0.12);
        var it = { text: mt, font: font, size: size, x: W / 2, y: H / 2, lead: 1.7, track: 0.08 }, gl = aty_gpts(it), lay = aty_lay(mt, size, it);
        // the whole line, dimmed; the focus glyphs are hidden in it (they are drawn as the lyric below)
        var flags = [], gi = 0;
        for (i = 0; i < lay.length; i++) { if (lay[i].sp) { flags.push(0); continue; } flags.push(gi >= s0 && gi < s1 ? 1 : 0); gi++; }
        var D = aty_text(ctx, mt, { font: font, size: size, x: W / 2, y: H / 2, lead: 1.7, track: 0.08, color: same ? sc.fg : sc.sub, alpha: same ? 0.5 : 0.42, name: 'line (dim)' });
        jzAnimator(D, 'JZ Focus Gap', [['ADBE Text Opacity', 0]], 'var a=' + aty_arr(flags) + ';(a[textIndex-1]||0)*100');
        aty_op(ctx, D, 'oc(time/0.3)*K');
        // focus spans per text line: one lyric layer each, set exactly over the dimmed glyphs
        var spans = [], cur = null;
        for (i = 0; i < gl.length; i++) {
            if (i < s0 || i >= s1) continue;
            if (!cur || cur.li !== gl[i].li) { cur = { li: gl[i].li, g: [], j: i - s0 }; spans.push(cur); }
            cur.g.push(gl[i]);
        }
        var bb = null, hd = aty_hd(ctx), ac = aty_acc(sc), S = null;
        for (var k2 = 0; k2 < spans.length; k2++) {
            var sp = spans[k2], g0 = sp.g[0], gN = sp.g[sp.g.length - 1], txt = '';
            var ti0 = g0.ti, ti1 = gN.ti;
            for (i = ti0; i <= ti1; i++) txt += lay[i].ch;
            bb = jzUnion(bb, aty_draw(ctx, { text: txt, font: font, size: size, align: 'left', x: g0.x - g0.w / 2, y: g0.y, track: 0.08, color: sc.fg, mi: sp.j }));
            var La = g0.x - g0.w / 2, Lb = gN.x + gN.w / 2, y = g0.y, q = 'oe((time-' + jzN(0.25 + k2 * 0.1) + ')/0.5)*K';
            if (!S) S = jzShapeLayer(ctx, 'focus marks', 0, 0);
            if (mark === 'bar') aty_rectGrp(S, 'bar ' + (k2 + 1), La, y + size * 0.62, Lb - La, Math.max(3 * aty_k(ctx), size * 0.07), ac, { piv: [La, y], gsc: hd + '[100*' + q + ',100]' });
            else if (mark === 'dot') {
                for (i = 0; i < sp.g.length; i++) {
                    var gd = sp.g[i], gg = jzGrp(S, 'dot ' + (k2 + 1) + '-' + (i + 1));
                    jzAddEllipse(gg, size * 0.14, size * 0.14, gd.x, gd.y - size * 0.72); jzAddFill(gg, ac);
                    gg = S.property('ADBE Root Vectors Group').property('dot ' + (k2 + 1) + '-' + (i + 1));
                    jzGX(gg).property('ADBE Vector Anchor').setValue([gd.x, gd.y - size * 0.72]); jzGX(gg).property('ADBE Vector Position').setValue([gd.x, gd.y - size * 0.72]);
                    jzSetExpr(jzGX(gg).property('ADBE Vector Scale'), hd + 'var s=100*' + q + ';[s,s]');
                }
            } else {
                var bx = jzGrp(S, 'box ' + (k2 + 1));
                jzAddRect(bx, Lb - La + size * 0.28, size * 1.28, 0, (La + Lb) / 2, y); jzAddStroke(bx, ac, Math.max(1.5 * aty_k(ctx), size * 0.03));
                bx = S.property('ADBE Root Vectors Group').property('box ' + (k2 + 1));
                jzSetExpr(jzGX(bx).property('ADBE Vector Group Opacity'), hd + '100*' + q);
            }
        }
        if (S) jzNoGhost(S);
        return bb || { x0: W * 0.3, y0: H * 0.4, x1: W * 0.7, y1: H * 0.6, cx: W / 2, cy: H / 2 };
    }
});

// ================================================================ pack typo part 3: enters, exits and holds (helpers: p_typo1)
// Per-glyph work = text animators whose Expression Selectors read per-glyph tables (positions / reading order / line) computed
// here from the layer's TextDocument like the browser's layoutText. Lines / brackets / cursors the browser draws in item space
// are shape layers parented to the lyric layer (its layer space = their space), following its opacity.

var ATY_MF = 'function ioc(x){x=cl(x);return x<0.5?4*x*x*x:1-Math.pow(-2*x+2,3)/2;}function lp(a,b,t){return a+(b-a)*t;}';

// geometry of a text layer in its own layer space: glyph centres (baseline - CY em), line extents along the reading axis
function aty_geo(m) {
    var L = m.L, td = L.property('ADBE Text Properties').property('ADBE Text Document').value, i, j;
    var fs = td.fontSize, tr = (td.tracking || 0) / 1000, LD = td.autoLeading === false && td.leading ? td.leading : fs * 1.2;
    var lines = String(td.text).split(/\r\n|\r|\n|\u0003/), nL = lines.length, V = nL >= 2, mono = /Mono|Consolas|Courier/i.test(String(td.font || ''));
    for (i = 0; i < nL; i++) if (jzChars(lines[i]).length > 1) V = false;
    var just = td.justification, left = just === ParagraphJustification.LEFT_JUSTIFY, right = just === ParagraphJustification.RIGHT_JUSTIFY;
    var g = [], ti = 0, ln = [];
    for (i = 0; i < nL; i++) {
        var cs = jzChars(lines[i]), ws = [], w = 0;
        for (j = 0; j < cs.length; j++) { ws.push(aty_adv(cs[j], mono) * fs); w += ws[j] + (j < cs.length - 1 ? tr * fs : 0); }
        var x = left ? 0 : right ? -w : -w / 2, y = i * LD - ATY_CY * fs;
        for (j = 0; j < cs.length; j++) { g.push({ ch: cs[j], x: x + ws[j] / 2, y: y, w: ws[j], h: fs, li: V ? 0 : i, ci: V ? i : j, ti: ti++, sp: aty_isSp(cs[j]) || cs[j] === '' }); x += ws[j] + tr * fs; }
    }
    // lines: a0 / a1 along the reading axis, c = cross position (centre)
    var nLine = V ? 1 : nL;
    for (i = 0; i < nLine; i++) ln.push({ a0: 1e9, a1: -1e9, c: 0, n: 0 });
    for (i = 0; i < g.length; i++) {
        var q = g[i]; if (q.sp) continue;
        var Li = ln[q.li], a0 = V ? q.y - q.h / 2 : q.x - q.w / 2, a1 = V ? q.y + q.h / 2 : q.x + q.w / 2;
        Li.a0 = Math.min(Li.a0, a0); Li.a1 = Math.max(Li.a1, a1); Li.c = V ? q.x : q.y; Li.n++;
    }
    var plain = [], x0 = 1e9, x1 = -1e9, y0 = 1e9, y1 = -1e9;
    for (i = 0; i < g.length; i++) if (!g[i].sp) { plain.push(g[i]); x0 = Math.min(x0, g[i].x - g[i].w / 2); x1 = Math.max(x1, g[i].x + g[i].w / 2); y0 = Math.min(y0, g[i].y - g[i].h / 2); y1 = Math.max(y1, g[i].y + g[i].h / 2); }
    if (x0 > x1) { x0 = x1 = y0 = y1 = 0; }
    return { g: g, ln: ln, plain: plain, V: V, fs: fs, N: g.length, box: { x0: x0, x1: x1, y0: y0, y1: y1, cx: (x0 + x1) / 2, cy: (y0 + y1) / 2, w: x1 - x0, h: y1 - y0 } };
}
function aty_cutN(m) { return Math.max(1, jzCount(String(m.c.text || ''))); }
function aty_mk(m) { return jzClamp((m.ctx.fx.motion != null ? m.ctx.fx.motion : 0.7) / 0.7, 0, 1.6); }
// reading order 0..1 per glyph (spaces 0); single-glyph layers use their mi within the cut
function aty_ord(m, G) {
    var out = [], n = G.plain.length, k = 0, i;
    if (n <= 1) { var N = aty_cutN(m), o = N > 1 ? jzClamp((m.o.mi || 0) / (N - 1), 0, 1) : 0; for (i = 0; i < G.N; i++) out.push(o); return out; }
    for (i = 0; i < G.N; i++) { if (G.g[i].sp) out.push(0); else out.push(k++ / (n - 1)); }
    return out;
}
function aty_rank(G) { var out = [], k = 0; for (var i = 0; i < G.N; i++) out.push(G.g[i].sp ? -1 : k++); return out; }
// key glyph (first kanji of the longest kanji run; latin: first letter of the longest word; else first plain glyph) -> ti
function aty_keyTi(G) {
    var g = G.g, best = -1, bl = 0, i, j;
    for (i = 0; i < g.length; i++) {
        if (!jzIsKanji(g[i].ch)) continue;
        j = i; while (j < g.length && jzIsKanji(g[j].ch)) j++;
        if (j - i > bl) { bl = j - i; best = i; }
        i = j;
    }
    if (best >= 0) return best;
    var wb = -1, wl = 0;
    for (i = 0; i < g.length; i++) {
        if (!/[A-Za-z0-9]/.test(g[i].ch)) continue;
        j = i; while (j < g.length && /[A-Za-z0-9'\u2019]/.test(g[j].ch)) j++;
        if (j - i > wl) { wl = j - i; wb = i; }
        i = j;
    }
    if (wb >= 0) return wb;
    for (i = 0; i < g.length; i++) if (!g[i].sp && !jzIsPunct(g[i].ch) && !aty_isSmall(g[i].ch)) return i;
    return 0;
}
function aty_col(m, which) { var sc = m.ctx.sc; return which === 'sub' ? aty_subc(sc) : aty_acc(sc); }
// per-glyph animators sharing one body: the body sets s sx sy (scale) dx dy (px) a (alpha) cm (colour mix) bl (blur 0..1) sw (outline 0..1) h (hide)
// o: { pos: bound px, sc: 1, op: 1, col: hex, blur: px, fillOp: 1, stroke: px }
function aty_fx(m, name, pre, body, o) {
    var L = m.L, hd = m.HD + ATY_MF + pre + 'var i=textIndex-1,s=1,sx=1,sy=1,dx=0,dy=0,a=1,cm=0,bl=0,sw=0,h=false;' + body + ';\n';
    if (o.pos) { var KB = Math.max(1, o.pos); jzAnimator(L, name + ' Move', [['ADBE Text Position 3D', [KB, KB, 0]]], hd + '[dx/' + jzN(KB) + '*100,dy/' + jzN(KB) + '*100,0]'); }
    if (o.sc) jzAnimator(L, name + ' Size', [['ADBE Text Scale 3D', [400, 400, 100]]], hd + '[Math.max(-100,Math.min(100,(s*sx-1)/3*100)),Math.max(-100,Math.min(100,(s*sy-1)/3*100)),0]');
    if (o.col) jzAnimator(L, name + ' Colour', [['ADBE Text Fill Color', jzHex(o.col)]], hd + 'cl(cm)*100');
    if (o.blur) jzAnimator(L, name + ' Soft', [['ADBE Text Blur', [o.blur, o.blur]]], hd + 'cl(bl)*100');
    if (o.dot) {       // tiny glyph + thick round stroke in the accent colour = a dot
        var td = L.property('ADBE Text Properties').property('ADBE Text Document').value;
        if (!td.applyStroke) jzTextDoc(L, function (d) { d.applyStroke = true; d.strokeColor = d.fillColor; d.strokeWidth = 0; });
        jzAnimator(L, name + ' Dot', [['ADBE Text Stroke Width', m.size * 0.9], ['ADBE Text Stroke Color', jzHex(o.dot)]], hd + 'cl(bl)*100');
    }
    if (o.stroke) jzAnimator(L, name + ' Outline', [['ADBE Text Stroke Width', o.stroke], ['ADBE Text Fill Opacity', 0]], hd + 'cl(sw)*100');
    if (o.op) jzAnimator(L, name + ' Alpha', [['ADBE Text Opacity', 0]], hd + 'h?100:(1-cl(a))*100');
}
// shape layer living in the lyric layer's space (parented, identity transform), following its opacity, main pass only
function aty_post(m, name) {
    var S = jzShapeLayer(m.ctx, name, 0, 0);
    S.parent = m.L;
    jzXf(S, 'ADBE Anchor Point').setValue([0, 0]); jzXf(S, 'ADBE Position').setValue([0, 0]); jzXf(S, 'ADBE Scale').setValue([100, 100]); jzXf(S, 'ADBE Rotate Z').setValue(0);
    jzSetExpr(jzXf(S, 'ADBE Opacity'), 'var p=thisLayer.parent;p?p.transform.opacity:value');
    jzNoGhost(S);
    return S;
}
// per-line rule segments in layer space: from a (expr, 0..1 along the line) to b (expr), offset across the line by off (px)
function aty_lineRules(m, S, G, off, col, lw, qa, qb, gop, pre2) {
    for (var k = 0; k < G.ln.length; k++) {
        var Ln = G.ln[k]; if (!Ln.n) continue;
        var c = Ln.c + off, seg = G.V ? [[c, Ln.a0], [c, Ln.a1]] : [[Ln.a0, c], [Ln.a1, c]], pre = m.HD + ATY_MF + (pre2 || '') + 'var k=' + k + ';';
        aty_grp(S, 'rule ' + (k + 1), [seg], col, { width: lw, start: pre + '100*cl(' + qa + ')', trim: pre + '100*cl(' + qb + ')', gop: gop ? pre + gop : null });
    }
}
// layer mask in layer space: the first mask of the layer adds, later ones intersect
function aty_mask(L, x0, y0, x1, y1, expandExpr) {
    var mp = L.property('ADBE Mask Parade'), first = mp.numProperties === 0;
    var mk = jzMaskRect(L, x0, y0, x1, y1);
    if (!first) { try { mk.maskMode = MaskMode.INTERSECT; } catch (e) {} }
    if (expandExpr) mk.property('ADBE Mask Offset').expression = expandExpr;
    return mk;
}
// brackets \u300C \u300D around the box, opening (half = expr in px) along the reading axis
function aty_brackets(m, G, halfExpr, alphaExpr) {
    var b = G.box, V = G.V, size = m.size, g = size * 0.18, Lb = Math.max(size * 0.28, (V ? b.w : b.h) * 0.3), lw = Math.max(2 * m.u, size * 0.055), col = aty_col(m);
    var S = aty_post(m, 'JZ brackets'), hd = m.HD + ATY_MF;
    if (!V) {
        var yt = b.cy - b.h / 2 - g * 0.5, yb = b.cy + b.h / 2 + g * 0.5, xl = b.cx - g, xr = b.cx + g;
        aty_grp(S, 'open', [[[xl, yt + Lb], [xl, yt], [xl + Lb, yt]]], col, { width: lw, gpos: hd + 'var hf=' + halfExpr + ';[-hf,0]' });
        aty_grp(S, 'close', [[[xr, yb - Lb], [xr, yb], [xr - Lb, yb]]], col, { width: lw, gpos: hd + 'var hf=' + halfExpr + ';[hf,0]' });
    } else {
        var yt2 = b.cy - g, yb2 = b.cy + g, xr2 = b.cx + b.w / 2 + g * 0.5, xl2 = b.cx - b.w / 2 - g * 0.5;
        aty_grp(S, 'open', [[[xr2 - Lb, yt2], [xr2, yt2], [xr2, yt2 + Lb]]], col, { width: lw, gpos: hd + 'var hf=' + halfExpr + ';[0,-hf]' });
        aty_grp(S, 'close', [[[xl2 + Lb, yb2], [xl2, yb2], [xl2, yb2 - Lb]]], col, { width: lw, gpos: hd + 'var hf=' + halfExpr + ';[0,hf]' });
    }
    jzSetExpr(jzXf(S, 'ADBE Opacity'), hd + 'var p=thisLayer.parent;(p?p.transform.opacity:100)*cl(' + alphaExpr + ')');
}
function aty_isSolo(m, G) { return G.plain.length <= 1 && aty_cutN(m) > 1; }
// thin mask at the box centre, opened by mask expansion (half px); 'after' = expansion once the move is over
function aty_clipMask(m, G, halfExpr) {
    var b = G.box, big = m.size * 4, r = jzRect(m.L);
    var x0 = Math.min(b.x0, r.left) - big, x1 = Math.max(b.x1, r.left + r.width) + big, y0 = Math.min(b.y0, r.top) - big, y1 = Math.max(b.y1, r.top + r.height) + big;
    if (G.V) aty_mask(m.L, x0, b.cy - 0.01, x1, b.cy + 0.01, m.HD + ATY_MF + halfExpr);
    else aty_mask(m.L, b.cx - 0.01, y0, b.cx + 0.01, y1, m.HD + ATY_MF + halfExpr);
}

/* ================================================================ ENTRANCES */

/* \u30AD\u30FC\u5B57\u5148\u884C \u2014 the key glyph lands first (big -> size), the rest slide out from behind it */
jzReg('enter', 'tyKeyFirst', { selfHide: true, apply: function (m) {
    var G = aty_geo(m), i;
    if (G.plain.length <= 1) { aty_fx(m, 'JZ In Key', 'var q=cl(P/0.7);', 'if(q<=0)h=true;s=lp(1.9,1,oe(q));a=cl(q*4)', { sc: 1, op: 1 }); return; }
    var ki = aty_keyTi(G), gk = G.g[ki], md = 1, D = [], DX = [], DY = [], KB = 1;
    for (i = 0; i < G.N; i++) if (!G.g[i].sp) md = Math.max(md, Math.abs(i - ki));
    for (i = 0; i < G.N; i++) { D.push(Math.abs(i - ki) / md); DX.push(gk.x - G.g[i].x); DY.push(gk.y - G.g[i].y); KB = Math.max(KB, Math.abs(DX[i]), Math.abs(DY[i])); }
    aty_fx(m, 'JZ In Key', 'var KI=' + ki + ',D=' + aty_arr(D) + ',DX=' + aty_arr(DX) + ',DY=' + aty_arr(DY) + ';',
        'var kq=cl(P/0.42);if(i===KI){if(kq<=0)h=true;s=lp(2.1,1,oe(kq));a=cl(kq*4);}else{var q=cl((P-0.3-D[i]*0.35)/0.35);if(q<=0)h=true;var e=oe(q);dx=DX[i]*(1-e);dy=DY[i]*(1-e);s=lp(0.4,1,e);a=cl(q*2.5);}',
        { pos: KB, sc: 1, op: 1 });
} });

/* \u884C\u9001\u308A\u30EF\u30A4\u30D7 \u2014 each line is wiped on in turn, an accent rule running ahead under it */
jzReg('enter', 'tyLineWipe', { selfHide: true, apply: function (m) {
    var G = aty_geo(m), nL = G.ln.length, st = Math.min(0.28, 0.6 / nL), LI = [], A0 = [], LN = [], LA = [], LB = [], i;
    for (i = 0; i < G.N; i++) { var g = G.g[i]; LI.push(g.li); A0.push(G.V ? g.y - g.h / 2 : g.x - g.w / 2); LN.push(Math.max(1, G.V ? g.h : g.w)); }
    for (i = 0; i < nL; i++) { LA.push(G.ln[i].a0); LB.push(G.ln[i].a1); }
    var Q = 'function qk(k){return ioc(cl((P-k*' + jzN(st) + ')/' + jzN(1 - (nL - 1) * st) + '));}';
    // glyphs appear as the wipe edge passes them (a soft per-glyph edge)
    aty_fx(m, 'JZ In LineWipe', Q + 'var LI=' + aty_arr(LI) + ',A0=' + aty_arr(A0) + ',LN=' + aty_arr(LN) + ',LA=' + aty_arr(LA) + ',LB=' + aty_arr(LB) + ';',
        'var k=LI[i],q=qk(k),ed=lp(LA[k],LB[k],q),f=(ed-A0[i])/LN[i];if(f<=0)h=true;a=q>=1?1:cl(f*1.3)', { op: 1 });
    var S = aty_post(m, 'JZ wipe rule');
    aty_lineRules(m, S, G, m.size * 0.64, aty_col(m), Math.max(2 * m.u, m.size * 0.05), 'ic(cl(qk(k)*1.4-0.4))', 'qk(k)', 'qk(k)<1?100:0', Q);
} });

/* \u4E00\u5B57\u305A\u3064\u62E1\u5927 \u2014 glyphs are flashed one at a time, large in the middle, then set into their slot */
jzReg('enter', 'tyZoomOne', { selfHide: true, apply: function (m) {
    var G = aty_geo(m), nn = G.plain.length, i;
    if (!nn) return;
    var b = G.box, big = jzClamp(Math.min(m.W, m.H) * 0.5 / Math.max(1, m.size), 1.15, 2.6), Lq = nn > 1 ? Math.min(0.55, 2.2 / (nn + 1)) : 1;
    if (nn === 1 && aty_cutN(m) > 1) { aty_fx(m, 'JZ In ZoomOne', 'var q=cl(P);', 'if(q<=0)h=true;s=lp(' + jzN(big) + ',1,oe(q));a=cl(q*5)', { sc: 1, op: 1 }); return; }
    var R = aty_rank(G), CX = [], CY = [], KB = 1;
    for (i = 0; i < G.N; i++) { CX.push(b.cx - G.g[i].x); CY.push(b.cy - G.g[i].y); KB = Math.max(KB, Math.abs(CX[i]), Math.abs(CY[i])); }
    aty_fx(m, 'JZ In ZoomOne', 'var R=' + aty_arr(R) + ',CX=' + aty_arr(CX) + ',CY=' + aty_arr(CY) + ',BG=' + jzN(big) + ',LQ=' + jzN(Lq) + ',NN=' + nn + ';',
        'var k=R[i];if(k<0){h=true;}else{var s0=NN>1?k/(NN-1)*(1-LQ):0,q=cl((P-s0)/LQ);if(q<=0)h=true;' +
        'if(q<0.38){dx=CX[i];dy=CY[i];s=lp(BG*1.25,BG,oc(q/0.38));a=cl(q/0.1);}else if(q<1){var e=ioc((q-0.38)/0.62);dx=CX[i]*(1-e);dy=CY[i]*(1-e);s=lp(BG,1,e);}}',
        { pos: KB, sc: 1, op: 1 });
} });

/* \u4E0B\u7DDA\u304B\u3089\u7ACB\u3064 \u2014 an underline is drawn, the glyphs grow up out of it, the line then retracts */
jzReg('enter', 'tyUnderLift', { selfHide: true, apply: function (m) {
    var G = aty_geo(m), O = aty_ord(m, G), multi = G.plain.length > 1, V = G.V, i, HW = [];
    for (i = 0; i < G.N; i++) HW.push((V ? G.g[i].w : G.g[i].h) * 0.45);
    aty_fx(m, 'JZ In Lift', 'var O=' + aty_arr(O) + ',HW=' + aty_arr(HW) + ';',
        'var q=cl((P-0.22-' + (multi ? 'O[i]' : '0') + '*0.38)/0.4);if(q<=0)h=true;if(q<1){var e=Math.max(0.02,ob(q,1.5));' + (V ? 'sx=e;dx=-(1-e)*HW[i];' : 'sy=e;dy=(1-e)*HW[i];') + '}',
        { pos: m.size, sc: 1, op: 1 });
    var S = aty_post(m, 'JZ lift line');
    aty_lineRules(m, S, G, (V ? -1 : 1) * m.size * 0.56, aty_col(m), Math.max(2.5 * m.u, m.size * 0.055), 'ic(cl((P-0.72)/0.28))', 'oe(cl(P/0.32))', null);
} });

/* \u70B9\u304B\u3089\u5B57 \u2014 every glyph starts as a small accent dot that pops, then grows into the glyph */
jzReg('enter', 'tyDotGrow', { selfHide: true, apply: function (m) {
    var G = aty_geo(m), O = aty_ord(m, G);
    aty_fx(m, 'JZ In Dot', 'var O=' + aty_arr(O) + ';',
        'var t0=O[i]*0.5,q1=cl((P-t0)/0.18),q2=cl((P-t0-0.2)/0.3);if(q1<=0)h=true;else if(q2<=0){s=0.2*ob(q1,2.6);cm=1;bl=1;}else if(q2<1){s=lp(0.3,1,ob(q2,1.8));a=cl(0.35+q2*3);cm=1-cl(q2*2);bl=1-cl(q2*4);}',
        { sc: 1, op: 1, col: aty_col(m), dot: aty_col(m) });
} });

/* \u62EC\u5F27\u304C\u958B\u304F \u2014 \u300C \u300D start together in the middle and slide apart, revealing the line between them */
jzReg('enter', 'tyBracketOpen', { apply: function (m) {
    var G = aty_geo(m), b = G.box, pad = m.size * 0.12, hw = (G.V ? b.h : b.w) / 2 + pad;
    var half = hw + '*ioc(cl((P-0.08)/0.62))';
    aty_clipMask(m, G, 'P>=1?1e4:' + half);
    if (!aty_isSolo(m, G)) aty_brackets(m, G, jzN(hw) + '*ioc(cl((P-0.08)/0.62))', 'cl(P/0.08)*(1-cl((P-0.78)/0.22))');
} });

/* \u6253\u3061\u76F4\u3057 \u2014 typed in with a cursor; one glyph is mistyped, deleted and typed again */
jzReg('enter', 'tyRetype', { selfHide: true, apply: function (m) {
    var G = aty_geo(m), N = G.N, i;
    if (G.plain.length <= 1) { jzAnimator(m.L, 'JZ In Retype', [['ADBE Text Opacity', 0]], m.HD + 'P<0.35?100:0'); return; }
    var seed = (m.c.seed | 0) + (m.o.mi || 0) * 101, wi = -1;
    if (G.plain.length >= 3) wi = G.plain[1 + Math.floor(jzR(seed, 31) * (G.plain.length - 1)) % (G.plain.length - 1)].ti;
    var S0 = N + (wi >= 0 ? 2 : 0);
    var pre = 'var WI=' + wi + ',NN=' + N + ',k=Math.floor(cl(P)*' + jzN(S0 + 0.999) + '),sh,wr=false;' +
        'if(WI<0||k<=WI)sh=Math.min(NN,k);else if(k===WI+1){sh=WI+1;wr=true;}else if(k===WI+2)sh=WI;else sh=Math.min(NN,k-2);';
    aty_fx(m, 'JZ In Retype', pre, 'if(i>=sh)h=true;if(wr&&i===WI)cm=1', { op: 1, col: aty_col(m) });
    if (wi >= 0) jzAnimator(m.L, 'JZ In Typo', [['ADBE Text Character Offset', 3 + Math.floor(jzR(seed, 32) * 9)]], m.HD + pre + '(wr&&textIndex-1===WI)?100:0');
    if (G.V) return;
    // block cursor after the last typed glyph (blinks once the line is complete, gone at the end of the entrance)
    var CX = [], CY = [];
    for (i = 0; i <= N; i++) {
        var g = i > 0 ? G.g[i - 1] : null;
        CX.push(g ? g.x + g.w / 2 + m.size * 0.08 + m.size * 0.25 : (G.g[0] ? G.g[0].x - G.g[0].w / 2 + m.size * 0.25 : 0)); CY.push(g ? g.y : (G.g[0] ? G.g[0].y : 0));
    }
    var S = aty_post(m, 'JZ cursor');
    aty_rectGrp(S, 'cursor', -m.size * 0.25, -m.size * 0.45, m.size * 0.5, m.size * 0.9, m.ctx.sc.accent,
        { gpos: m.HD + pre + 'var X=' + aty_arr(CX) + ',Y=' + aty_arr(CY) + ';[X[sh],Y[sh]]' });
    jzSetExpr(jzXf(S, 'ADBE Opacity'), m.HD + pre + 'var p=thisLayer.parent;var o=p?p.transform.opacity:100;(time<DL||P>=1)?0:(sh>=NN&&Math.floor(time*12)%2===1?0:o)');
} });

/* \u30EB\u30D3\u304B\u3089 \u2014 each glyph appears small in the ruby position above its slot, then drops and grows into place */
jzReg('enter', 'tyRubyDrop', { selfHide: true, apply: function (m) {
    var G = aty_geo(m), O = aty_ord(m, G), V = G.V, OF = [], i;
    for (i = 0; i < G.N; i++) OF.push(V ? G.g[i].w * 0.8 : -G.g[i].h * 0.8);
    aty_fx(m, 'JZ In Ruby', 'var O=' + aty_arr(O) + ',OF=' + aty_arr(OF) + ';',
        'var q=cl((P-O[i]*0.45)/0.55);if(q<=0)h=true;else if(q<0.4){' + (V ? 'dx' : 'dy') + '=OF[i];s=0.32;a=cl(q/0.4*1.6);cm=1;}else if(q<1){var e=oc((q-0.4)/0.6);s=lp(0.32,1,e);' + (V ? 'dx' : 'dy') + '=OF[i]*(1-e);cm=e<0.55?1:0;}',
        { pos: m.size, sc: 1, op: 1, col: aty_col(m, 'sub') });
} });

/* ================================================================ EXITS */

/* \u7DDA\u3067\u6D88\u3059 \u2014 a strike line is drawn through each line, the glyphs collapse onto it, then the line retracts */
jzReg('exit', 'tyStrike', { apply: function (m) {
    var G = aty_geo(m), O = aty_ord(m, G), V = G.V;
    aty_fx(m, 'JZ Out Strike', 'var O=' + aty_arr(O) + ';',
        'var q=cl((PO-0.3-O[i]*0.3)/0.4);if(q>=1)h=true;else if(q>0){var e=ic(q);' + (V ? 'sx' : 'sy') + '=1-e*0.95;a=1-e*e;}', { sc: 1, op: 1 });
    var S = aty_post(m, 'JZ strike'), pad = m.size * 0.1, i;
    for (i = 0; i < G.ln.length; i++) { G.ln[i].a0 -= pad; G.ln[i].a1 += pad; }
    aty_lineRules(m, S, G, m.size * 0.04, aty_col(m), Math.max(2.5 * m.u, m.size * 0.07), 'ic(cl((PO-0.74)/0.26))', 'PO>0?oe(cl(PO/0.38)):0', null);
} });

/* \u70B9\u306B\u623B\u308B \u2014 glyphs shrink into small accent dots, which then wink out */
jzReg('exit', 'tyToDot', { apply: function (m) {
    var G = aty_geo(m), O = aty_ord(m, G);
    aty_fx(m, 'JZ Out Dot', 'var O=' + aty_arr(O) + ';',
        'var q=cl((PO-O[i]*0.4)/0.6);if(q>=1)h=true;else if(q>0){if(q<0.5)s=lp(1,0.3,ic(q/0.5));else{var u=(q-0.5)/0.5;s=0.22*(1-ic(u));cm=1;bl=1;a=1-u*u;}}',
        { sc: 1, op: 1, col: aty_col(m), dot: aty_col(m) });
} });

/* \u6539\u884C\u9001\u308A \u2014 the text line-feeds upward in three steps and leaves through a window */
jzReg('exit', 'tyLineFeed', { apply: function (m) {
    var G = aty_geo(m), b = G.box, V = G.V, pad = m.size * 0.12, dist = (V ? b.w : b.h) + m.size * 0.3;
    var st = 'var kk=cl(PO)*3,sg=Math.min(3,Math.floor(kk)),ff=kk-sg,stp=Math.min(1,(sg+oc(cl(ff*2.6)))/3);';
    jzAnimator(m.L, 'JZ Out LineFeed', [['ADBE Text Position 3D', V ? [dist, 0, 0] : [0, -dist, 0]]], m.HD + st + 'stp*100');
    if (V) aty_mask(m.L, b.x0 - pad, b.y0 - m.size * 4, b.x1 + pad, b.y1 + m.size * 4, m.HD + 'PO<=0?1e4:0');
    else aty_mask(m.L, b.x0 - m.size * 4, b.y0 - pad, b.x1 + m.size * 4, b.y1 + pad, m.HD + 'PO<=0?1e4:0');
    m.parts.op.push('f*=PO>=0.999?0:1;');
} });

/* \u62EC\u5F27\u9589\u3058 \u2014 \u300C \u300D appear at the ends and close in to the middle, taking the line with them */
jzReg('exit', 'tyBracketClose', { apply: function (m) {
    var G = aty_geo(m), b = G.box, hw = (G.V ? b.h : b.w) / 2 + m.size * 0.12, half = jzN(hw) + '*(1-ioc(cl((PO-0.12)/0.7)))';
    aty_clipMask(m, G, 'PO<=0?1e4:' + half);
    m.parts.op.push('f*=cl((PO-0.12)/0.7)>=1?0:1;');
    if (!aty_isSolo(m, G)) aty_brackets(m, G, half, 'cl(PO/0.12)*(1-cl((PO-0.84)/0.16))');
} });

/* \u756A\u53F7\u306B\u5909\u308F\u308B \u2014 each glyph turns into its small index number, then the numbers fade */
jzReg('exit', 'tyToIndex', { apply: function (m) {
    var G = aty_geo(m), O = aty_ord(m, G), V = G.V, i;
    aty_fx(m, 'JZ Out Index', 'var O=' + aty_arr(O) + ';', 'var q=cl((PO-O[i]*0.4)/0.6);if(q>=0.3)h=true;else if(q>0)s=lp(1,0.55,ic(q/0.3))', { sc: 1, op: 1 });
    var base = G.plain.length <= 1 ? Math.round(m.o.mi || 0) : 0, strs = [], J = [], off = [], fs = m.size * 0.42, LD = fs * 1.2, k = 0;
    for (i = 0; i < G.N; i++) {
        var g = G.g[i]; if (g.sp) continue;
        var t = aty_p2(base + (++k)), cs = jzChars(t);
        for (var c2 = 0; c2 < cs.length; c2++) { J.push(i); off.push([g.x, g.y + ATY_CY * fs - (strs.length) * LD]); }
        strs.push(t);
    }
    if (!strs.length) return;
    // the numbers: one text layer in the lyric's space, each number over its glyph
    var T = jzText(m.ctx, strs.join('\r'), { font: jzMonoF(m.ctx), size: fs, color: aty_col(m, 'sub'), x: 0, y: 0, align: 'center', track: 0, leading: LD, name: 'JZ indices' });
    T.parent = m.L;
    jzXf(T, 'ADBE Anchor Point').setValue([0, 0]); jzXf(T, 'ADBE Position').setValue([0, 0]); jzXf(T, 'ADBE Scale').setValue([100, 100]); jzXf(T, 'ADBE Rotate Z').setValue(0);
    jzCharOffsets(T, off, 'JZ Place');
    var pre = m.HD + 'var O=' + aty_arr(O) + ',J=' + aty_arr(J) + ';var j=J[textIndex-1]||0,q=cl((PO-O[j]*0.4)/0.6),u=(q-0.3)/0.7;';
    jzAnimator(T, 'JZ Index Drift', [['ADBE Text Position 3D', V ? [-m.size * 0.2, 0, 0] : [0, -m.size * 0.2, 0]]], pre + 'cl(u)*100');
    jzAnimator(T, 'JZ Index Alpha', [['ADBE Text Opacity', 0]], pre + '(q<0.3||q>=1)?100:ic(u)*100');
    jzSetExpr(jzXf(T, 'ADBE Opacity'), 'var p=thisLayer.parent;p?p.transform.opacity:value');
    jzNoGhost(T);
} });

/* \u4E00\u5B57\u6B8B\u3057 \u2014 everything folds into the key glyph, which then swells and fades */
jzReg('exit', 'tyKeyLast', { apply: function (m) {
    var G = aty_geo(m), i;
    if (G.plain.length <= 1 && aty_cutN(m) > 1) {
        var keep = !!G.plain[0] && jzIsKanji(G.plain[0].ch) && jzR(m.c.seed | 0, Math.round(m.o.mi || 0), 5) < 0.5;
        if (!keep) aty_fx(m, 'JZ Out Key', 'var q=cl(PO/0.5);', 'if(q>=1)h=true;s=1-0.6*ic(q);a=1-q', { sc: 1, op: 1 });
        else aty_fx(m, 'JZ Out Key', 'var q=cl((PO-0.4)/0.6);', 'if(q>=1)h=true;s=1+ic(q)*1.4;a=1-ic(q)', { sc: 1, op: 1 });
        return;
    }
    var ki = G.plain.length > 1 ? aty_keyTi(G) : (G.plain[0] ? G.plain[0].ti : 0), gk = G.g[ki] || G.g[0], md = 1, D = [], DX = [], DY = [], KB = 1;
    for (i = 0; i < G.N; i++) if (!G.g[i].sp) md = Math.max(md, Math.abs(i - ki));
    for (i = 0; i < G.N; i++) { D.push(Math.abs(i - ki) / md); DX.push(gk.x - G.g[i].x); DY.push(gk.y - G.g[i].y); KB = Math.max(KB, Math.abs(DX[i]), Math.abs(DY[i])); }
    aty_fx(m, 'JZ Out Key', 'var KI=' + ki + ',D=' + aty_arr(D) + ',DX=' + aty_arr(DX) + ',DY=' + aty_arr(DY) + ';',
        'if(i===KI){var q=cl((PO-0.45)/0.55);if(q>=1)h=true;s=1+ic(q)*1.8;a=1-ic(q);}else{var q2=cl((PO-(1-D[i])*0.2)/0.45);if(q2>=1)h=true;else if(q2>0){var e=ic(q2);dx=DX[i]*e;dy=DY[i]*e;s=1-0.6*e;a=1-e;}}',
        { pos: KB, sc: 1, op: 1 });
} });

/* \u4E0B\u7DDA\u3078\u6C88\u3080 \u2014 an underline is drawn, each glyph sinks into it (masked at the line), then the line retracts */
jzReg('exit', 'tyUnderSink', { apply: function (m) {
    var G = aty_geo(m), O = aty_ord(m, G), V = G.V, HW = [], i, off = (V ? -1 : 1) * m.size * 0.56;
    for (i = 0; i < G.N; i++) HW.push(V ? G.g[i].w : G.g[i].h);
    var one = G.ln.length <= 1;
    if (one) aty_fx(m, 'JZ Out Sink', 'var O=' + aty_arr(O) + ',HW=' + aty_arr(HW) + ';',
        'var q=cl((PO-0.2-O[i]*0.4)/0.35);if(q>=1)h=true;else if(q>0){var d=ic(q)*1.25;' + (V ? 'dx=-HW[i]*d' : 'dy=HW[i]*d') + ';}', { pos: m.size * 1.4, op: 1 });
    // several lines: a static clip per line would let glyphs show again in the next line's band -> they squash into their underline instead
    else aty_fx(m, 'JZ Out Sink', 'var O=' + aty_arr(O) + ',HW=' + aty_arr(HW) + ';',
        'var q=cl((PO-0.2-O[i]*0.4)/0.35);if(q>=1)h=true;else if(q>0){var d=ic(q);' + (V ? 'sx=1-d;dx=-HW[i]*0.55*d' : 'sy=1-d;dy=HW[i]*0.55*d') + ';}', { pos: m.size, sc: 1, op: 1 });
    // one line: clip it at its underline while sinking (the mask opens fully outside the exit)
    var big = m.size * 6, r = jzRect(m.L);
    if (one && G.ln[0] && G.ln[0].n) {
        var Ln = G.ln[0], c = Ln.c + off;
        if (V) aty_mask(m.L, c, Ln.a0 - big, c + big, Ln.a1 + big, m.HD + 'PO<=0?1e4:0');
        else aty_mask(m.L, Math.min(Ln.a0, r.left) - big, c - big, Math.max(Ln.a1, r.left + r.width) + big, c, m.HD + 'PO<=0?1e4:0');
    }
    var S = aty_post(m, 'JZ sink line');
    aty_lineRules(m, S, G, off, aty_col(m), Math.max(2.5 * m.u, m.size * 0.05), 'ic(cl((PO-0.78)/0.22))', 'PO>0?oe(cl(PO/0.28)):0', null);
} });

/* \u7E26\u7D44\u306B\u6298\u308C\u308B \u2014 a horizontal line folds down into a vertical column (a column folds into a row), then fades */
jzReg('exit', 'tyFoldVert', { apply: function (m) {
    var G = aty_geo(m), V = G.V, i, DX = [], DY = [], KB = 1, lines = {};
    for (i = 0; i < G.N; i++) { var g = G.g[i]; if (g.sp) continue; if (!lines[g.li]) lines[g.li] = []; lines[g.li].push(g); }
    for (i = 0; i < G.N; i++) {
        var q = G.g[i], dx = 0, dy = 0;
        if (!q.sp) {
            var Ls = lines[q.li], n = Ls.length, k = 0, c0 = 1e9, c1 = -1e9;
            for (var j = 0; j < n; j++) { if (Ls[j] === q) k = j; c0 = Math.min(c0, V ? Ls[j].y : Ls[j].x); c1 = Math.max(c1, V ? Ls[j].y : Ls[j].x); }
            var mid = (c0 + c1) / 2, pitch = Math.min(1.02, (V ? m.W : m.H) * 0.82 / Math.max(1, n * (V ? q.w : q.h))), of = (k - (n - 1) / 2) * pitch;
            if (!V) { dx = mid - q.x; dy = of * q.h; } else { dy = mid - q.y; dx = of * q.w; }
        }
        DX.push(dx); DY.push(dy); KB = Math.max(KB, Math.abs(dx), Math.abs(dy) + m.size);
    }
    aty_fx(m, 'JZ Out Fold', 'var DX=' + aty_arr(DX) + ',DY=' + aty_arr(DY) + ',e=ioc(cl(PO/0.5)),ff=cl((PO-0.36)/0.5),dr=iq(ff)*' + jzN(m.size * 0.25) + ';',
        'if(ff>=1)h=true;dx=DX[i]*e' + (V ? '-dr' : '') + ';dy=DY[i]*e' + (V ? '' : '+dr') + ';a=1-iq(ff)', { pos: KB, op: 1 });
} });

/* ================================================================ HOLDS */

/* \u4E00\u5B57\u306E\u9F13\u52D5 \u2014 only the key glyph pulses */
jzReg('hold', 'tyKeyPulse', { apply: function (m) {
    var G = aty_geo(m);
    if (!G.plain.length) return;
    var kk = jzN(0.1 * aty_mk(m)), pulse = 'var pu=Math.pow(0.5+0.5*Math.sin(time*6.2832*0.95),4),kq=' + kk + '*AMT*pu;';
    if (G.plain.length === 1) { if (!jzIsKanji(G.plain[0].ch)) return; aty_fx(m, 'JZ Hold Pulse', pulse, 's=1+kq*0.7', { sc: 1 }); return; }
    aty_fx(m, 'JZ Hold Pulse', pulse + 'var KI=' + aty_keyTi(G) + ';', 'if(i===KI)s=1+kq', { sc: 1 });
} });

/* \u8AAD\u307F\u9001\u308A \u2014 a reading cursor steps along the line: the current glyph lifts a little in the accent colour */
jzReg('hold', 'tyReadCursor', { apply: function (m) {
    var G = aty_geo(m), n = G.plain.length;
    if (!n) return;
    var V = G.V, lift = m.size * 0.07 * Math.min(1, aty_mk(m)), tot = n <= 1 ? aty_cutN(m) : n;
    var pre = 'var ps=Math.floor(time*4)%' + (tot + 2) + ',fr=(time*4)%1,up=oc(cl(fr*4))*(1-ic(cl((fr-0.6)/0.4))),on=AMT>=0.3;';
    var sel = n <= 1 ? '(' + (Math.round(m.o.mi || 0) % (tot + 2)) + '===ps)' : '(R[i]===ps)';
    aty_fx(m, 'JZ Hold Read', pre + 'var R=' + aty_arr(aty_rank(G)) + ';',
        'if(on&&' + sel + '){' + (V ? 'dx=' + jzN(lift) + '*up' : 'dy=-' + jzN(lift) + '*up') + ';cm=1;}', { pos: Math.max(1, lift), col: aty_col(m) });
} });

/* \u767D\u629C\u304D\u660E\u6EC5 \u2014 now and then one glyph switches to outline only for a moment */
jzReg('hold', 'tyOutlineBlink', { apply: function (m) {
    var G = aty_geo(m), n = G.plain.length, td = m.L.property('ADBE Text Properties').property('ADBE Text Document').value;
    if (!n || td.applyFill === false) return;
    if (!td.applyStroke) jzTextDoc(m.L, function (d) { d.applyStroke = true; d.strokeColor = d.fillColor; d.strokeWidth = 0; });
    var pr = 0.28 * (0.5 + 0.5 * aty_mk(m)) / (n === 1 ? Math.max(1, aty_cutN(m) * 0.5) : 1);
    var pre = 'var sp=Math.floor(time*6),R=' + aty_arr(aty_rank(G)) + ',on=hh(SD*0.37+sp*7.13)<' + jzN(pr) + '*AMT,pk=Math.floor(hh(SD*0.61+sp*3.7)*' + n + '),tw=' + (n > 3 ? 'hh(SD*0.23+sp*5.3)<0.3?Math.floor(hh(SD*0.91+sp*2.9)*' + n + '):-1' : '-1') + ';';
    aty_fx(m, 'JZ Hold Blink', pre, 'if(on&&(R[i]===pk||R[i]===tw))sw=1', { stroke: Math.max(1.5 * m.u, m.size * 0.03) });
} });

/* \u5B57\u9593\u30B9\u30C6\u30C3\u30D7 \u2014 letter spacing snaps between a few set values, like a typographer trying options */
jzReg('hold', 'tyTrackStep', { apply: function (m) {
    var G = aty_geo(m);
    if (G.plain.length < 2 || G.V) return;
    var mk = Math.min(1.2, aty_mk(m));
    jzAnimator(m.L, 'JZ Hold Track', [['ADBE Text Tracking Amount', 100 * mk]],
        m.HD + 'var lv=[0,1,2,1],t=time/0.55,k=Math.floor(t),f=t-k,a=lv[k%4],b=lv[(k+1)%4],e=cl((f-0.85)/0.15);e=e>=1?1:1-Math.pow(2,-10*e);(a+(b-a)*e)/2*AMT*100');
} });

// ================================================================ pack typo part 4: decor, text treatments, transitions (helpers: p_typo1, p_typo3)

/* ---------------------------------------------------------------- decor helpers */
function aty_FS(ctx) { return 16 * aty_k(ctx); }
function aty_MG(ctx) { return Math.round(jzU(ctx) * 0.05); }
function aty_hit(x0, y0, x1, y1, bb, pad) { pad = pad || 0; return !(x1 < bb.x0 - pad || x0 > bb.x1 + pad || y1 < bb.y0 - pad || y0 > bb.y1 + pad); }
// a screen corner (inside the margin) clear of the lyric; ok=false when none is free
function aty_corner(ctx, bb, w, h, right, low) {
    var W = ctx.W, H = ctx.H, m = aty_MG(ctx), sx0 = right ? 1 : -1, sy0 = low ? 1 : -1, c = [[sx0, sy0], [-sx0, sy0], [sx0, -sy0], [-sx0, -sy0]];
    for (var i = 0; i < 4; i++) {
        var X = c[i][0] > 0 ? W - m - w : m, Y = c[i][1] > 0 ? H - m - h : m;
        if (!aty_hit(X, Y, X + w, Y + h, bb, 14 * aty_k(ctx))) return { x: X, y: Y, ok: true, sx: c[i][0], sy: c[i][1] };
    }
    return { x: m, y: m, ok: false };
}
function aty_keyChar(text) {
    var a = jzChars(String(text || '')), i;
    for (i = 0; i < a.length; i++) if (jzIsKanji(a[i])) return a[i];
    for (i = 0; i < a.length; i++) if (!aty_isSp(a[i]) && !jzIsPunct(a[i]) && !aty_isSmall(a[i])) return a[i];
    for (i = 0; i < a.length; i++) if (!aty_isSp(a[i])) return a[i];
    return null;
}
function aty_bbok(ctx, bb) {
    if (bb && bb.x1 > bb.x0 && bb.y1 > bb.y0) return { x0: Math.max(bb.x0, -ctx.W * 0.1), x1: Math.min(bb.x1, ctx.W * 1.1), y0: Math.max(bb.y0, -ctx.H * 0.1), y1: Math.min(bb.y1, ctx.H * 1.1) };
    return { x0: ctx.W * 0.35, x1: ctx.W * 0.65, y0: ctx.H * 0.4, y1: ctx.H * 0.6 };
}

/* ================================================================ DECOR */

/* \u5965\u4ED8 \u2014 a small colophon block (line, time, glyph count, reading) typed into a free corner, with an accent hairline */
jzReg('decor', 'tyColophon', { back: false, build: function (ctx, bb0, d) {
    var bb = aty_bbok(ctx, bb0), sc = ctx.sc, c = ctx.cut, k = aty_k(ctx), fs = aty_FS(ctx) * 1.1, lead = fs * 1.7, i;
    var txt = jzTrim(c.text || ''), rom = aty_roma(txt), lt = jzTrim(String(c.lineText || txt).replace(/\s+/g, ' ')), la = jzChars(lt);
    var rows = [[la.length > 18 ? la.slice(0, 17).join('') + '\u2026' : lt, jzBodyF(ctx), sc.fg],
        ['No.' + aty_p2((c.line | 0) + 1) + '  ' + jzFmtTime(c.start) + ' \u2013 ' + jzFmtTime(c.end != null ? c.end : c.start + c.dur), jzMonoF(ctx), sc.sub],
        [jzCount(txt) + ' CHARS' + (rom ? '  /  ' + (rom.length > 16 ? rom.substr(0, 15) + '\u2026' : rom) : ''), jzMonoF(ctx), sc.sub]];
    var w = 0; for (i = 0; i < 3; i++) w = Math.max(w, aty_tw(rows[i][0], rows[i][1], fs, 0.12));
    var h = lead * 3, sp = aty_corner(ctx, bb, w + fs * 1.4, h, !!d.right, !!d.low);
    if (!sp.ok) return;
    var hd = aty_hd(ctx);
    var S = jzShapeLayer(ctx, 'colophon rule', 0, 0), g = jzGrp(S, 'rule');
    jzAddPath(g, [[sp.x, sp.y], [sp.x, sp.y + h]], false); jzAddStroke(g, aty_acc(sc), Math.max(1.5 * k, 2 * k)); jzAddTrimPaths(g, hd + '100*oe(time/0.6)*K');
    aty_op(ctx, S, 'K');
    for (i = 0; i < 3; i++) {       // each row is typed in
        var L = aty_label(ctx, rows[i][0], sp.x + fs * 1.4, sp.y + lead * (i + 0.5), { font: rows[i][1], size: fs, color: rows[i][2], alpha: i ? 0.85 : 1, op: 'K' });
        jzAnimator(L, 'JZ Type', [['ADBE Text Opacity', 0]], ATY_FNS + 'textIndex>Math.floor(textTotal*cl((time-' + jzN(0.1 + i * 0.14) + ')/0.45))?100:0');
    }
} });

/* \u67F1\u3068\u30CE\u30F3\u30D6\u30EB \u2014 a running head (line number + lyric line + hairline) at the top and a folio number at the bottom */
jzReg('decor', 'tyRunningHead', { back: false, build: function (ctx, bb0, d) {
    var bb = aty_bbok(ctx, bb0), W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, k = aty_k(ctx), m = aty_MG(ctx), fs = aty_FS(ctx) * 0.9, hd = aty_hd(ctx), e = 'oe(time/0.7)*K';
    var yH = m + fs * 0.6, right = !!d.right;
    if (!aty_hit(0, yH - fs, W, yH + fs, bb, 8 * k)) {
        var hs = jzChars(aty_p2((c.line | 0) + 1) + '\u3000' + jzTrim(String(c.lineText || c.text || '').replace(/\s+/g, ' '))).slice(0, 30).join('');
        var tw = aty_tw(hs, jzBodyF(ctx), fs, 0.2), x0 = right ? W - m - tw : m;
        aty_label(ctx, hs, x0, yH, { font: jzBodyF(ctx), size: fs, track: 0.2, op: e });
        var a0 = right ? x0 - fs : x0 + tw + fs, a1 = right ? m : W - m;
        if ((a1 - a0) * (right ? -1 : 1) > 20 * k) aty_lines(ctx, 'running head rule', [[[a0, yH], [a1, yH]]], sc.sub, { width: Math.max(1, k), alpha: 0.6, trim: hd + '100*' + e, op: 'K' });
    }
    var yF = H - m - fs * 0.2, folio = jzPad((c.index | 0) + 1, 3), fw = aty_tw(folio, jzMonoF(ctx), fs * 1.3, 0.2), xF = right ? m + fw / 2 : W - m - fw / 2;
    if (!aty_hit(xF - fw, yF - fs * 1.2, xF + fw, yF + fs, bb, 8 * k)) {
        var a = 'oc((time-0.2)/0.4)*K';
        aty_label(ctx, folio, xF, yF, { size: fs * 1.3, align: 'center', color: sc.fg, track: 0.2, op: a });
        aty_lines(ctx, 'folio rule', [[[xF - fw * 0.9, yF - fs * 1.1], [xF + fw * 0.9, yF - fs * 1.1]]], aty_acc(sc), { width: Math.max(1.5 * k, 2 * k), op: a });
    }
} });

/* \u5B57\u5272\u308A\u7DDA \u2014 hairlines at the glyph body edges run from the lyric out to the frame edges, like a type specimen */
jzReg('decor', 'tyGlyphBody', { back: false, build: function (ctx, bb0, d) {
    var bb = aty_bbok(ctx, bb0), W = ctx.W, H = ctx.H, sc = ctx.sc, k = aty_k(ctx), m = aty_MG(ctx) * 0.6, bw = bb.x1 - bb.x0, bh = bb.y1 - bb.y0, i;
    if (bh > bw * 1.3) return;           // vertical lyrics: nothing to divide horizontally
    var n = Math.max(1, Math.min(16, jzCount(String(ctx.cut.text || '')))), gap = 10 * k + bh * 0.06, lw = Math.max(1, 0.9 * k), hd = aty_hd(ctx);
    var S = jzShapeLayer(ctx, 'glyph body lines', 0, 0);
    for (i = 0; i <= n; i++) {
        var x = jzLerp(bb.x0, bb.x1, i / n), t0 = bb.y0 - gap, b0 = bb.y1 + gap, segs = [];
        if (t0 > m) segs.push([[x, t0], [x, m]]);
        if (b0 < H - m) segs.push([[x, b0], [x, H - m]]);
        if (segs.length) aty_grp(S, 'edge ' + (i + 1), segs, sc.sub, { width: lw, alpha: 0.45, trim: hd + '100*oe((time-' + jzN(i * 0.015) + ')/0.7)' });
    }
    var e = 'oe((time-0.1)/0.8)', sides = [];
    for (i = 0; i < 2; i++) { var y = i ? bb.y1 : bb.y0; sides.push([[bb.x0 - gap, y], [m, y]]); sides.push([[bb.x1 + gap, y], [W - m, y]]); }
    aty_grp(S, 'body', sides, sc.sub, { width: lw, alpha: 0.45, trim: hd + '100*' + e });
    aty_op(ctx, S, 'K');
    var fs = aty_FS(ctx) * 0.75;
    if (bb.y0 - gap > m + fs * 2) aty_label(ctx, aty_p2(jzCount(String(ctx.cut.text || ''))) + ' / W' + Math.round(bw / k), bb.x0 + 4 * k, bb.y0 - gap - fs * 0.8, { size: fs, color: aty_acc(sc), op: e + '*K' });
} });

/* \u6587\u5B57\u7F6B \u2014 a rule made of tiny repeated lyric text runs along the top and bottom edges, drifting in opposite directions */
jzReg('decor', 'tyTextRule', { back: false, build: function (ctx, bb0, d) {
    var bb = aty_bbok(ctx, bb0), W = ctx.W, H = ctx.H, sc = ctx.sc, c = ctx.cut, k = aty_k(ctx), m = aty_MG(ctx), fs = aty_FS(ctx) * 0.72;
    var font = (d.v | 0) % 2 ? jzMonoF(ctx) : jzBodyF(ctx), unit = jzTrim(String(c.lineText || c.text || '').replace(/\s+/g, ' ')) + '\u3000\uFF0F\u3000', per = aty_tw(unit, font, fs, 0.18);
    if (per < 4 * k) return;
    var rows = d.n >= 2 ? [m * 0.75, H - m * 0.75] : [d.low ? H - m * 0.75 : m * 0.75], hd = aty_hd(ctx), Lw = W - m * 1.2, x0 = (W - Lw) / 2;
    for (var r = 0; r < rows.length; r++) {
        var y = rows[r];
        if (aty_hit(0, y - fs, W, y + fs, bb, 6 * k)) continue;
        var e = 'oe((time-' + jzN(r * 0.1) + ')/0.8)', dir = r % 2 ? 1 : -1, reps = Math.min(30, Math.ceil(W * 1.2 / per) + 2), s = '';
        for (var q = 0; q < reps; q++) s += unit;
        var T = aty_text(ctx, s, { font: font, size: fs, align: 'left', track: 0.18, x: x0 - per, y: y, color: sc.sub, alpha: 0.8, name: 'text rule ' + (r + 1) });
        // glyphs drift inside a window that opens from the centre (the mask stays put: the drift is a text animator)
        jzAnimator(T, 'JZ Drift', [['ADBE Text Position 3D', [per, 0, 0]]], 'var per=' + jzN(per) + ';(((time*' + jzN(26 * k * dir) + ')%per+per)%per)/per*100');
        var ay = -ATY_CY * fs, cxL = W / 2 - (x0 - per);
        var mk = jzMaskRect(T, cxL - 0.01, ay - fs, cxL + 0.01, ay + fs);
        mk.property('ADBE Mask Offset').expression = hd + jzN(Lw / 2) + '*' + e;
        aty_op(ctx, T, 'K');
        var ry = y + (r || d.low ? -1 : 1) * fs * 1.05;
        aty_lines(ctx, 'text rule line ' + (r + 1), [[[x0, ry], [x0 + Lw, ry]]], sc.sub, { width: Math.max(1, 0.8 * k), alpha: 0.5, trim: hd + '50+50*' + e, start: hd + '50-50*' + e, op: 'K' });
    }
} });

/* \u7D1A\u6570\u898B\u672C \u2014 the key glyph of the lyric set at five falling sizes on one baseline, each with its size, in a free corner */
jzReg('decor', 'tyTypeScale', { back: false, build: function (ctx, bb0, d) {
    var bb = aty_bbok(ctx, bb0), sc = ctx.sc, k = aty_k(ctx), ch = aty_keyChar(ctx.cut.text), i;
    if (!ch) return;
    var S0 = jzU(ctx) * 0.085, ks = [1, 0.72, 0.52, 0.37, 0.26], font = (d.v | 0) % 2 ? jzSerifF(ctx) : jzFontKeyOf(ctx.st, 'display'), gapK = 0.18, w = 0;
    for (i = 0; i < 5; i++) w += S0 * ks[i] * (1 + gapK);
    var fs = aty_FS(ctx) * 0.7, h = S0 + fs * 2.2, sp = aty_corner(ctx, bb, w, h, !!d.right, !!d.low);
    if (!sp.ok) return;
    var yb = sp.y + S0 * 0.95, x = sp.x, gs = [], gp = [], ls = [], lp2 = [];
    for (i = 0; i < 5; i++) { var s = S0 * ks[i]; gs.push(ch); gp.push([x + s / 2, yb - s * 0.46]); ls.push(String(Math.round(s / k * 0.75))); lp2.push([x + s / 2, yb + fs * 1.1]); x += s * (1 + gapK); }
    // the five glyphs: one layer, placed and scaled per glyph
    var G = aty_multi(ctx, gs, gp, { font: font, size: S0, align: 'center', track: 0, color: sc.sub, name: 'type scale' });
    jzCharScales(G.L, ks, 'JZ Size');
    jzAnimator(G.L, 'JZ First', [['ADBE Text Fill Color', jzHex(sc.fg)]], 'textIndex===1?100:0');
    aty_multiFade(ctx, G, 'oc((time-0.1-j*0.07)/0.3)*K');
    var Lb = aty_multi(ctx, ls, lp2, { size: fs, align: 'center', track: 0.05, alpha: 0.9, name: 'type sizes' });
    aty_multiFade(ctx, Lb, 'oc((time-0.1-j*0.07)/0.3)*K');
    aty_lines(ctx, 'type scale base', [[[sp.x, yb + fs * 0.25], [x, yb + fs * 0.25]]], aty_acc(sc), { width: Math.max(1, k), alpha: 0.8, trim: aty_hd(ctx) + '100*oe(time/0.6)', op: 'K' });
} });

/* \u5927\u304D\u306A\u7D04\u7269 \u2014 huge dim \u300C \u300D (or \u300E \u300F / \u201C \u201D) glyphs set behind the lyric at its opposite corners */
jzReg('decor', 'tyBigPunct', { back: false, build: function (ctx, bb0, d) {
    var bb = aty_bbok(ctx, bb0), W = ctx.W, H = ctx.H, sc = ctx.sc, v = (d.v | 0) % 3, pairs = [['\u300C', '\u300D'], ['\u300E', '\u300F'], ['\u201C', '\u201D']], quote = v === 2;
    var S = jzClamp((bb.y1 - bb.y0) * (quote ? 1.6 : 2.1), Math.min(W, H) * 0.22, Math.min(W, H) * 0.55), gap = S * 0.04;
    var font = jzSerifF(ctx), col = d.accent ? jzMixHex(sc.bg, sc.accent, 0.42) : jzMixHex(sc.bg, sc.fg, 0.2), xl, yt, xr, yb;
    if (!quote) { xl = bb.x0 - gap - 0.2 * S; yt = bb.y0 - gap + 0.4 * S; xr = bb.x1 + gap + 0.2 * S; yb = bb.y1 + gap - 0.4 * S; }
    else { xl = bb.x0 - gap - 0.22 * S; yt = bb.y0 + 0.42 * S; xr = bb.x1 + gap + 0.22 * S; yb = bb.y1 + 0.12 * S; }
    var hd = aty_hd(ctx), sl = jzN(S * 0.25);
    for (var i = 0; i < 2; i++) {
        var L = aty_text(ctx, pairs[v][i], { font: font, size: S, x: i ? xr : xl, y: i ? yb : yt, color: col, name: 'big punct ' + (i + 1) });
        jzSetExpr(jzXf(L, 'ADBE Position'), hd + 'var sl=(1-oc(time/0.7))*' + sl + ';[value[0]' + (i ? '+' : '-') + 'sl,value[1]' + (i ? '+' : '-') + 'sl]');
        aty_op(ctx, L, 'oc(time/0.7)*K');
    }
} });

/* ================================================================ TREATMENTS */
function aty_alive(L, amin) {
    var td; try { td = L.property('ADBE Text Properties').property('ADBE Text Document').value; } catch (e) { return false; }
    if (!td || td.applyFill === false || !jzTrim(String(td.text || '')) || !(td.fontSize > 1)) return false;
    try { if (jzXf(L, 'ADBE Opacity').valueAtTime(0, true) < (amin || 0.9) * 100) return false; } catch (e2) {}
    return true;
}
function aty_tm(ctx, L, o) { return { L: L, ctx: ctx, c: ctx.cut, o: o || {}, size: jzFontSize(L), u: ctx.H / 1080, W: ctx.W, H: ctx.H }; }

/* \u4E00\u5B57\u629C\u304D \u2014 the key glyph is set hollow (outline only) among solid glyphs, or the reverse */
jzReg('treat', 'tyHollowKey', {
    plan: function (rng, st) { return { rev: rng.chance(0.35) }; },
    apply: function (ctx, L, P, o) {
        if (!aty_alive(L, 0.5)) return;
        var m = aty_tm(ctx, L, o), G = aty_geo(m), plain = G.plain, flags = [], i, ki = -1;
        if (!plain.length) return;
        if (plain.length === 1) { if (plain[0].ch !== aty_keyChar(ctx.cut.text) || P.rev) return; ki = plain[0].ti; }
        else { var kc = aty_keyChar(jzTrim(String(L.property('ADBE Text Properties').property('ADBE Text Document').value.text))); ki = plain[0].ti; for (i = 0; i < plain.length; i++) if (plain[i].ch === kc) { ki = plain[i].ti; break; } }
        for (i = 0; i < G.N; i++) flags.push(G.g[i].sp ? 0 : ((i === ki) !== !!P.rev ? 1 : 0));
        jzTextDoc(L, function (d) { if (!d.applyStroke) { d.applyStroke = true; d.strokeColor = d.fillColor; d.strokeWidth = 0; } });
        jzAnimator(L, 'JZ Hollow', [['ADBE Text Stroke Width', Math.max(1.5 * m.u, m.size * 0.035)], ['ADBE Text Fill Opacity', 0]], 'var a=' + aty_arr(flags) + ';(a[textIndex-1]||0)*100');
    }
});

/* \u5929\u5730\u7F6B \u2014 a heavy rule above and a hairline below every line (right / left of a vertical column) */
jzReg('treat', 'tyHeadRules', {
    plan: function (rng, st) { return { k: rng.range(0.06, 0.09), acc: rng.chance(0.5) }; },
    apply: function (ctx, L, P, o) {
        if (!aty_alive(L) || jzCount(String(L.property('ADBE Text Properties').property('ADBE Text Document').value.text)) < 2) return;
        var m = aty_tm(ctx, L, o), G = aty_geo(m), s = m.size, V = G.V, col = P.acc ? aty_acc(ctx.sc) : jzTextColor(L), i;
        var dl = (o && o.mi || 0) * (ctx.cut.stagger || 0.04), hd = aty_hd(ctx), S = aty_post(m, 'JZ head rules'), pad = s * 0.12;
        var thick = Math.max(3 * aty_k(ctx), s * (P.k || 0.075)), thin = Math.max(aty_k(ctx), s * 0.018);
        for (var kk = 0; kk < G.ln.length; kk++) {
            var Ln = G.ln[kk]; if (!Ln.n) continue;
            var a0 = Ln.a0 - pad, a1 = Ln.a1 + pad, top = Ln.c + (V ? 1 : -1) * s * 0.66, bot = Ln.c + (V ? -1 : 1) * s * 0.66;
            var q = hd + 'var q=oe((time-' + jzN(dl + 0.05 + kk * 0.08) + ')/0.5),o=ic(PO);';
            var pt = function (u, dd) { return V ? [dd, u] : [u, dd]; };
            aty_grp(S, 'head ' + (kk + 1), [[pt(a0, top), pt(a1, top)]], col, { width: thick, start: q + '100*o', trim: q + '100*Math.max(o,q)' });
            aty_grp(S, 'foot ' + (kk + 1), [[pt(a1, bot), pt(a0, bot)]], col, { width: thin, start: q + '100*o', trim: q + '100*Math.max(o,q)' });
        }
        jzXf(S, 'ADBE Opacity').setValue(100);
        i = 0;
    }
});

/* \u982D\u5B57\u5F37\u8ABF \u2014 the first glyph of the line is set larger (baseline kept) in the accent colour, the rest make room */
jzReg('treat', 'tyHeadBig', {
    plan: function (rng, st) { return { k: rng.range(1.35, 1.6), acc: rng.chance(0.7) }; },
    apply: function (ctx, L, P, o) {
        if (!aty_alive(L) || jzCount(String(L.property('ADBE Text Properties').property('ADBE Text Document').value.text)) < 2) return;
        var m = aty_tm(ctx, L, o), G = aty_geo(m), g0 = null, i, V = G.V;
        for (i = 0; i < G.N; i++) if (!G.g[i].sp && !jzIsPunct(G.g[i].ch)) { g0 = G.g[i]; break; }
        if (!g0) return;
        var k = P.k || 1.45, extra = (k - 1) * (V ? g0.h : g0.w), td = L.property('ADBE Text Properties').property('ADBE Text Document').value;
        var just = td.justification, center = just !== ParagraphJustification.LEFT_JUSTIFY && just !== ParagraphJustification.RIGHT_JUSTIFY;
        var sh = V ? -extra / 2 : center ? -extra / 2 : just === ParagraphJustification.RIGHT_JUSTIFY ? -extra : 0, off = [], KB = 1;
        for (i = 0; i < G.N; i++) {
            var g = G.g[i], dx = 0, dy = 0;
            if (V || g.li === g0.li) {
                if (i === g0.ti) { if (V) dy = sh + extra / 2; else { dx = sh + extra / 2; dy = -(k - 1) * g.h * 0.36; } }
                else if (i < g0.ti) { if (V) dy = sh; else dx = sh; }
                else { if (V) dy = sh + extra; else dx = sh + extra; }
            }
            off.push([dx, dy]); KB = Math.max(KB, Math.abs(dx), Math.abs(dy));
        }
        jzCharOffsets(L, off, 'JZ Head Room');
        var sc2 = []; for (i = 0; i < G.N; i++) sc2.push(i === g0.ti ? k : 1);
        jzCharScales(L, sc2, 'JZ Head Size');
        var ac = P.acc && jzContrast(ctx.sc.accent, ctx.sc.bg) >= 1.8 ? ctx.sc.accent : null;
        if (ac) jzAnimator(L, 'JZ Head Colour', [['ADBE Text Fill Color', jzHex(ac)]], 'textIndex===' + (g0.ti + 1) + '?100:0');
    }
});

/* \u5B57\u756A\u53F7 \u2014 a tiny superscript index number beside every glyph */
jzReg('treat', 'tyIndexSup', {
    plan: function (rng, st) { return { acc: rng.chance(0.6) }; },
    apply: function (ctx, L, P, o) {
        if (!aty_alive(L)) return;
        var m = aty_tm(ctx, L, o), G = aty_geo(m), V = G.V, col = P.acc ? aty_acc(ctx.sc) : ctx.sc.sub, base = G.plain.length === 1 ? Math.round(o && o.mi || 0) : 0;
        var fs = Math.max(10 * aty_k(ctx), m.size * 0.26), LD = fs * 1.2, strs = [], off = [], n = base, i;
        for (i = 0; i < G.N; i++) {
            var g = G.g[i]; if (g.sp) continue;
            n++; if (n > 40 + base) break;
            var t = String(n), x = g.x + (V ? g.w * 0.55 : g.w * 0.52), y = g.y + (V ? -g.h * 0.3 : -g.h * 0.42), cs = jzChars(t);
            for (var c2 = 0; c2 < cs.length; c2++) off.push([x, y + ATY_CY * fs - strs.length * LD]);
            strs.push(t);
        }
        if (!strs.length) return;
        var T = jzText(ctx, strs.join('\r'), { font: jzMonoF(ctx), size: fs, color: col, x: 0, y: 0, align: 'left', track: 0, leading: LD, name: 'JZ glyph indices' });
        T.parent = L;
        jzXf(T, 'ADBE Anchor Point').setValue([0, 0]); jzXf(T, 'ADBE Position').setValue([0, 0]); jzXf(T, 'ADBE Scale').setValue([100, 100]); jzXf(T, 'ADBE Rotate Z').setValue(0);
        jzCharOffsets(T, off, 'JZ Place');
        var dl = (o && o.mi || 0) * (ctx.cut.stagger || 0.04);
        jzSetExpr(jzXf(T, 'ADBE Opacity'), aty_hd(ctx) + 'var p=thisLayer.parent;(p?p.transform.opacity:100)*cl((time-' + jzN(dl + 0.15) + ')/0.3)*(1-ic(PO))');
        jzNoGhost(T);
    }
});

/* ================================================================ TRANSITIONS */
function aty_tacc(t) {
    var sc = t.sc, pb = (t.scPrev || sc).bg, l = [sc.accent, sc.accent2, sc.fg];
    for (var i = 0; i < l.length; i++) if (l[i] && jzContrast(l[i], sc.bg) >= 1.8 && jzContrast(l[i], pb) >= 1.4) return l[i];
    return sc.fg;
}
// the old cut on top for the transition only, cut away by an inverted alpha matte M (built by the caller, groups = revealed parts)
function aty_tcopy(t, name) { var C = t.A.duplicate(); C.name = name; C.moveBefore(t.B); C.inPoint = t.t0; C.outPoint = t.t0 + t.dur; return C; }
function aty_tmatte(M, C) { M.moveBefore(C); C.trackMatteType = TrackMatteType.ALPHA_INVERTED; M.enabled = false; }
function aty_ths(t) { return 'var T0=' + jzN(t.t0) + ',TD=' + jzN(Math.max(1 / 240, t.dur)) + ';' + ATY_FNS + 'var p=cl((time-T0)/TD);'; }

/* \u7F6B\u7DDA\u30EF\u30A4\u30D7 \u2014 the frame is ruled into text lines; each line is "typed" across, a cursor bar leading it */
jzReg('trans', 'tyRuleWipe', {
    plan: function (rng, st) { return { n: rng.int(7, 12), rtl: rng.chance(0.25) }; },
    build: function (t) {
        var W = t.W, H = t.H, n = Math.max(2, Math.round(t.P.n || 9)), rtl = !!t.P.rtl, bh = H / n, ac = aty_tacc(t), lw = Math.max(1, Math.min(W, H) * 0.0015), st = 0.5 / n, i;
        var HS = aty_ths(t), C = aty_tcopy(t, 'JZ Trans rule wipe (prev cut)');
        var M = jzEvShape(t.comp, 'JZ Trans rule wipe matte', t.t0, t.dur), Ln = jzEvShape(t.comp, 'JZ Trans rule wipe lines', t.t0, t.dur), cw = Math.max(3, bh * 0.14);
        for (i = 0; i < n; i++) {
            var y0 = Math.round(i * bh), y1 = Math.round((i + 1) * bh), Q = HS + 'var q=ioc(cl((p-' + jzN(i * st) + ')/' + jzN(1 - (n - 1) * st) + '));', px = rtl ? W : 0;
            aty_rectGrp(M, 'band ' + (i + 1), 0, y0, W, y1 - y0 + 1, '#FFFFFF', { piv: [px, y0], gsc: Q + '[100*q,100]' });
            aty_rectGrp(Ln, 'cursor ' + (i + 1), rtl ? -cw : 0, y0 + bh * 0.2, cw, bh * 0.6, ac, { gpos: Q + '[' + (rtl ? jzN(W) + '*(1-q)' : jzN(W) + '*q') + ',0]', gop: Q + '(q>0&&q<1)?100:0' });
        }
        var rules = []; for (i = 1; i < n; i++) rules.push([[0, Math.round(i * bh)], [W, Math.round(i * bh)]]);
        aty_grp(Ln, 'rules', rules, ac, { width: lw, gop: HS + '60*Math.sin(Math.PI*p)' });
        aty_tmatte(M, C);
    }
});

/* \u5347\u76EE\u9001\u308A \u2014 a manuscript grid; the new cut fills in cell by cell in reading order (vertical: columns right to left) */
jzReg('trans', 'tyGridCells', {
    plan: function (rng, st) { return { rows: rng.int(4, 6), tate: rng.chance(0.7) }; },
    build: function (t) {
        var W = t.W, H = t.H, rows = Math.max(2, Math.round(t.P.rows || 5)), tate = !!t.P.tate, cell = H / rows, cols = Math.ceil(W / cell), ox = (W - cols * cell) / 2;
        var N = rows * cols, ac = aty_tacc(t), lw = Math.max(1, Math.min(W, H) * 0.0016), j, c, r, X = [], Y = [];
        var HS = aty_ths(t) + 'var kk=ioc(cl(p/0.92))*' + N + ',fk=Math.floor(kk);';
        var C = aty_tcopy(t, 'JZ Trans grid cells (prev cut)'), M = jzEvShape(t.comp, 'JZ Trans grid cells matte', t.t0, t.dur);
        for (j = 0; j < N; j++) {
            if (tate) { c = cols - 1 - Math.floor(j / rows); r = j % rows; } else { r = Math.floor(j / cols); c = j % cols; }
            var x = ox + c * cell, y = r * cell; X.push(x + cell / 2); Y.push(y + cell / 2);
            aty_rectGrp(M, 'cell ' + (j + 1), x, y, cell + 1, cell + 1, '#FFFFFF', { gop: HS + (j + '<fk?100:(' + j + '===fk?(kk-fk)*100:0)') });
        }
        var G = jzEvShape(t.comp, 'JZ Trans grid lines', t.t0, t.dur), segs = [];
        for (c = 0; c <= cols; c++) segs.push([[Math.round(ox + c * cell), 0], [Math.round(ox + c * cell), H]]);
        for (r = 1; r < rows; r++) segs.push([[0, Math.round(r * cell)], [W, Math.round(r * cell)]]);
        aty_grp(G, 'grid', segs, ac, { width: lw, gop: HS + '50*Math.sin(Math.PI*p)' });
        var gc = jzGrp(G, 'current');
        jzAddRect(gc, cell - lw * 3, cell - lw * 3, 0, 0, 0); jzAddStroke(gc, ac, lw * 3);
        gc = G.property('ADBE Root Vectors Group').property('current');
        jzSetExpr(jzGX(gc).property('ADBE Vector Position'), HS + 'var X=' + aty_arr(X) + ',Y=' + aty_arr(Y) + ',q=Math.min(fk,' + (N - 1) + ');[X[q],Y[q]]');
        jzSetExpr(jzGX(gc).property('ADBE Vector Group Opacity'), HS + '(p>0&&fk<' + N + ')?100:0');
        aty_tmatte(M, C);
    }
});

// ================================================================ build a composition from a plan
function jzEventsArr(plan, type, t0, t1) {
    var out = [], ev = plan.events || [];
    for (var i = 0; i < ev.length; i++) {
        var e = ev[i]; if (e.type !== type) continue;
        if (t0 != null && (e.t < t0 - 0.7 || e.t > t1 + 0.1)) continue;
        out.push('[' + jzN(e.t) + ',' + jzN(e.amp || 1) + ',' + jzN(Math.max(e.dur || 0, 1 / 24)) + ']');
    }
    return '[' + out.join(',') + ']';
}

// Structure of the generated project:
//   main comp  \u2500\u2500 base background (first scheme) \u2500 one WRAPPER precomp per cut \u2500 effect-event layers \u2500 HUD \u2500 JZ FX \u2500 flash \u2500 vignette
//   wrapper    \u2500\u2500 scheme background + radial lift \u2500 paper \u2500 background graphic \u2500 ghost B \u2500 ghost A \u2500 content \u2500 "JZ Camera" null
//   content    \u2500\u2500 the layout's text / shape layers + decor (built by the registry entries)
// Cut-to-cut transitions work on neighbouring wrapper layers in the main comp.
// jzBuildStart returns a job that builds the comp step by step: job.step(ms) works for about ms milliseconds and returns,
// so the CEP panel / ScriptUI can hand control back to After Effects between steps (long songs: no "not responding").
// job.phase: 'cuts' -> 'trans' -> 'events' -> 'done'; job.done / job.total = cuts built; job.cancelled = true skips the rest and finishes what exists.
function jzBuild(plan, opt) { var job = jzBuildStart(plan, opt); while (!job.finished) job.step(null); return job.comp; }
function jzBuildStart(plan, opt) {
    opt = opt || {};
    JZLOG = []; JZ_FALLBACKS = 0; JZ_FALLBACK_KEYS = []; JZ_FONT_MISSING = {}; JZ_FONT_NOAPI = false;
    jzSetLang(plan.lang || (typeof jzDetectLang === 'function' ? jzDetectLang(plan) : 'ja'));   // \u6B4C\u8A5E\u306E\u8A00\u8A9E \u2192 faces
    var W = opt.width || plan.width || 1920, H = opt.height || plan.height || 1080, fps = plan.fps || 24, D = Math.max(1, plan.duration || 10);
    var st = plan.style, fx = plan.fx || {}, roles = opt.roles || JZ_ROLE_DEFAULT;
    var ghostAmt = (fx.chroma == null ? 0.7 : fx.chroma) * (st.ghost == null ? 1 : st.ghost);
    var title = String(plan.title || 'lyric').substr(0, 20);
    var comp = app.project.items.addComp('JIZURA ' + title, W, H, 1, D, fps);
    // edges uncovered by glitch shifts show the scheme background (the browser redraws over the old frame) instead of black
    try { comp.bgColor = plan.keyBg ? [0, 0, 0] : jzHex(st.schemes[0].bg); } catch (eb) {}
    var folder = app.project.items.addFolder('JIZURA ' + title + ' cuts');
    var u = H / 1080;
    var schemes = st.schemes, cuts = plan.cuts || [];
    var FXV = { motion: fx.motion == null ? 0.7 : fx.motion, glitch: fx.glitch == null ? 0.5 : fx.glitch, decor: fx.decor == null ? 0.5 : fx.decor, texture: fx.texture == null ? 0.6 : fx.texture, chroma: fx.chroma == null ? 0.7 : fx.chroma };
    function schemeOf(c) { return schemes[(c.scheme || 0) % schemes.length] || schemes[0]; }
    var paperAmt = (st.texture && st.texture.paper) || 0;
    // \u8EFD\u91CF (opt.light): no colour-shift ghosts, no paper noise, no bloom / grain / slice warp / zoom blur, and no screen
    // effects that duplicate the picture \u2014 plays back several times faster in After Effects
    var LIGHT = !!opt.light;
    // \u5408\u6210\u7528\u306E\u80CC\u666F: the plan's style is already white-on-black; no lift / paper / background graphic / vignette,
    // the finished comp is made monochrome (Tint) and \u2014 for green \u2014 screened onto #00FF00
    var KEY = (plan.keyBg === 'green' || plan.keyBg === 'black') ? plan.keyBg : null;
    if (KEY) paperAmt = 0;

    // ---------- base background (visible between cuts)
    var base = comp.layers.addSolid(jzHex(schemes[0].bg), 'JZ Background', W, H, 1, D);
    if (!KEY) jzBgLift(base, schemes[0], W, H);

    // ---------- cuts
    var lagA = 0.8 / 24, lagB = 1.6 / 24, wraps = [];
    // the lyric (or its companion) of one cut: content comp + ghosts + camera null, in comp T (the wrapper, or a
    // band-sized "stage" comp for \u4E2D\u592E\u3092\u7A7A\u3051\u308B \u2014 its bounds clip it, like the browser's clip)
    function buildContent(c, T, CW, CH, cu, sc, label, cdur, ci) {
        var pc = app.project.items.addComp(label + ' text', CW, CH, 1, cdur, fps);
        pc.parentFolder = folder;
        var ctx = { comp: pc, W: CW, H: CH, u: cu, sc: sc, st: st, fx: FXV, cut: c, P: c.params || {}, roles: roles, plan: plan };
        var bb = null, lk = jzFallback('layout', c.layout, 'center');
        if (lk !== c.layout) { ctx.P = JZ_REG.layout[lk].plan ? JZ_REG.layout[lk].plan(new JzRng(jzHash(c.seed, 31)), { text: c.text, n: jzCount(c.text), W: CW, H: CH, dur: c.dur }, st) : {}; }
        try { bb = JZ_REG.layout[lk].build(ctx); }
        catch (e) { jzWarn('cut ' + (ci + 1) + ' ' + lk + ': ' + e.toString() + (e.line ? ' (line ' + e.line + ')' : '')); }
        try { jzDecorate(ctx, bb); } catch (e2) { jzWarn('decor: ' + e2.toString()); }
        var CL = T.layers.add(pc); CL.name = 'content'; CL.startTime = 0;
        var content = [CL];
        if (ghostAmt > 0.02 && opt.ghosts !== false && !LIGHT) {
            // layers marked with jzNoGhost() stay out of the ghosts: feed them from a copy of the content comp with those layers off
            var gsrc = null, li;
            for (li = 1; li <= pc.numLayers; li++) if (jzIsNoGhost(pc.layer(li))) { gsrc = pc; break; }
            if (gsrc) {
                try {
                    gsrc = pc.duplicate(); gsrc.name = label + ' ghost'; gsrc.parentFolder = folder;
                    jzTidyComp(gsrc, true);          // drop the main-pass-only layers (keeps what the ghosted layers still need)
                } catch (eg) { gsrc = null; jzWarn('ghost copy: ' + eg.toString()); }
            }
            var ghosts = [['B', lagB, [-3.4, -1.3], sc.ghostB], ['A', lagA, [3.2, 1.9], sc.ghostA]];
            for (var g = 0; g < ghosts.length; g++) {
                var G = gsrc ? T.layers.add(gsrc) : CL.duplicate();
                G.name = 'ghost ' + ghosts[g][0];
                G.startTime = ghosts[g][1]; G.inPoint = 0; G.outPoint = cdur;
                G.moveAfter(CL);
                var tint = jzEffect(G, 'ADBE Tint', 'JZ Ghost Colour');
                jzEP(tint, 1, jzHex(ghosts[g][3])); jzEP(tint, 2, jzHex(ghosts[g][3])); jzEP(tint, 3, 100);
                if (jzLum(sc.bg) > 0.55) G.blendingMode = BlendingMode.MULTIPLY;
                var off = ghosts[g][2];
                jzSetExpr(jzXf(G, 'ADBE Position'), 'var T0=' + jzN(c.start) + ',ev=' + jzEventsArr(plan, 'chroma', c.start, c.end) + ';var s=1;for(var i=0;i<ev.length;i++){var dt=(time+T0-ev[i][0])*24;if(dt>=0&&dt<14)s+=ev[i][1]*Math.pow(0.55,dt);}' +
                    'var k=' + jzN(ghostAmt * u) + '*s;[value[0]+' + off[0] + '*k,value[1]+' + off[1] + '*k]');
                content.push(G);
            }
        }
        // camera: a null at the comp centre (identity transform) that carries the content and its ghosts
        var nul = T.layers.addNull(cdur); nul.name = 'JZ Camera';
        jzXf(nul, 'ADBE Anchor Point').setValue([CW / 2, CH / 2]); jzXf(nul, 'ADBE Position').setValue([CW / 2, CH / 2]);
        for (var q = 0; q < content.length; q++) content[q].parent = nul;
        var ck = jzFallback('cam', c.cam || 'push', 'push');
        try { JZ_REG.cam[ck].apply({ ctx: ctx, comp: T, nul: nul, content: content, P: c.camP || {}, W: CW, H: CH, u: cu, cut: c, fx: FXV, sc: sc }, c.camP || {}); }
        catch (e4) { jzWarn('cam ' + ck + ': ' + e4.toString() + (e4.line ? ' (line ' + e4.line + ')' : '')); }
        return CL;
    }
    function buildCut(ci) {
        var cut = cuts[ci];
        if (!(cut.end > cut.start)) return;
        cut.dur = cut.end - cut.start;
        var sc = schemeOf(cut), label = jzPad(ci + 1, 3) + ' ' + String(cut.text || cut.layout).substr(0, 16);
        var cdur = Math.max(cut.dur, 1 / fps) + 1.0;
        // wrapper: the full-frame background of the cut
        var wc = app.project.items.addComp(label, W, H, 1, cdur, fps);
        wc.parentFolder = folder;
        var bgL = wc.layers.addSolid(jzHex(sc.bg), 'JZ BG', W, H, 1, cdur);
        if (!KEY) jzBgLift(bgL, sc, W, H);
        if (paperAmt > 0.05 && FXV.texture > 0.05 && !LIGHT) {
            var pp = wc.layers.addSolid([0.5, 0.5, 0.5], 'JZ Paper', W, H, 1, cdur);
            var pn = jzEffect(pp, 'ADBE Fractal Noise', 'JZ Paper Noise'); jzEP(pn, 4, 60);
            // the browser's paper is a faint fibre texture: keep the overlay light, fainter still on dark schemes
            pp.blendingMode = BlendingMode.OVERLAY; jzXf(pp, 'ADBE Opacity').setValue((jzLum(sc.bg) < 0.4 ? 6 : 12) * paperAmt);
        }
        var bk = !KEY && cut.bg && cut.bg !== 'none' ? jzFallback('bg', cut.bg, null) : null;
        if (bk) {
            var bctx = { comp: wc, W: W, H: H, u: u, sc: sc, st: st, fx: FXV, cut: cut, plan: plan, P: cut.bgP || {} };
            try { JZ_REG.bg[bk].build(bctx, bctx.P); } catch (e3) { jzWarn('bg ' + bk + ': ' + e3.toString() + (e3.line ? ' (line ' + e3.line + ')' : '')); }
        }
        // \u4E2D\u592E\u3092\u7A7A\u3051\u308B: the cut lives in its side band, its companion (the other side) in the other band
        var parts = [[cut, plan.centerFree && cut.zone ? cut.zone : null]];
        if (plan.centerFree && cut.companion && cut.companion.zone) parts.push([cut.companion, cut.companion.zone]);
        var mainStage = null, mainContent = null;
        for (var pi = 0; pi < parts.length; pi++) {
            var c = parts[pi][0], Z = parts[pi][1];
            c.dur = c.end - c.start;
            var CW = Z ? Z.w : W, CH = Z ? Z.h : H, cu = Z ? CH / 1080 : u, T = wc;
            if (Z) { T = app.project.items.addComp(label + (pi ? ' side' : ' stage'), CW, CH, 1, cdur, fps); T.parentFolder = folder; }
            var CL = buildContent(c, T, CW, CH, cu, sc, label + (pi ? ' side' : ''), cdur, ci);
            if (Z) { var SL = wc.layers.add(T); SL.name = (pi ? 'side (' : 'stage (') + (Z.side || 'side') + ')'; SL.startTime = Math.max(0, c.start - cut.start); jzXf(SL, 'ADBE Position').setValue([Z.x + CW / 2, Z.y + CH / 2]); }
            if (!pi) { mainStage = Z ? T : null; mainContent = CL; }
        }
        // into the main comp
        var WL = comp.layers.add(wc);
        WL.startTime = cut.start; WL.inPoint = cut.start; WL.outPoint = cut.end;
        WL.name = (jzPad(ci + 1, 3) + ' ' + (cut.text || '')).substr(0, 24);
        wraps.push({ cut: cut, layer: WL, comp: wc, sc: sc, stage: mainStage, content: mainContent });
    }
    function finishTrans() {
        // ---------- cut-to-cut transitions (the previous wrapper holds its last state under the new one)
        for (var wi = 1; wi < wraps.length; wi++) {
            var B = wraps[wi], A = wraps[wi - 1], tk = B.cut.trans ? jzFallback('trans', B.cut.trans, null) : null;
            if (!tk || Math.abs(A.cut.end - B.cut.start) > 0.06) continue;
            var td = jzClamp(B.cut.transDur || jzMeta('trans', tk).dur || 0.35, 0.08, Math.max(0.1, B.cut.dur * 0.6));
            A.layer.outPoint = B.cut.start + td;
            var tctx = { comp: comp, W: W, H: H, u: u, A: A.layer, B: B.layer, t0: B.cut.start, dur: td, P: B.cut.transP || {}, sc: B.sc, scPrev: A.sc, st: st, fx: FXV, cut: B.cut, prev: A.cut, fps: fps };
            try { JZ_REG.trans[tk].build(tctx); } catch (e5) { jzWarn('trans ' + tk + ': ' + e5.toString() + (e5.line ? ' (line ' + e5.line + ')' : '')); }
        }

        // ---------- HUD
        if (plan.hud && opt.hud !== false) jzHUD(comp, plan, schemes[0], roles);

        // ---------- global FX (top adjustment layer)
        var fxL = comp.layers.addSolid([1, 1, 1], 'JZ FX', W, H, 1, D); fxL.adjustmentLayer = true;
        if (fx.onTwos !== false && (fx.koma == null || fx.koma > 0)) { var pt = jzEffect(fxL, 'ADBE Posterize Time', 'JZ Koma'); jzEP(pt, 1, fx.koma > 0 ? fx.koma : 12); }
        var tr = jzEffect(fxL, 'ADBE Geometry2', 'JZ Shake');
        jzEX(tr, 2, 'var ev=' + jzEventsArr(plan, 'shake') + ';var s=0;for(var i=0;i<ev.length;i++){var dt=(time-ev[i][0])*24;if(dt>=0&&dt<14)s+=ev[i][1]*Math.pow(0.62,dt);}seedRandom(Math.floor(time*12),true);[value[0]+random(-1,1)*s*16*' + jzN(u) + ',value[1]+random(-1,1)*s*11*' + jzN(u) + ']');
        if (!LIGHT) {
        var ww = jzEffect(fxL, 'ADBE Wave Warp', 'JZ Slice Glitch');
        jzEP(ww, 1, 2); jzEP(ww, 4, 0); jzEP(ww, 5, 0); jzEP(ww, 6, 1);
        jzEX(ww, 2, 'var ev=' + jzEventsArr(plan, 'slice') + ';var h=0;for(var i=0;i<ev.length;i++){var dt=time-ev[i][0];if(dt>=0&&dt<ev[i][2])h=Math.max(h,ev[i][1]);}posterizeTime(24);seedRandom(Math.floor(time*24),true);h>0?h*thisComp.width*0.05*random(0.4,1):0');
        jzEX(ww, 3, 'posterizeTime(24);seedRandom(Math.floor(time*24)+7,true);random(thisComp.height*0.02,thisComp.height*0.12)');
        jzEX(ww, 7, 'posterizeTime(24);seedRandom(Math.floor(time*24)+11,true);random(0,360)');
        }
        var rb = LIGHT ? null : jzEffect(fxL, 'CC Radial Blur', 'JZ Zoom Hit');
        jzEX(rb, 2, 'var ev=' + jzEventsArr(plan, 'zoom') + ';var a=0;for(var i=0;i<ev.length;i++){var dt=time-ev[i][0];if(dt>=0&&dt<ev[i][2])a=Math.max(a,ev[i][1]*(1-dt/ev[i][2]));}a*30');
        var iv = jzEffect(fxL, 'ADBE Invert', 'JZ Invert Hit');
        jzEX(iv, 2, 'var ev=' + jzEventsArr(plan, 'invert') + ';var on=false;for(var i=0;i<ev.length;i++){var dt=time-ev[i][0];if(dt>=0&&dt<ev[i][2])on=true;}on?0:100');
        var glowAmt = (st.glow || 0.6) * FXV.texture;
        if (glowAmt > 0.05 && !LIGHT) { var gl = jzEffect(fxL, 'ADBE Glo2', 'JZ Bloom'); jzEP(gl, 2, 70); jzEP(gl, 3, 60 * u); jzEP(gl, 4, 0.35 * glowAmt); }
        var gr = (st.texture && st.texture.grain || 0) * FXV.texture;
        if (gr > 0.02 && !LIGHT) { var nz = jzEffect(fxL, 'ADBE Noise', 'JZ Grain'); jzEP(nz, 1, 5 * gr); jzEP(nz, 2, 0); }

    }
    var evs = plan.events || [], fctx = { comp: comp, W: W, H: H, u: u, st: st, fx: FXV, plan: plan, fps: fps, wraps: wraps };
    var HEAVY_FX = { strobe: 1, rgbSplit: 1, radialChroma: 1, echoFrames: 1, kaleido: 1, perspectiveTilt: 1 };
    function buildEvent(ei) {
        var ev = evs[ei], fk = jzFallback('fx', ev.type, null);
        if (!fk || JZ_REG.fx[fk].builtin || (LIGHT && HEAVY_FX[fk])) return;
        var e1 = { t: ev.t, type: fk, amp: ev.amp || 1, dur: Math.max(ev.dur || 0, 1 / 24), seed: jzHash(ev.t, ev.type) % 100000, sc: schemeOf(jzCutAtTime(cuts, ev.t) || cuts[0] || { scheme: 0 }) };
        try { JZ_REG.fx[fk].build(fctx, e1); } catch (e6) { jzWarn('fx ' + fk + ': ' + e6.toString() + (e6.line ? ' (line ' + e6.line + ')' : '')); }
    }
    function finish() {
        // ---------- flash + vignette
        var fl = comp.layers.addSolid(jzHex(jzLum(schemes[0].bg) < 0.5 ? schemes[0].fg : '#ffffff'), 'JZ Flash', W, H, 1, D);
        jzSetExpr(jzXf(fl, 'ADBE Opacity'), 'var ev=' + jzEventsArr(plan, 'flash') + ';var o=0;for(var i=0;i<ev.length;i++){var dt=time-ev[i][0];if(dt>=0&&dt<ev[i][2])o=Math.max(o,Math.pow(1-dt/ev[i][2],1.5)*92);}o');
        var vg = KEY ? null : comp.layers.addSolid([0, 0, 0], 'JZ Vignette', W, H, 1, D);
        if (vg) try {
            var m = vg.property('ADBE Mask Parade').addProperty('ADBE Mask Atom');
            m.property('ADBE Mask Shape').setValue(jzCircleShape(W / 2, H / 2, Math.max(W, H) * 0.62));
            m.inverted = true; m.property('ADBE Mask Feather').setValue([H * 0.5, H * 0.5]);
            jzXf(vg, 'ADBE Scale').setValue([100, 100 * H / W * 1.6]);
        } catch (e7) { jzWarn('vignette: ' + e7.toString()); }
        if (vg) jzXf(vg, 'ADBE Opacity').setValue(32 * FXV.texture);
        if (KEY) {
            var km = comp.layers.addSolid([1, 1, 1], 'JZ Key Mono', W, H, 1, D); km.adjustmentLayer = true;
            jzEffect(km, 'ADBE Tint', 'JZ Key Mono');
            if (KEY === 'green') { var kg = comp.layers.addSolid([0, 1, 0], 'JZ Key Green', W, H, 1, D); kg.blendingMode = BlendingMode.SCREEN; }
        }

        // no hidden leftovers anywhere in what was built (track mattes stay: After Effects keeps a matte's own video off)
        try { jzTidyTree(comp); } catch (et) { jzWarn('tidy: ' + et.toString()); }

        // audio layer (optional)
        if (opt.audioItem) { try { var au = comp.layers.add(opt.audioItem); au.startTime = opt.audioStart || 0; au.moveToEnd(); } catch (e8) { jzWarn('audio: ' + e8.toString()); } }
        comp.openInViewer();
    }
    var job = { comp: comp, total: cuts.length, done: 0, events: evs.length, eventsDone: 0, phase: 'cuts', cancelled: false, finished: false };
    job.step = function (ms) {
        var t0 = new Date().getTime();
        function more() { return ms == null || new Date().getTime() - t0 < ms; }
        if (job.phase === 'cuts') {
            while (job.done < cuts.length && !job.cancelled) { buildCut(job.done); job.done++; if (!more()) return job; }
            job.phase = 'trans';
        }
        if (job.phase === 'trans') { finishTrans(); job.phase = 'events'; if (!more()) return job; }
        if (job.phase === 'events') {
            while (job.eventsDone < evs.length && !job.cancelled) { buildEvent(job.eventsDone); job.eventsDone++; if (!more()) return job; }
            finish(); job.phase = 'done'; job.finished = true;
        }
        return job;
    };
    return job;
}
function jzCutAtTime(cuts, t) { for (var i = cuts.length - 1; i >= 0; i--) if (t >= cuts[i].start - 1e-6 && t < cuts[i].end) return cuts[i]; return null; }
// scheme background: radial "lift" like the browser (Gradient Ramp on the solid)
function jzBgLift(L, sc, W, H) {
    var lift = jzLum(sc.bg) < 0.5 ? 0.045 : 0.1, b = jzHex(sc.bg);
    var c1 = [Math.min(1, b[0] + lift), Math.min(1, b[1] + lift), Math.min(1, b[2] + lift)];
    var ramp = jzEffect(L, 'ADBE Ramp', 'JZ BG Lift');
    jzEP(ramp, 1, [W / 2, H * 0.45]); jzEP(ramp, 2, c1); jzEP(ramp, 3, [W, H]); jzEP(ramp, 4, b); jzEP(ramp, 5, 2);
}

function jzHUD(comp, plan, sc, roles) {
    var W = comp.width, H = comp.height, m = Math.round(H * 0.045), L = H * 0.035, fs = Math.max(10, H * 0.016);
    var ctx = { comp: comp, W: W, H: H, sc: sc, roles: roles, cut: { dur: plan.duration, outDur: 0.2, inDur: 0.3, seed: 1 } };
    var S = jzShapeLayer(ctx, 'HUD frame', 0, 0), g = jzGrp(S);
    jzAddPath(g, [[m, m + L], [m, m], [m + L, m]], false); jzAddPath(g, [[W - m - L, m], [W - m, m], [W - m, m + L]], false);
    jzAddPath(g, [[m, H - m - L], [m, H - m], [m + L, H - m]], false); jzAddPath(g, [[W - m - L, H - m], [W - m, H - m], [W - m, H - m - L]], false);
    jzAddStroke(g, sc.sub, 1.4, 90);
    jzText(ctx, (plan.title || 'UNTITLED') + (plan.artist ? ' / ' + plan.artist : ''), { font: 'gothic_med', size: fs, color: sc.sub, x: m + L * 0.6, y: m + L * 0.9, align: 'left', track: 0.12 });
    var rec = jzText(ctx, 'REC', { font: 'mono', size: fs, color: sc.sub, x: W - m - L * 2.2, y: m + L * 0.9, align: 'left', track: 0.1 });
    var dot = jzShapeLayer(ctx, 'REC dot', W - m - L * 2.6, m + L * 0.9), gd = jzGrp(dot); jzAddEllipse(gd, fs * 0.64, fs * 0.64); jzAddFill(gd, sc.accent);
    jzSetExpr(jzXf(dot, 'ADBE Opacity'), 'Math.floor(time*6)%2===0?100:0');
    var tc = jzText(ctx, '00:00:00:00', { font: 'mono', size: fs, color: sc.sub, x: m + L * 0.6, y: H - m - L * 0.9, align: 'left', track: 0.1 });
    try { tc.property('ADBE Text Properties').property('ADBE Text Document').expression = 'timeToTimecode(time)'; } catch (e) {}
    var starts = []; for (var i = 0; i < (plan.lines || []).length; i++) starts.push(jzN(plan.lines[i].start));
    var lc = jzText(ctx, 'LYRIC 00/00', { font: 'mono', size: fs, color: sc.sub, x: W - m - L * 0.6, y: H - m - L * 0.9, align: 'right', track: 0.1 });
    try { lc.property('ADBE Text Properties').property('ADBE Text Document').expression = 'var ls=[' + starts.join(',') + '];var n=0;for(var i=0;i<ls.length;i++)if(time>=ls[i])n=i+1;"LYRIC "+("0"+n).slice(-2)+"/"+("0"+ls.length).slice(-2)'; } catch (e2) {}
    var bar = jzShapeLayer(ctx, 'HUD progress', 0, 0), gb = jzGrp(bar);
    jzAddPath(gb, [[W * 0.3, H - m - L * 0.9], [W * 0.7, H - m - L * 0.9]], false); jzAddStroke(gb, sc.accent, 2);
    jzAddTrimPaths(gb, 'linear(time,0,thisComp.duration,0,100)');
}

// ---------------------------------------------------------------- tidy: remove hidden / main-pass-only leftovers
// index of the layer used as track matte by layer i (0 = none): AE 23+ trackMatteLayer, else the legacy "layer above"
function jzMatteIndex(C, i) {
    var L = C.layer(i);
    try { if (L.trackMatteLayer) return L.trackMatteLayer.index; } catch (e) {}
    try { if (L.trackMatteType && L.trackMatteType !== TrackMatteType.NO_TRACK_MATTE && i > 1) return i - 1; } catch (e2) {}
    return 0;
}
// ghost = the ghost copy of a content comp: also drop the jzNoGhost() layers. A dropped layer that another kept layer still needs
// stays: as a track matte it keeps its video off (After Effects' own rule for mattes); as a parent it stays on at 0 % opacity.
function jzTidyComp(C, ghost) {
    var n = C.numLayers, i, cand = [], keep = [], par = [], mat = [], changed = true, L;
    for (i = 1; i <= n; i++) {
        L = C.layer(i); par[i] = 0; mat[i] = jzMatteIndex(C, i);
        try { if (L.parent) par[i] = L.parent.index; } catch (e) {}
        var hidden = false; try { hidden = !L.enabled; } catch (e1) {}
        cand[i] = hidden || (ghost && jzIsNoGhost(L));
    }
    for (i = 1; i <= n; i++) if (cand[i] && mat[i]) cand[mat[i]] = true;      // a matte goes with the layer it cuts
    for (i = 1; i <= n; i++) keep[i] = !cand[i];
    while (changed) {
        changed = false;
        for (i = 1; i <= n; i++) if (keep[i]) {
            if (par[i] && !keep[par[i]]) { keep[par[i]] = true; changed = true; }
            if (mat[i] && !keep[mat[i]]) { keep[mat[i]] = true; changed = true; }
        }
    }
    var isMatte = [];
    for (i = 1; i <= n; i++) if (keep[i] && mat[i]) isMatte[mat[i]] = true;
    for (i = n; i >= 1; i--) {
        L = C.layer(i);
        if (!keep[i]) { try { L.remove(); } catch (e2) { jzWarn('tidy remove: ' + e2.toString()); } continue; }
        if (!cand[i] || isMatte[i]) continue;
        // kept only as a parent: visible switch on, nothing drawn
        try { L.enabled = true; var op = L.property('ADBE Transform Group').property('ADBE Opacity'); try { op.expression = ''; } catch (e3) {} op.setValue(0); } catch (e4) { jzWarn('tidy parent: ' + e4.toString()); }
    }
}
function jzTidyTree(comp) {
    var seen = {}, todo = [comp], C, i, L;
    while (todo.length) {
        C = todo.pop();
        if (!C || seen[C.id]) continue;
        seen[C.id] = true;
        jzTidyComp(C, / ghost$/.test(C.name));
        for (i = 1; i <= C.numLayers; i++) { L = C.layer(i); try { if (L.source && L.source instanceof CompItem) todo.push(L.source); } catch (e) {} }
    }
}

// ================================================================ diagnostics (real After Effects)
// Walks a built comp tree, evaluates every expression at a few times and collects AE's own error messages
// (Property.expressionError), plus the substitutions and warnings of the build. Used by the panels'
// "\u8A3A\u65AD\u30EC\u30DD\u30FC\u30C8" button; the report is plain text so it can be sent as-is.
var JZ_PANEL_VERSION = '0.9.0';
// parts this panel can build, counted like the browser (its expression groups' orders)
function jzPartsCount() {
    var n = 0, g, i, o = JZ_DATA.orders || {};
    for (g in o) if (o.hasOwnProperty(g) && JZ_REG[g]) for (i = 0; i < o[g].length; i++) if (JZ_REG[g][o[g][i]]) n++;
    return n;
}
function jzDiagnose(comp, plan, maxSecs) {
    var t0 = new Date().getTime(), limit = (maxSecs || 120) * 1000, seen = {}, errs = [], nExpr = 0, nErr = 0, nComps = 0, nLayers = 0, stopped = false;
    function walkProps(G, L, path, depth) {
        if (stopped || depth > 12) return;
        var n = 0; try { n = G.numProperties || 0; } catch (e) { n = 0; }
        for (var i = 1; i <= n; i++) {
            if (new Date().getTime() - t0 > limit) { stopped = true; return; }
            var P = null; try { P = G.property(i); } catch (e1) { continue; }
            if (!P) continue;
            var nm = path + ' > ' + P.name, sub = 0;
            try { sub = P.numProperties || 0; } catch (e2) { sub = 0; }
            if (sub > 0) { walkProps(P, L, nm, depth + 1); continue; }
            var ex = ''; try { ex = P.canSetExpression && P.expressionEnabled !== false ? P.expression : ''; } catch (e3) { ex = ''; }
            if (!ex) continue;
            nExpr++;
            var msg = '', a = Math.max(L.inPoint, 0), b = Math.max(a, L.outPoint - 0.05), ts = [a + 0.02, (a + b) / 2, b], j;
            for (j = 0; j < ts.length && !msg; j++) {
                try { P.valueAtTime(ts[j], false); } catch (e4) { msg = 'eval: ' + e4.toString(); }
                try { if (!msg && P.expressionError) msg = String(P.expressionError); } catch (e5) {}
            }
            if (msg) { nErr++; if (errs.length < 300) errs.push(nm + '\n      ' + msg.split('\n')[0]); }
        }
    }
    function walkComp(C, path) {
        if (!C || seen[C.id]) return;
        seen[C.id] = true; nComps++;
        for (var i = 1; i <= C.numLayers; i++) {
            if (stopped) return;
            var L = C.layer(i); nLayers++;
            walkProps(L, L, path + ' / ' + L.name, 0);
            try { if (L.source && L.source instanceof CompItem) walkComp(L.source, L.source.name); } catch (e) {}
        }
    }
    walkComp(comp, comp ? comp.name : '?');
    var out = [];
    out.push('JIZURA \u8A3A\u65AD\u30EC\u30DD\u30FC\u30C8');
    out.push('panel ' + JZ_PANEL_VERSION + ' / parts ' + jzPartsCount() + ' / After Effects ' + app.version + ' / ' + $.os);
    try { out.push('expression engine: ' + app.project.expressionEngine); } catch (e6) {}
    out.push('comp: ' + (comp ? comp.name + ' ' + comp.width + 'x' + comp.height + ' ' + comp.frameRate + 'fps' : '(none)'));
    out.push('checked: comps ' + nComps + ', layers ' + nLayers + ', expressions ' + nExpr + (stopped ? ' (time limit \u2014 partial)' : '') + ', ' + ((new Date().getTime() - t0) / 1000).toFixed(1) + 's');
    out.push('substitutions: ' + JZ_FALLBACKS + (JZ_FALLBACK_KEYS.length ? '  ' + JZ_FALLBACK_KEYS.join(', ') : ''));
    var mf = jzMissingFonts(); if (mf.length) out.push('missing fonts: ' + mf.join(', '));
    out.push('build notes: ' + JZLOG.length);
    for (var k = 0; k < JZLOG.length && k < 60; k++) out.push('   ' + JZLOG[k]);
    out.push('expression errors: ' + nErr);
    for (k = 0; k < errs.length; k++) out.push('   ' + errs[k]);
    if (plan && plan.cuts) {
        out.push('cuts:');
        for (k = 0; k < plan.cuts.length; k++) {
            var c = plan.cuts[k], dl = [], d;
            for (d = 0; c.decor && d < c.decor.length; d++) dl.push(c.decor[d].id);
            out.push('   ' + (k + 1) + ' ' + jzN(c.start) + '-' + jzN(c.end) + ' layout ' + c.layout + ' / enter ' + c.enter + ' / hold ' + c.hold + ' / exit ' + c.exit +
                ' / treat ' + (c.treat || '-') + ' / bg ' + (c.bg || '-') + ' / cam ' + (c.cam || '-') + ' / trans ' + (c.trans || '-') + ' / decor ' + dl.join(','));
        }
        var ev = [];
        for (k = 0; plan.events && k < plan.events.length; k++) ev.push(plan.events[k].type + '@' + jzN(plan.events[k].t));
        out.push('events: ' + ev.join(' '));
    }
    return { text: out.join('\n'), errors: nErr, expressions: nExpr, partial: stopped };
}
// save next to the project (or on the desktop); returns the file path or null
function jzSaveReport(text) {
    var dirs = [], f, i;
    try { if (app.project.file) dirs.push(app.project.file.parent); } catch (e) {}
    try { dirs.push(Folder.desktop); } catch (e1) {}
    try { dirs.push(Folder.temp); } catch (e2) {}
    for (i = 0; i < dirs.length; i++) {
        try {
            f = new File(dirs[i].fsName + '/JIZURA_report.txt'); f.encoding = 'UTF-8'; f.lineFeed = 'Windows';
            if (f.open('w')) { f.write(text); f.close(); return f.fsName; }
        } catch (e3) {}
    }
    return null;
}

// ================================================================ ScriptUI panel
var JZ_SECTION = 'JIZURA';
function jzGet(key, def) { try { if (app.settings.haveSetting(JZ_SECTION, key)) return decodeURIComponent(app.settings.getSetting(JZ_SECTION, key)); } catch (e) {} return def; }
function jzPut(key, v) { try { app.settings.saveSetting(JZ_SECTION, key, encodeURIComponent(String(v))); } catch (e) {} }

var JZ_SAMPLE = 'I remember/the color of dawn\nA voice faded into the distance\nCan we still make it in time?\n*Transparent* is not how this ends!';

function jzUI(thisObj) {
    var win = (thisObj instanceof Panel) ? thisObj : new Window('palette', 'JIZURA', undefined, { resizeable: true });
    win.orientation = 'column'; win.alignChildren = ['fill', 'top']; win.spacing = 6; win.margins = 10;
    var head = win.add('group'); head.alignChildren = ['left', 'center'];
    var ttl = head.add('statictext', undefined, 'JIZURA  lyric motion  v' + JZ_PANEL_VERSION + ' (' + jzPartsCount() + ' techniques)'); try { ttl.graphics.font = ScriptUI.newFont(ttl.graphics.font.name, 'BOLD', 14); } catch (e) {}

    var tp = win.add('tabbedpanel'); tp.alignChildren = ['fill', 'top'];
    // ---------------- tab 1: from lyrics
    var t1 = tp.add('tab', undefined, 'From lyrics'); t1.orientation = 'column'; t1.alignChildren = ['fill', 'top']; t1.spacing = 6; t1.margins = 8;
    t1.add('statictext', undefined, 'One phrase per line. / = cut, *word* = emphasis, final ! = impact');
    var lyr = t1.add('edittext', undefined, jzGet('lyrics', JZ_SAMPLE), { multiline: true, wantReturn: true, scrolling: true }); lyr.preferredSize = [440, 150];
    var gT = t1.add('group'); gT.add('statictext', undefined, 'Song title'); var eTitle = gT.add('edittext', undefined, jzGet('title', '')); eTitle.preferredSize.width = 120;
    gT.add('statictext', undefined, 'Artist'); var eArtist = gT.add('edittext', undefined, jzGet('artist', '')); eArtist.preferredSize.width = 100;
    var bOmk = t1.add('button', undefined, 'Create a variation');
    bOmk.helpTip = 'Randomize style, mood, effects, colors and seed, then create a composition.';
    try { bOmk.preferredSize.height = 34; bOmk.graphics.font = ScriptUI.newFont(bOmk.graphics.font.name, 'BOLD', 13); } catch (e) {}
    var gSw = t1.add('group'); gSw.orientation = 'column'; gSw.alignChildren = ['left', 'top']; gSw.spacing = 2;
    var cExtra = gSw.add('checkbox', undefined, 'Include new effects'); cExtra.value = jzGet('extra', '0') === '1';
    cExtra.helpTip = 'Include techniques, styles and fonts added after the first release in random picks.';
    var cWa = gSw.add('checkbox', undefined, 'Include Japanese motifs'); cWa.value = jzGet('wa', '1') === '1';
    cWa.helpTip = 'Include Japanese motifs such as lanterns, shoji, fans and cherry blossoms in automatic picks.';
    var cTypo = gSw.add('checkbox', undefined, 'Use typographic parts'); cTypo.value = jzGet('typo', '1') === '1';
    cTypo.helpTip = 'About 50 parts that work with lines, numbers and type alone';
    var cKin = gSw.add('checkbox', undefined, 'Use kinetic parts'); cKin.value = jzGet('kinetic', '1') === '1';
    cKin.helpTip = 'About 50 motion-first parts: words move, bounce and stack one by one';
    var cHor = gSw.add('checkbox', undefined, 'Include horror effects'); cHor.value = jzGet('horror', '0') === '1';
    cHor.helpTip = 'About 50 eerie parts and 3 styles. When on, Randomize adds a Horror mood (horror parts are used only in that mood)';
    var gLang = gSw.add('group'); gLang.spacing = 4; gLang.add('statictext', undefined, 'Lyrics language');
    var JZ_LANG_KEYS = ['auto', 'ja', 'zh-Hant', 'zh-Hans', 'ko', 'en'];
    var ddLang = gLang.add('dropdownlist', undefined, ['Auto-detect', '\u65E5\u672C\u8A9E', '\u7E41\u9AD4\u4E2D\u6587', '\u7B80\u4F53\u4E2D\u6587', '\uD55C\uAD6D\uC5B4', 'English']); ddLang.selection = parseInt(jzGet('lang', '0'), 10) || 0;
    ddLang.helpTip = 'Chinese (Traditional / Simplified) and Korean lyrics are set in fonts that have those characters, close in feel to each style. Auto-detect looks at kana, Hangul and the characters that differ between Traditional and Simplified Chinese.';
    function switches() { return { extra: cExtra.value, wa: cWa.value, typo: cTypo.value, kinetic: cKin.value, horror: cHor.value, lang: JZ_LANG_KEYS[ddLang.selection ? ddLang.selection.index : 0] }; }
    var gS = t1.add('group'); gS.add('statictext', undefined, 'Style');
    var styleNames = [], i;
    for (i = 0; i < JZ_DATA.styleOrder.length; i++) { var stI = JZ_DATA.styles[JZ_DATA.styleOrder[i]]; styleNames.push(stI.name + (stI.extra || stI.wa ? '  \u3014' + (stI.extra ? 'New' : '') + (stI.extra && stI.wa ? ' \u00B7 ' : '') + (stI.wa ? 'JP' : '') + '\u3015' : '') + (stI.set === 'horror' ? '  [H]' : '')); }
    var ddStyle = gS.add('dropdownlist', undefined, styleNames); ddStyle.selection = parseInt(jzGet('style', '0'), 10) || 0;
    var gC = t1.add('group'); gC.add('statictext', undefined, 'Size');
    var sizes = ['Same as active comp', '1920\u00D71080', '1080\u00D71920', '1080\u00D71080', '3840\u00D72160', '1280\u00D7720', '1440\u00D71080 (4:3)', '1080\u00D71440 (3:4)'];
    var ddSize = gC.add('dropdownlist', undefined, sizes); ddSize.selection = parseInt(jzGet('size', '1'), 10) || 0;
    gC.add('statictext', undefined, 'fps'); var ddFps = gC.add('dropdownlist', undefined, ['24', '30', '60']); ddFps.selection = parseInt(jzGet('fps', '0'), 10) || 0;
    var gK = t1.add('group'); gK.add('statictext', undefined, 'Background');
    var ddKey = gK.add('dropdownlist', undefined, ['Style background', 'Green screen (compositing)', 'Black background (compositing)']); ddKey.selection = parseInt(jzGet('key', '0'), 10) || 0;
    ddKey.helpTip = 'Solid green or black background behind white text and effects. Key out green or use Screen blending for black.';
    var cCenter = t1.add('checkbox', undefined, 'Keep the centre free (for a character: left / right on wide, top / bottom on tall frames)'); cCenter.value = jzGet('center', '0') === '1';
    cCenter.helpTip = 'For a character or other footage in the middle: each cut is laid out in a side band (left / right on wide frames, top / bottom on tall ones, alternating line by line). Backgrounds and screen effects still cover the whole frame.';
    var gCD = t1.add('group'); gCD.add('statictext', undefined, '  On tall frames'); var ddCDir = gCD.add('dropdownlist', undefined, ['Top / bottom', 'Left / right']); ddCDir.selection = parseInt(jzGet('centerDir', '0'), 10) || 0;
    var cLight = t1.add('checkbox', undefined, 'Lightweight (faster playback in AE)'); cLight.value = jzGet('light', '0') === '1';
    cLight.helpTip = 'Leaves out the colour-shift copies, paper texture, bloom, grain and some screen effects so the composition plays back faster in After Effects (recommended for long songs)';

    var pT = t1.add('panel', undefined, 'Timing'); pT.alignChildren = ['left', 'top']; pT.margins = 10;
    var rAuto = pT.add('radiobutton', undefined, 'Auto (text length and BPM) / LRC timestamps');
    var rLayer = pT.add('radiobutton', undefined, 'Use selected layer markers for line starts');
    var rComp = pT.add('radiobutton', undefined, 'Use composition markers for line starts');
    var tm = jzGet('timing', 'auto'); rAuto.value = tm === 'auto'; rLayer.value = tm === 'layer'; rComp.value = tm === 'comp';
    var gB = pT.add('group'); gB.add('statictext', undefined, 'BPM'); var eBpm = gB.add('edittext', undefined, jzGet('bpm', '')); eBpm.preferredSize.width = 50;
    gB.add('statictext', undefined, 'Line duration'); var eScale = gB.add('edittext', undefined, jzGet('lineScale', '1')); eScale.preferredSize.width = 40;
    var cAudio = pT.add('checkbox', undefined, 'Include selected audio layer in new composition'); cAudio.value = jzGet('audio', '1') === '1';

    var pF = t1.add('panel', undefined, 'Effects'); pF.alignChildren = ['fill', 'top']; pF.margins = 10;
    var gM = pF.add('group'); gM.add('statictext', undefined, 'Mood').preferredSize.width = 120;
    var moodNames = ['Standard (all techniques)'];
    for (i = 0; i < JZ_DATA.moodOrder.length; i++) moodNames.push(JZ_DATA.moods[JZ_DATA.moodOrder[i]].name);
    var ddMood = gM.add('dropdownlist', undefined, moodNames); ddMood.selection = parseInt(jzGet('mood', '0'), 10) || 0;
    ddMood.helpTip = 'Mood influences the selection of layouts, entrances and exits (reproducible by seed).';
    function slider(parent, label, key, def) {
        var g = parent.add('group'); g.add('statictext', undefined, label).preferredSize.width = 120;
        var v = parseFloat(jzGet(key, String(def)));
        var s = g.add('slider', undefined, v, 0, 100); s.preferredSize.width = 170;
        var t = g.add('statictext', undefined, String(Math.round(v))); t.preferredSize.width = 30;
        s.onChanging = function () { t.text = String(Math.round(s.value)); };
        s.key = key; s.lbl = t; return s;
    }
    var sMotion = slider(pF, 'Motion strength', 'motion', 70), sGlitch = slider(pF, 'Glitch', 'glitch', 55), sChroma = slider(pF, 'Color offset', 'chroma', 70);
    var sDecor = slider(pF, 'Decoration amount', 'decor', 50), sDensity = slider(pF, 'Cut density', 'density', 55), sTexture = slider(pF, 'Texture', 'texture', 60), sBg = slider(pF, 'Background changes', 'bgSwitch', 35);
    var gO = pF.add('group');
    var cTwos = gO.add('checkbox', undefined, 'On twos'); cTwos.value = jzGet('twos', '1') === '1';
    var cFlash = gO.add('checkbox', undefined, 'Flash'); cFlash.value = jzGet('flash', '1') === '1';
    gO.add('statictext', undefined, 'HUD'); var ddHud = gO.add('dropdownlist', undefined, ['Auto by style', 'Show', 'Hide']); ddHud.selection = parseInt(jzGet('hud', '0'), 10) || 0;
    var gSeed = t1.add('group'); gSeed.add('statictext', undefined, 'Seed');
    var eSeed = gSeed.add('edittext', undefined, jzGet('seed', '20260922')); eSeed.preferredSize.width = 110;
    var bShuffle = gSeed.add('button', undefined, 'Shuffle');
    bShuffle.onClick = function () { eSeed.text = String(Math.floor(Math.random() * 999999999)); };

    var pP = t1.add('panel', undefined, 'Accent and offset colors'); pP.alignChildren = ['fill', 'top']; pP.margins = 10; pP.spacing = 6;
    var gP1 = pP.add('group');
    var cPal = gP1.add('checkbox', undefined, 'Override style colors'); cPal.value = jzGet('palOn', '0') === '1';
    var bPal = gP1.add('button', undefined, 'Random palette');
    bPal.helpTip = 'Randomize accent and offset colors with automatic brightness adjustment.';
    var gP2 = pP.add('group'); gP2.spacing = 10;
    function colorField(label, key) {
        var g = gP2.add('group'); g.orientation = 'column'; g.alignChildren = ['left', 'top']; g.spacing = 2;
        g.add('statictext', undefined, label);
        var row = g.add('group'); row.spacing = 4;
        var sw = row.add('group'); sw.preferredSize = [16, 16];
        var e = row.add('edittext', undefined, jzGet('pal_' + key, '')); e.characters = 8;
        var f = { e: e, sw: sw, key: key };
        e.onChange = function () { var h = jzCleanHex(e.text); if (h) { e.text = h; cPal.value = true; } paint(f); };
        return f;
    }
    function paint(f) {
        var h = jzCleanHex(f.e.text); if (!h) return;
        try { var c = jzHex(h); f.sw.graphics.backgroundColor = f.sw.graphics.newBrush(f.sw.graphics.BrushType.SOLID_COLOR, [c[0], c[1], c[2], 1]); } catch (e) {}
    }
    var fAcc = colorField('Accent', 'accent'), fGA = colorField('Offset A', 'ghostA'), fGB = colorField('Offset B', 'ghostB'), palFields = [fAcc, fGA, fGB];
    function curStyle() { return JZ_DATA.styles[JZ_DATA.styleOrder[ddStyle.selection ? ddStyle.selection.index : 0]] || JZ_DATA.styles.noir; }
    function setPal(p) { fAcc.e.text = p.accent; fGA.e.text = p.ghostA; fGB.e.text = p.ghostB; for (var q = 0; q < 3; q++) paint(palFields[q]); }
    function showStylePal() { var sc = curStyle().schemes[0]; setPal({ accent: sc.accent, ghostA: sc.ghostA, ghostB: sc.ghostB }); }
    if (!cPal.value || !jzCleanHex(fAcc.e.text)) showStylePal(); else setPal({ accent: fAcc.e.text, ghostA: fGA.e.text, ghostB: fGB.e.text });
    ddStyle.onChange = function () { if (!cPal.value) showStylePal(); };
    bPal.onClick = function () { setPal(jzRandomPalette(curStyle().schemes[0].bg)); cPal.value = true; status.text = 'Palette changed. Create a composition to apply it.'; };
    cPal.onClick = function () { if (!cPal.value) showStylePal(); };

    var gGo = t1.add('group'); gGo.alignChildren = ['fill', 'center'];
    var bBuild = gGo.add('button', undefined, 'Create composition'); bBuild.alignment = ['fill', 'center'];

    // ---------------- tab 2: from JSON
    var t2 = tp.add('tab', undefined, 'From JSON'); t2.orientation = 'column'; t2.alignChildren = ['fill', 'top']; t2.margins = 8;
    t2.add('statictext', undefined, 'Import a JSON file made with Export for AE in the browser app.', undefined, { multiline: true });
    t2.add('statictext', undefined, 'Create an editable composition with the same timing, layouts and effects.', undefined, { multiline: true });
    var cAudio2 = t2.add('checkbox', undefined, 'Include selected audio layer'); cAudio2.value = true;
    var bJson = t2.add('button', undefined, 'Open JSON and build\u2026');
    t2.add('statictext', undefined, 'If the result looks wrong, save a diagnostic report (JIZURA_report.txt).', undefined, { multiline: true });
    var bDiag = t2.add('button', undefined, 'Save diagnostics for last composition');

    // ---------------- tab 3: fonts
    var t3 = tp.add('tab', undefined, 'Fonts'); t3.orientation = 'column'; t3.alignChildren = ['fill', 'top']; t3.margins = 8;
    t3.add('statictext', undefined, 'Specify PostScript font names (fallbacks when a font is unavailable).', undefined, { multiline: true });
    function fontRow(label, key, def) { var g = t3.add('group'); g.add('statictext', undefined, label).preferredSize.width = 95; var e = g.add('edittext', undefined, jzGet('font_' + key, def)); e.preferredSize.width = 230; return e; }
    var fDisplay = fontRow('Display', 'display', 'YuGothic-Bold'), fSerif = fontRow('Serif', 'serif', 'YuMincho-Demibold'), fBody = fontRow('Small text', 'body', 'YuGothic-Medium'), fMono = fontRow('Monospace', 'mono', 'Consolas');
    var cForce = t3.add('checkbox', undefined, 'Always use these four fonts (disable automatic selection)'); cForce.value = jzGet('forceFonts', '0') === '1';
    var bPick = t3.add('button', undefined, 'Use selected text layer font for display text');
    bPick.onClick = function () {
        var c = app.project.activeItem;
        if (!(c instanceof CompItem) || !c.selectedLayers.length) { alert('Select a text layer'); return; }
        try { fDisplay.text = c.selectedLayers[0].property('ADBE Text Properties').property('ADBE Text Document').value.font; } catch (e) { alert('This is not a text layer'); }
    };
    t3.add('statictext', undefined, 'Installed Google Fonts are used automatically in After Effects 2024 or later.', undefined, { multiline: true });

    var status = win.add('statictext', undefined, 'Ready', { truncate: 'end' });
    var gRun = win.add('group'); gRun.alignChildren = ['fill', 'center'];
    var pbar = gRun.add('progressbar', undefined, 0, 100); pbar.alignment = ['fill', 'center']; pbar.preferredSize.height = 6;
    var bStop = gRun.add('button', undefined, 'Stop'); bStop.enabled = false; bStop.helpTip = 'Stop building (the composition is finished with the cuts built so far)';
    tp.selection = t1;

    // ---- build in short steps: After Effects gets control back between steps (app.scheduleTask), so long songs
    //      don't freeze it into "not responding"; the bar shows progress and \u4E2D\u6B62 finishes with the cuts built so far
    var RUN = null;
    function setRunning(on) { bBuild.enabled = bOmk.enabled = bJson.enabled = !on; bStop.enabled = on; if (!on) pbar.value = 0; }
    function runBuild(plan, bo, undoName, done) {
        if (RUN) { alert('JIZURA: a build is running. Wait for it to finish or press Stop.'); return; }
        var job = null;
        app.beginUndoGroup(undoName);
        try { job = jzBuildStart(plan, bo); }
        catch (e) { alert('Build error: ' + e.toString() + (e.line ? ' (line ' + e.line + ')' : '')); }
        finally { app.endUndoGroup(); }
        if (!job) { done(null); return; }
        RUN = { job: job, done: done, name: undoName, t0: new Date().getTime() };
        setRunning(true);
        $.global.__JZ_TICK = tick;
        if (!schedule()) { while (RUN) tick(true); }          // no scheduleTask: plain loop (the old behaviour)
    }
    function schedule() { try { app.scheduleTask('if ($.global.__JZ_TICK) $.global.__JZ_TICK();', 20, false); return true; } catch (e) { return false; } }
    function tick(sync) {
        var R = RUN; if (!R) return;
        var err = null, job = R.job;
        app.beginUndoGroup(R.name);
        try { job.step(sync ? 1e9 : 900); }
        catch (e) { err = e.toString() + (e.line ? ' (line ' + e.line + ')' : ''); }
        finally { app.endUndoGroup(); }
        if (err || job.finished) {
            RUN = null; $.global.__JZ_TICK = null; setRunning(false);
            if (err) alert('Build error: ' + err);
            R.done(err ? null : job.comp, job);
            return;
        }
        var k = job.phase === 'cuts' ? job.done / Math.max(1, job.total) * 0.9 : 0.9 + 0.1 * job.eventsDone / Math.max(1, job.events);
        pbar.value = Math.round(k * 100);
        status.text = 'Building\u2026 ' + Math.round(k * 100) + '% (' + (job.phase === 'cuts' ? job.done + ' / ' + job.total + ' cuts' : 'adding effects') + ')';
        try { if (win.update) win.update(); } catch (eu) {}
        if (!sync && !schedule()) { while (RUN) tick(true); }
    }
    bStop.onClick = function () { if (RUN) { RUN.job.cancelled = true; status.text = 'Stopping\u2026 (finishing with the cuts built so far)'; } };

    function roles() {
        jzPut('font_display', fDisplay.text); jzPut('font_serif', fSerif.text); jzPut('font_body', fBody.text); jzPut('font_mono', fMono.text); jzPut('forceFonts', cForce.value ? '1' : '0');
        return { display: fDisplay.text, serif: fSerif.text, body: fBody.text, mono: fMono.text, __force: cForce.value };
    }
    function audioSel(on) {
        var c = app.project.activeItem;
        if (!on || !(c instanceof CompItem) || !c.selectedLayers.length) return null;
        var L = c.selectedLayers[0];
        try { if (L.hasAudio && L.source) return { item: L.source, start: L.startTime }; } catch (e) {}
        return null;
    }
    var fontNoted = false, lastComp = null, lastPlan = null;
    function report(comp, t0, label) {
        var s = (label ? label + '  ' : '') + (comp ? comp.name : '') + ' \u2014 ' + ((new Date().getTime() - t0) / 1000).toFixed(1) + 's';
        if (JZLOG.length) { s += ' / Warnings: ' + JZLOG.length + ''; alert('JIZURA: Composition created with some warnings:\n\n' + JZLOG.slice(0, 14).join('\n')); }
        var mf = comp ? jzMissingFonts() : [];
        if (mf.length) {
            s += ' / Font substitutions: ' + mf.length; status.helpTip = 'Fonts missing on this computer: ' + mf.join(', ');
            if (!fontNoted) {
                fontNoted = true;
                alert('JIZURA: These fonts are unavailable, so similar fonts were used.\n\n' + mf.join('\n') +
                    '\n\nInstall them from Google Fonts, restart After Effects and rebuild for the closest browser match.');
            }
        }
        if (comp && JZ_FONT_NOAPI && !fontNoted) {
            fontNoted = true;
            alert('JIZURA: This version of After Effects cannot check installed fonts, so the font tab fallbacks were used.\n\nFor matching type, install the Google Fonts used in the browser and select them in the font tab, or use AE 2024 or later.');
        }
        status.text = s;
    }

    function moodKey() { var ix = ddMood.selection ? ddMood.selection.index : 0; return ix > 0 ? JZ_DATA.moodOrder[ix - 1] : null; }
    function doBuild(label) {
        var t0 = new Date().getTime();
        jzPut('mood', ddMood.selection ? ddMood.selection.index : 0); jzPut('palOn', cPal.value ? '1' : '0');
        for (var pf = 0; pf < palFields.length; pf++) jzPut('pal_' + palFields[pf].key, palFields[pf].e.text);
        jzPut('lyrics', lyr.text); jzPut('title', eTitle.text); jzPut('artist', eArtist.text); jzPut('style', ddStyle.selection.index);
        jzPut('size', ddSize.selection.index); jzPut('fps', ddFps.selection.index); jzPut('timing', rLayer.value ? 'layer' : rComp.value ? 'comp' : 'auto');
        jzPut('bpm', eBpm.text); jzPut('lineScale', eScale.text); jzPut('audio', cAudio.value ? '1' : '0'); jzPut('seed', eSeed.text);
        jzPut('twos', cTwos.value ? '1' : '0'); jzPut('flash', cFlash.value ? '1' : '0'); jzPut('hud', ddHud.selection.index);
        jzPut('extra', cExtra.value ? '1' : '0'); jzPut('wa', cWa.value ? '1' : '0'); jzPut('typo', cTypo.value ? '1' : '0'); jzPut('kinetic', cKin.value ? '1' : '0'); jzPut('horror', cHor.value ? '1' : '0'); jzPut('key', ddKey.selection.index); jzPut('center', cCenter.value ? '1' : '0'); jzPut('centerDir', ddCDir.selection ? ddCDir.selection.index : 0); jzPut('light', cLight.value ? '1' : '0'); jzPut('lang', ddLang.selection ? ddLang.selection.index : 0);
        var sl = [sMotion, sGlitch, sChroma, sDecor, sDensity, sTexture, sBg]; for (var k = 0; k < sl.length; k++) jzPut(sl[k].key, sl[k].value);
        var active = app.project.activeItem, W = 1920, H = 1080, fps = [24, 30, 60][ddFps.selection.index], dur = null;
        var sz = ddSize.selection.index;
        if (sz === 0) { if (active instanceof CompItem) { W = active.width; H = active.height; fps = active.frameRate; } }
        else { var wh = sizes[sz].split('\u00D7'); W = parseInt(wh[0], 10); H = parseInt(wh[1], 10); }
        var starts = null, mk, j;
        if (rLayer.value || rComp.value) {
            if (!(active instanceof CompItem)) { alert('Open a composition to use markers'); return; }
            try {
                if (rLayer.value) { if (!active.selectedLayers.length) { alert('Select a layer with markers'); return; } mk = active.selectedLayers[0].property('ADBE Marker'); }
                else mk = active.markerProperty;
                starts = []; for (j = 1; j <= mk.numKeys; j++) starts.push(mk.keyTime(j));
                if (!starts.length) { alert('No markers found'); return; }
                dur = active.duration;
            } catch (e) { alert('Could not read markers: ' + e.toString()); return; }
        }
        var au = audioSel(cAudio.value); if (au && !dur) dur = null;
        var sw = switches(), en = jzMoodEnabled(moodKey(), parseInt(eSeed.text, 10) || 1, sw);
        var o = {
            lyrics: lyr.text, title: eTitle.text, artist: eArtist.text, style: JZ_DATA.styleOrder[ddStyle.selection.index], seed: parseInt(eSeed.text, 10) || 1,
            fx: { motion: sMotion.value / 100, glitch: sGlitch.value / 100, chroma: sChroma.value / 100, decor: sDecor.value / 100, density: sDensity.value / 100, texture: sTexture.value / 100, bgSwitch: sBg.value / 100, onTwos: cTwos.value, flash: cFlash.value, hud: false },
            width: W, height: H, fps: fps, bpm: parseFloat(eBpm.text) || 0, starts: starts, enabled: en, offset: 0.4, lineScale: parseFloat(eScale.text) || 1, duration: dur,
            extra: sw.extra, wa: sw.wa, typo: sw.typo, kinetic: sw.kinetic, horror: sw.horror, lang: sw.lang, centerFree: cCenter.value, centerDir: ddCDir.selection && ddCDir.selection.index === 1 ? 'lr' : 'tb'
        };
        var st = JZ_DATA.styles[o.style];
        o.fx.hud = ddHud.selection.index === 1 ? true : ddHud.selection.index === 2 ? false : !!st.hud;
        var plan;
        try { plan = jzMakePlan(o); } catch (e1) { alert('Could not plan composition: ' + e1.toString() + (e1.line ? ' (line ' + e1.line + ')' : '')); return; }
        if (!plan.cuts.length) { alert('Lyrics are empty'); return; }
        plan.hud = o.fx.hud;
        if (cPal.value) {
            var pal = { accent: jzCleanHex(fAcc.e.text), ghostA: jzCleanHex(fGA.e.text), ghostB: jzCleanHex(fGB.e.text) };
            if (!pal.accent && !pal.ghostA && !pal.ghostB) jzWarn('Invalid color. Style colors were kept (enter #RRGGBB).');
            else plan.style = jzStyleWithPalette(plan.style, pal);
        }
        var keyI = ddKey.selection ? ddKey.selection.index : 0;
        if (keyI > 0) { plan.keyBg = keyI === 1 ? 'green' : 'black'; plan.style = jzKeyStyle(plan.style); }
        status.text = 'Building\u2026 (' + plan.cuts.length + ' cuts)';
        runBuild(plan, { roles: roles(), audioItem: au ? au.item : null, audioStart: au ? au.start : 0, light: cLight.value }, 'JIZURA build', function (comp, job) {
            if (comp) { lastComp = comp; lastPlan = plan; }
            report(comp, t0, jobLabel(label, job));
        });
    }
    function jobLabel(label, job) { return job && job.cancelled ? 'Stopped (' + job.done + ' / ' + job.total + ' cuts)' + (label ? ' ' + label : '') : label; }
    bBuild.onClick = function () { doBuild(''); };

    // \u304A\u307E\u304B\u305B: roll every setting on the panel, show it, then build a fresh comp
    bOmk.onClick = function () {
        var r = jzOmakase(moodKey(), JZ_DATA.styleOrder[ddStyle.selection ? ddStyle.selection.index : 0], null, switches());
        ddStyle.selection = jzIndexOf(JZ_DATA.styleOrder, r.style);
        ddMood.selection = jzIndexOf(JZ_DATA.moodOrder, r.mood) + 1;
        var map = [[sMotion, 'motion'], [sGlitch, 'glitch'], [sChroma, 'chroma'], [sDecor, 'decor'], [sDensity, 'density'], [sTexture, 'texture'], [sBg, 'bgSwitch']];
        for (var q = 0; q < map.length; q++) if (r.fx[map[q][1]] != null) { map[q][0].value = Math.round(r.fx[map[q][1]] * 100); map[q][0].lbl.text = String(map[q][0].value); }
        cTwos.value = r.onTwos; cFlash.value = r.flash; ddHud.selection = r.hud; eSeed.text = String(r.seed);
        if (r.palette) { setPal(r.palette); cPal.value = true; } else { cPal.value = false; showStylePal(); }
        doBuild('Variation: ' + JZ_DATA.styles[r.style].name + ' \u00D7 ' + JZ_DATA.moods[r.mood].name + (r.palette ? ' \u00B7 Random palette' : ''));
    };

    bJson.onClick = function () {
        var f = File.openDialog('JIZURA AE JSON', 'JSON:*.json', false);
        if (!f) return;
        var t0 = new Date().getTime(), plan;
        try { f.encoding = 'UTF-8'; f.open('r'); var s = f.read(); f.close(); plan = jzParseJSON(s); }
        catch (e) { alert('Could not read JSON: ' + e.toString()); return; }
        if (!plan || !plan.cuts || !plan.style) { alert('This is not a JIZURA AE project JSON'); return; }
        var note = plan.aeNote ? String(plan.aeNote) : '';
        if (plan.version !== 2 && !note) note = '';
        var au = audioSel(cAudio2.value);
        status.text = 'Building\u2026 (' + plan.cuts.length + ' cuts)';
        // a line-range JSON starts part-way into the song: slide the song layer left by the same amount
        var off = +plan.audioOffset || 0;
        runBuild(plan, { roles: roles(), audioItem: au ? au.item : null, audioStart: au ? au.start - off : 0, light: cLight.value }, 'JIZURA build from JSON', function (comp, job) {
            if (comp) { lastComp = comp; lastPlan = plan; }
            if (JZ_FALLBACKS > 0) {
                note = (note ? note + ' / ' : '') + 'Techniques unavailable in this panel: ' + JZ_FALLBACKS + ' instances were replaced by similar techniques';
                alert('JIZURA: This JSON contains ' + JZ_FALLBACKS + ' unsupported techniques; similar ones were substituted.\n\n' + JZ_FALLBACK_KEYS.slice(0, 12).join(', ') +
                    '\n\nThis panel may be older than the browser edition. Install the latest JIZURA_AE_en.jsx (v' + JZ_PANEL_VERSION + ', 860 techniques) and restart After Effects.');
            }
            report(comp, t0, jobLabel(note ? 'Substitutions' : '', job));
            if (note) status.helpTip = note;
        });
    };
    bDiag.onClick = function () {
        if (!lastComp) { alert('Create a composition first; diagnostics inspect the last one created here.'); return; }
        var ok = false; try { ok = !!lastComp.name; } catch (e) { ok = false; }
        if (!ok) { alert('The last composition could not be found (it may have been deleted).'); return; }
        status.text = 'Diagnosing\u2026 (this can take a few seconds)';
        var r = jzDiagnose(lastComp, lastPlan, 120), path = jzSaveReport(r.text);
        status.text = 'Diagnostics: expression errors ' + r.errors + ' / ' + r.expressions + (r.partial ? ' (partial)' : '');
        alert('JIZURA diagnostics: expressions ' + r.expressions + ', errors ' + r.errors + '' + (r.partial ? ' (partial, time limit)' : '') + '\n\n' +
            (path ? 'Report saved: \n' + path : 'Could not save report. Allow scripts to write files and access the network in After Effects preferences.\n\n' + r.text.substr(0, 1500)));
    };

    win.onResizing = win.onResize = function () { try { this.layout.resize(); } catch (e) {} };
    if (win instanceof Window) { win.center(); win.show(); } else { win.layout.layout(true); win.layout.resize(); }
    return win;
}

jzUI(thisObj);
})(this);
