{
  "name": "Northern Lights Portal",
  "dimension": "3d",
  "slot": "action_bar",
  "width": 256,
  "height": 128,
  "textBaseline": 128,
  "placement": {
    "x": 0,
    "y": 0
  },
  "shapes": [],
  "texts": [],
  "heads": [],
  "images": [],
  "bars": [],
  "triggers": [],
  "world": {
    "glsl": "const int STATE_OPEN = 0;\nconst int STATE_SURGE = 1;\nconst float PI = 3.14159265;\nfloat portalRing(vec3 p) {\n    return length(vec2(length(p.xy)-0.79,p.z))-0.032;\n}\nfloat flakeLine(vec2 p, vec2 a, vec2 b) {\n    vec2 q=p-a, v=b-a;\n    return length(q-v*clamp(dot(q,v)/dot(v,v),0.0,1.0));\n}\nfloat snowShape(vec2 p) {\n    float a=mod(atan(p.y,p.x)+PI/6.0,PI/3.0)-PI/6.0;\n    vec2 q=vec2(cos(a),abs(sin(a)))*length(p);\n    float d=flakeLine(q,vec2(0.0),vec2(1.0,0.0));\n    d=min(d,flakeLine(q,vec2(0.47,0.0),vec2(0.77,0.26)));\n    return min(d,flakeLine(q,vec2(0.73,0.0),vec2(0.91,0.16)));\n}\nvec4 object(vec3 ro, vec3 rd, float t0, float t1, float time, vec3 light, inout float depth) {\n    float clock=time+rpw_seed*3.73;\n    float surge=0.0;\n    if(rpw_state==STATE_SURGE)surge=sin(PI*clamp(rpw_state_time/3.0,0.0,1.0));\n    float ringT=t1;\n    bool ringHit=false;\n    float t=t0;\n    for(int i=0;i<48;i++) {\n        float d=portalRing(ro+rd*t);\n        if(d<0.001) {ringHit=true;ringT=t;break;}\n        t+=max(d*0.9,0.001);\n        if(t>t1)break;\n    }\n    vec3 ringCol=vec3(0.0);\n    if(ringHit) {\n        vec3 p=ro+rd*ringT;\n        vec3 n=normalize(p-vec3(normalize(p.xy)*0.79,0.0));\n        float angle=atan(p.y,p.x);\n        float hue=0.5+0.5*sin(angle*2.0-clock*0.32);\n        vec3 tint=mix(vec3(0.10,1.0,0.40),vec3(0.60,0.10,1.0),hue);\n        float ridge=pow(0.5+0.5*cos(atan(p.z,length(p.xy)-0.79)*5.0+angle*3.0-clock*0.3),12.0);\n        float lam=max(dot(n,normalize(vec3(0.4,0.8,0.5))),0.0);\n        ringCol=tint*(0.45+0.45*lam+ridge*1.8)+vec3(0.62,1.0,0.90)*pow(max(dot(n,normalize(vec3(0.2,0.6,0.8))),0.0),30.0)*1.4;\n        ringCol*=1.0+surge*0.6;\n    }\n    float nearT=t0, farT=min(t1,ringT);\n    float aa=dot(rd.xy,rd.xy), bb=dot(ro.xy,rd.xy);\n    float cc=dot(ro.xy,ro.xy)-0.775*0.775;\n    float hh=bb*bb-aa*cc;\n    bool volume=true;\n    if(aa>0.00001) {\n        if(hh<0.0)volume=false;\n        else {float h=sqrt(max(hh,0.0));nearT=max(nearT,(-bb-h)/aa);farT=min(farT,(-bb+h)/aa);}\n    } else if(cc>0.0)volume=false;\n    if(abs(rd.z)>0.00001) {\n        float za=(-0.23-ro.z)/rd.z, zb=(0.23-ro.z)/rd.z;\n        nearT=max(nearT,min(za,zb));farT=min(farT,max(za,zb));\n    } else if(abs(ro.z)>0.23)volume=false;\n    vec3 col=vec3(0.0);\n    float alpha=0.0, visibleT=t1;\n    if(volume && farT>nearT) {\n        float stepT=(farT-nearT)/20.0;\n        for(int i=0;i<20;i++) {\n            float st=nearT+(float(i)+0.5)*stepT;\n            vec3 p=ro+rd*st;\n            float rr=length(p.xy);\n            float edge=1.0-smoothstep(0.70,0.775,rr);\n            vec2 uv=rpw_rot(0.25*sin(clock*0.16)+rr*0.45)*p.xy;\n            float twist=0.16*sin(uv.y*4.0+clock*0.23)+0.06*sin(uv.y*9.0-clock*0.19);\n            float x=uv.x+twist;\n            float wave=sin(x*7.3+sin(x*9.0+clock*0.18)*0.8+clock*0.21);\n            float curtainZ=wave*0.105+0.035*sin(uv.y*5.0+x*4.0-clock*0.3);\n            float sheet=exp(-abs(p.z-curtainZ)*29.0);\n            float lower=-0.34+0.19*sin(x*6.0-clock*0.23)+0.045*sin(x*17.0+clock*0.18);\n            float y=uv.y-lower;\n            float height=smoothstep(-0.065,0.04,y)*exp(-max(y,0.0)*2.2);\n            float fibres=0.35+0.65*pow(0.5+0.5*sin(x*110.0+sin(x*27.0)*2.0+clock*0.35),2.0);\n            float density=sheet*height*(0.5+fibres*1.1);\n            float second=exp(-abs(p.z+curtainZ+0.10)*32.0)*smoothstep(-0.3,0.25,uv.y)*(0.3+0.7*pow(0.5+0.5*sin(x*82.0-clock*0.27),2.0));\n            float curlY=uv.y+0.49-0.13*sin(x*7.0-clock*0.26);\n            float lowRibbon=exp(-abs(curlY)*16.0)*exp(-abs(p.z-0.10+0.06*sin(x*6.0+clock*0.2))*27.0);\n            lowRibbon*=0.25+0.60*fibres;\n            float purple=smoothstep(0.13,0.65,y);\n            vec3 aurora=mix(vec3(0.06,1.6,0.39),vec3(0.71,0.10,1.5),purple);\n            vec3 lowTint=mix(vec3(0.06,0.95,0.48),vec3(0.44,0.08,1.05),0.5+0.5*sin(x*4.0+clock*0.19));\n            vec3 emission=aurora*density+vec3(0.66,0.08,1.3)*second*0.85+lowTint*lowRibbon*0.75;\n            float base=1.7*(1.0-smoothstep(0.16,0.23,abs(p.z)));\n            float den=density*4.0+second*2.7+lowRibbon*2.0+base;\n            float a=1.0-exp(-den*edge*stepT*2.0);\n            vec3 c=(emission*7.0+vec3(0.008,0.015,0.055)*base)/den;\n            col+=(1.0-alpha)*c*a*(1.0+surge*0.5);\n            alpha+=(1.0-alpha)*a;\n            if(alpha>0.25 && visibleT==t1)visibleT=st;\n        }\n    }\n    if(ringHit) {col+=(1.0-alpha)*ringCol;alpha=1.0;visibleT=min(visibleT,ringT);}\n    float faceT=abs(rd.z)>0.0001?-ro.z/rd.z:-1.0;\n    if(faceT>=t0 && faceT<=t1) {\n        vec3 p=ro+rd*faceT;\n        float r=length(p.xy);\n        float glow=exp(-abs(r-0.79)*55.0)*0.47;\n        glow*=1.0-smoothstep(0.87,0.94,r);\n        float h=0.5+0.5*sin(atan(p.y,p.x)*2.0-clock*0.32);\n        vec3 gc=mix(vec3(0.10,1.0,0.40),vec3(0.60,0.10,1.0),h);\n        col+=gc*glow*(1.0-alpha*0.35);\n        alpha=max(alpha,glow);\n        if(glow>0.1)visibleT=min(visibleT,faceT);\n    }\n    // Three drifting crystal fields; only the cell beneath the ray needs evaluating.\n    for(int i=0;i<3;i++) {\n        float fi=float(i);\n        vec3 normal=normalize(vec3(0.08*sin(clock*0.13+fi),0.16*cos(clock*0.17+fi*2.0),1.0));\n        vec3 centre=vec3(0.0,0.0,-0.16+fi*0.22+0.035*sin(clock*0.23+fi));\n        float denom=dot(rd,normal);\n        if(abs(denom)<0.001)continue;\n        float ft=dot(centre-ro,normal)/denom;\n        if(ft<t0 || ft>t1 || (ringHit && ft>ringT))continue;\n        vec3 pos=ro+rd*ft;\n        float fade=1.0-smoothstep(0.66,0.76,length(pos.xy));\n        if(fade<0.01 || length(pos)>0.92)continue;\n        vec2 drift=vec2(0.035*sin(clock*0.32+fi),clock*(0.040+fi*0.012));\n        vec2 cell=floor((pos.xy+drift)/0.32);\n        float h=rpw_hash(vec3(cell,fi+rpw_seed));\n        if(h<0.45)continue;\n        float h2=rpw_hash(vec3(cell+7.1,fi+rpw_seed));\n        vec2 fc=(cell+vec2(0.22+0.56*h,0.22+0.56*h2))*0.32;\n        vec2 q=(pos.xy+drift-fc)/(0.020+h2*0.017);\n        if(dot(q,q)>1.2)continue;\n        q=rpw_rot(clock*0.16+h*6.28)*q;\n        float ink=(1.0-smoothstep(0.035,0.10,snowShape(q)))*fade;\n        vec3 sc=mix(vec3(0.55,0.78,1.0),vec3(0.88,1.0,0.96),h)*1.6*(0.70+fi*0.15);\n        col=col*(1.0-ink)+sc*ink;\n        alpha+=(1.0-alpha)*ink;\n        if(ink>0.35)visibleT=min(visibleT,ft);\n    }\n    if(alpha<0.002)return vec4(0.0);\n    depth=clamp(visibleT,t0,t1);\n    return vec4(col/max(alpha,0.0001),clamp(alpha,0.0,1.0));\n}",
    "prompt": "a glowing round portal filled with swirling green and purple aurora borealis light, with snowflakes drifting through it",
    "size": 6
  }
}