برچسب: With

  • Matrix Sentinels: Building Dynamic Particle Trails with TSL

    Matrix Sentinels: Building Dynamic Particle Trails with TSL


    While experimenting with particle systems, I challenged myself to create particles with tails, similar to snakes moving through space. At first, I didn’t have access to TSL, so I tested basic ideas, like using noise derivatives and calculating previous steps for each particle, but none of them worked as expected.

    I spent a long time pondering how to make it work, but all my solutions involved heavy testing with WebGL and GPGPU, which seemed like it would require too much code for a simple proof of concept. That’s when TSL (Three.js Shader Language) came into play. With its Compute Shaders, I was able to compute arrays and feed the results into materials, making it easier to test ideas quickly and efficiently. This allowed me to accomplish the task without much time lost.

    Now, let’s dive into the step-by-step process of building the particle system, from setting up the environment to creating the trails and achieving that fluid movement.

    Step 1: Set Up the Particle System

    First, we’ll define the necessary uniforms that will be used to create and control the particles in the system.

    uniforms = {
        color: uniform( new THREE.Color( 0xffffff ).setRGB( 1, 1, 1 ) ),
        size: uniform( 0.489 ),
    
        uFlowFieldInfluence: uniform( 0.5 ),
        uFlowFieldStrength: uniform( 3.043 ),
        uFlowFieldFrequency: uniform( 0.207 ),
    }

    Next, create the variables that will define the parameters of the particle system. The “tails_count” variable determines how many segments each snake will have, while the “particles_count” defines the total number of segments in the scene. The “story_count” variable represents the number of frames used to store the position data for each segment. Increasing this value will increase the distance between segments, as we will store the position history of each one. The “story_snake” variable holds the history of one snake, while “full_story_length” stores the history for all snakes. These variables will be enough to bring the concept to life.

    tails_count = 7 //  n-1 point tails
    particles_count = this.tails_count * 200 // need % tails_count
    story_count = 5 // story for 1 position
    story_snake = this.tails_count * this.story_count
    full_story_length = ( this.particles_count / this.tails_count ) * this.story_snake

    Next, we need to create the buffers required for the computational shaders. The most important buffer to focus on is the “positionStoryBuffer,” which will store the position history of all segments. To understand how it works, imagine a train: the head of the train sets the direction, and the cars follow in the same path. By saving the position history of the head, we can use that data to determine the position of each car by referencing its position in the history.

    const positionsArray = new Float32Array( this.particles_count * 3 )
    const lifeArray = new Float32Array( this.particles_count )
    
    const positionInitBuffer = instancedArray( positionsArray, 'vec3' );
    const positionBuffer = instancedArray( positionsArray, 'vec3' );
    
    // Tails
    const positionStoryBuffer = instancedArray( new Float32Array( this.particles_count * this.tails_count * this.story_count ), 'vec3' );
    
    const lifeBuffer = instancedArray( lifeArray, 'float' );

    Now, let’s create the particle system with a material. I chose a standard material because it allows us to use an emissiveNode, which will interact with Bloom effects. For each segment, we’ll use a sphere and disable frustum culling to ensure the particles don’t accidentally disappear off the screen.

    const particlesMaterial = new THREE.MeshStandardNodeMaterial( {
        metalness: 1.0,
        roughness: 0
    } );
        
    particlesMaterial.emissiveNode = color(0x00ff00)
    
    const sphereGeometry = new THREE.SphereGeometry( 0.1, 32, 32 );
    
    const particlesMesh = this.particlesMesh = new THREE.InstancedMesh( sphereGeometry, particlesMaterial, this.particles_count );
    particlesMesh.instanceMatrix.setUsage( THREE.DynamicDrawUsage );
    particlesMesh.frustumCulled = false;
    
    this.scene.add( this.particlesMesh )
    

    Step 2: Initialize Particle Positions

    To initialize the positions of the particles, we’ll use a computational shader to reduce CPU usage and speed up page loading. We randomly generate the particle positions, which form a pseudo-cube shape. To keep the particles always visible on screen, we assign them a lifetime after which they disappear and won’t reappear from their starting positions. The “cycleStep” helps us assign each snake its own random positions, ensuring the tails are generated in the same location as the head. Finally, we send this data to the computation process.

    const computeInit = this.computeInit = Fn( () => {
        const position = positionBuffer.element( instanceIndex )
        const positionInit = positionInitBuffer.element( instanceIndex );
        const life = lifeBuffer.element( instanceIndex )
    
        // Position
        position.xyz = vec3(
            hash( instanceIndex.add( uint( Math.random() * 0xffffff ) ) ),
            hash( instanceIndex.add( uint( Math.random() * 0xffffff ) ) ),
            hash( instanceIndex.add( uint( Math.random() * 0xffffff ) ) )
        ).sub( 0.5 ).mul( vec3( 5, 5, 5 ) );
    
        // Copy Init
        positionInit.assign( position )
    
        const cycleStep = uint( float( instanceIndex ).div( this.tails_count ).floor() )
    
        // Life
        const lifeRandom = hash( cycleStep.add( uint( Math.random() * 0xffffff ) ) )
        life.assign( lifeRandom )
    
    } )().compute( this.particles_count );
    
    this.renderer.computeAsync( this.computeInit ).then( () => {
        this.initialCompute = true
    } )
    Initialization of particle position

    Step 3: Compute Position History

    For each frame, we compute the position history for each segment. The key aspect of the “computePositionStory” function is that new positions are recorded only from the head of the snake, and all positions are shifted one step forward using a queue algorithm.

    const computePositionStory = this.computePositionStory = Fn( () => {
        const positionStory = positionStoryBuffer.element( instanceIndex )
    
        const cycleStep = instanceIndex.mod( uint( this.story_snake ) )
        const lastPosition = positionBuffer.element( uint( float( instanceIndex.div( this.story_snake ) ).floor().mul( this.tails_count ) ) )
    
        If( cycleStep.equal( 0 ), () => { // Head
            positionStory.assign( lastPosition )
        } )
    
        positionStoryBuffer.element( instanceIndex.add( 1 ) ).assign( positionStoryBuffer.element( instanceIndex ) )
    
    } )().compute( this.full_story_length );

    Step 4: Update Particle Positions

    Next, we update the positions of all particles, taking into account the recorded history of their positions. First, we use simplex noise to generate the new positions of the particles, allowing our snakes to move smoothly through space. Each particle also has its own lifetime, during which it moves and eventually resets to its original position. The key part of this function is determining which particle is the head and which is the tail. For the head, we generate a new position based on simplex noise, while for the tail, we use positions from the saved history.

    const computeUpdate = this.computeUpdate = Fn( () => {
    
        const position = positionBuffer.element( instanceIndex )
        const positionInit = positionInitBuffer.element( instanceIndex )
    
        const life = lifeBuffer.element( instanceIndex );
    
        const _time = time.mul( 0.2 )
    
        const uFlowFieldInfluence = this.uniforms.uFlowFieldInfluence
        const uFlowFieldStrength = this.uniforms.uFlowFieldStrength
        const uFlowFieldFrequency = this.uniforms.uFlowFieldFrequency
    
        If( life.greaterThanEqual( 1 ), () => {
            life.assign( life.mod( 1 ) )
            position.assign( positionInit )
    
        } ).Else( () => {
            life.addAssign( deltaTime.mul( 0.2 ) )
        } )
    
        // Strength
        const strength = simplexNoise4d( vec4( position.mul( 0.2 ), _time.add( 1 ) ) ).toVar()
        const influence = uFlowFieldInfluence.sub( 0.5 ).mul( -2.0 ).toVar()
        strength.assign( smoothstep( influence, 1.0, strength ) )
    
        // Flow field
        const flowField = vec3(
            simplexNoise4d( vec4( position.mul( uFlowFieldFrequency ).add( 0 ), _time ) ),
            simplexNoise4d( vec4( position.mul( uFlowFieldFrequency ).add( 1.0 ), _time ) ),
            simplexNoise4d( vec4( position.mul( uFlowFieldFrequency ).add( 2.0 ), _time ) )
        ).normalize()
    
        const cycleStep = instanceIndex.mod( uint( this.tails_count ) )
    
        If( cycleStep.equal( 0 ), () => { // Head
            const newPos = position.add( flowField.mul( deltaTime ).mul( uFlowFieldStrength ) /* * strength */ )
            position.assign( newPos )
        } ).Else( () => { // Tail
            const prevTail = positionStoryBuffer.element( instanceIndex.mul( this.story_count ) )
            position.assign( prevTail )
        } )
    
    } )().compute( this.particles_count );

    To display the particle positions, we’ll create a simple function called “positionNode.” This function will not only output the positions but also apply a slight magnification effect to the head of the snake.

    particlesMaterial.positionNode = Fn( () => {
        const position = positionBuffer.element( instanceIndex );
    
        const cycleStep = instanceIndex.mod( uint( this.tails_count ) )
        const finalSize = this.uniforms.size.toVar()
    
        If( cycleStep.equal( 0 ), () => {
            finalSize.addAssign( 0.5 )
        } )
    
        return positionLocal.mul( finalSize ).add( position )
    } )()

    The final element will be to update the calculations on each frame.

    async update( deltaTime ) {
    
        // Compute update
        if( this.initialCompute) {
            await this.renderer.computeAsync( this.computePositionStory )
            await this.renderer.computeAsync( this.computeUpdate )
        }
    }

    Conclusion

    Now, you should be able to easily create position history buffers for other problem-solving tasks, and with TSL, this process becomes quick and efficient. I believe this project has potential for further development, such as transferring position data to model bones. This could enable the creation of beautiful, flying dragons or similar effects in 3D space. For this, a custom bone structure tailored to the project would be needed.



    Source link

  • Easy logging management with Seq and ILogger in ASP.NET | Code4IT

    Easy logging management with Seq and ILogger in ASP.NET | Code4IT


    Seq is one of the best Log Sinks out there : it’s easy to install and configure, and can be added to an ASP.NET application with just a line of code.

    Table of Contents

    Just a second! 🫷
    If you are here, it means that you are a software developer.
    So, you know that storage, networking, and domain management have a cost .

    If you want to support this blog, please ensure that you have disabled the adblocker for this site.
    I configured Google AdSense to show as few ADS as possible – I don’t want to bother you with lots of ads, but I still need to add some to pay for the resources for my site.

    Thank you for your understanding.
    Davide

    Logging is one of the most essential parts of any application.

    Wouldn’t it be great if we could scaffold and use a logging platform with just a few lines of code?

    In this article, we are going to learn how to install and use Seq as a destination for our logs, and how to make an ASP.NET 8 API application send its logs to Seq by using the native logging implementation.

    Seq: a sink and dashboard to manage your logs

    In the context of logging management, a “sink” is a receiver of the logs generated by one or many applications; it can be a cloud-based system, but it’s not mandatory: even a file on your local file system can be considered a sink.

    Seq is a Sink, and works by exposing a server that stores logs and events generated by an application. Clearly, other than just storing the logs, Seq allows you to view them, access their details, perform queries over the collection of logs, and much more.

    It’s free to use for individual usage, and comes with several pricing plans, depending on the usage and the size of the team.

    Let’s start small and install the free version.

    We have two options:

    1. Download it locally, using an installer (here’s the download page);
    2. Use Docker: pull the datalust/seq image locally and run the container on your Docker engine.

    Both ways will give you the same result.

    However, if you already have experience with Docker, I suggest you use the second approach.

    Once you have Docker installed and running locally, open a terminal.

    First, you have to pull the Seq image locally (I know, it’s not mandatory, but I prefer doing it in a separate step):

    Then, when you have it downloaded, you can start a new instance of Seq locally, exposing the UI on a specific port.

    docker run --name seq -d --restart unless-stopped -e ACCEPT_EULA=Y -p 5341:80 datalust/seq:latest
    

    Let’s break down the previous command:

    • docker run: This command is used to create and start a new Docker container.
    • --name seq: This option assigns the name seq to the container. Naming containers can make them easier to manage.
    • -d: This flag runs the container in detached mode, meaning it runs in the background.
    • --restart unless-stopped: This option ensures that the container will always restart unless it is explicitly stopped. This is useful for ensuring that the container remains running even after a reboot or if it crashes.
    • -e ACCEPT_EULA=Y: This sets an environment variable inside the container. In this case, it sets ACCEPT_EULA to Y, which likely indicates that you accept the End User License Agreement (EULA) for the software running in the container.
    • -p 5341:80: This maps port 5341 on your host machine to port 80 in the container. This allows you to access the service running on port 80 inside the container via port 5341 on your host.
    • datalust/seq:latest: This specifies the Docker image to use for the container. datalust/seq is the image name, and latest is the tag, indicating that you want to use the latest version of this image.

    So, this command runs a container named seq in the background, ensures it restarts unless stopped, sets an environment variable to accept the EULA, maps a host port to a container port, and uses the latest version of the datalust/seq image.

    It’s important to pay attention to the used port: by default, Seq uses port 5341 to interact with the UI and the API. If you prefer to use another port, feel free to do that – just remember that you’ll need some additional configuration.

    Now that Seq is installed on your machine, you can access its UI. Guess what? It’s on localhost:5341!

    Seq brand new instance

    However, Seq is “just” a container for our logs – but we have to produce them.

    A sample ASP.NET API project

    I’ve created a simple API project that exposes CRUD operations for a data model stored in memory (we don’t really care about the details).

    [ApiController]
    [Route("[controller]")]
    public class BooksController : ControllerBase
    {
        public BooksController()
        {
    
        }
    
        [HttpGet("{id}")]
        public ActionResult<Book> GetBook([FromRoute] int id)
        {
    
            Book? book = booksCatalogue.SingleOrDefault(x => x.Id == id);
            return book switch
            {
                null => NotFound(),
                _ => Ok(book)
            };
        }
    }
    

    As you can see, the details here are not important.

    Even the Main method is the default one:

    var builder = WebApplication.CreateBuilder(args);
    
    builder.Services.AddControllers();
    
    builder.Services.AddEndpointsApiExplorer();
    builder.Services.AddSwaggerGen();
    
    var app = builder.Build();
    
    if (app.Environment.IsDevelopment())
    {
        app.UseSwagger();
        app.UseSwaggerUI();
    }
    
    app.UseHttpsRedirection();
    
    app.MapControllers();
    
    app.Run();
    

    We have the Controllers, we have Swagger… well, nothing fancy.

    Let’s mix it all together.

    How to integrate Seq with an ASP.NET application

    If you want to use Seq in an ASP.NET application (may it be an API application or whatever else), you have to add it to the startup pipeline.

    First, you have to install the proper NuGet package: Seq.Extensions.Logging.

    The Seq.Extensions.Logging NuGet package

    Then, you have to add it to your Services, calling the AddSeq() method:

    var builder = WebApplication.CreateBuilder(args);
    
    builder.Services.AddControllers();
    
    builder.Services.AddEndpointsApiExplorer();
    builder.Services.AddSwaggerGen();
    
    + builder.Services.AddLogging(lb => lb.AddSeq());
    
    var app = builder.Build();
    

    Now, Seq is ready to intercept whatever kind of log arrives at the specified port (remember, in our case, we are using the default one: 5341).

    We can try it out by adding an ILogger to the BooksController constructor:

    private readonly ILogger<BooksController> _logger;
    
    public BooksController(ILogger<BooksController> logger)
    {
        _logger = logger;
    }
    

    So that we can use the _logger instance to create logs as we want, using the necessary Log Level:

    [HttpGet("{id}")]
    public ActionResult<Book> GetBook([FromRoute] int id)
    {
        _logger.LogInformation("I am Information");
        _logger.LogWarning("I am Warning");
        _logger.LogError("I am Error");
        _logger.LogCritical("I am Critical");
    
        Book? book = booksCatalogue.SingleOrDefault(x => x.Id == id);
        return book switch
        {
            null => NotFound(),
            _ => Ok(book)
        };
    }
    

    Log messages on Seq

    Using Structured Logging with ILogger and Seq

    One of the best things about Seq is that it automatically handles Structured Logging.

    [HttpGet("{id}")]
    public ActionResult<Book> GetBook([FromRoute] int id)
    {
        _logger.LogInformation("Looking if in my collection with {TotalBooksCount} books there is one with ID {SearchedId}"
     , booksCatalogue.Count, id);
    
        Book? book = booksCatalogue.SingleOrDefault(x => x.Id == id);
        return book switch
        {
            null => NotFound(),
            _ => Ok(book)
        };
    }
    

    Have a look at this line:

    _logger.LogInformation("Looking if in my collection with {TotalBooksCount} books there is one with ID {SearchedId}"
     , booksCatalogue.Count, id);
    

    This line generates a string message, replaces all the placeholders, and, on top of that, creates two properties, SearchedId and TotalBooksCount; you can now define queries using these values.

    Structured Logs in Seq allow you to view additional logging properties

    Further readings

    I have to admit it: logging management is one of my favourite topics.

    I’ve already written a sort of introduction to Seq in the past, but at that time, I did not use the native ILogger, but Serilog, a well-known logging library that added some more functionalities on top of the native logger.

    🔗 Logging with Serilog and Seq | Code4IT

    This article first appeared on Code4IT 🐧

    In particular, Serilog can be useful for propagating Correlation IDs across multiple services so that you can fetch all the logs generated by a specific operation, even though they belong to separate applications.

    🔗 How to log Correlation IDs in .NET APIs with Serilog

    Feel free to search through my blog all the articles related to logging – I’m sure you will find interesting stuff!

    Wrapping up

    I think Seq is the best tool for local development: it’s easy to download and install, supports structured logging, and can be easily added to an ASP.NET application with just a line of code.

    I usually add it to my private projects, especially when the operations I run are complex enough to require some well-structured log.

    Given how it’s easy to install, sometimes I use it for my work projects too: when I have to fix a bug, but I don’t want to use the centralized logging platform (since it’s quite complex to use), I add Seq as a destination sink, run the application, and analyze the logs in my local machine. Then, of course, I remove its reference, as I want it to be just a discardable piece of configuration.

    I hope you enjoyed this article! Let’s keep in touch on LinkedIn, Twitter or BlueSky! 🤜🤛

    Happy coding!

    🐧





    Source link

  • Python – Simple Stock Analysis with yfinance – Useful code

    Python – Simple Stock Analysis with yfinance – Useful code


    Sometimes, the graphs of stocks are useful. Sometimes these are not. In general, do your own research, none of this is financial advice.

    And while doing that, if you want to analyze stocks with just a few lines of python, this article might help? This simple yet powerful script helps you spot potential buy and sell opportunities for Apple (AAPL) using two classic technical indicators: moving averages and RSI.

    Understanding the Strategy

    1. SMA Crossover: The Trend Following Signal

    The script first calculates two Simple Moving Averages (SMA):

    The crossover strategy is simple:

    This works because moving averages smooth out price noise, helping identify the overall trend direction.

    2. RSI: The Overbought/Oversold Indicator

    The Relative Strength Index (RSI) measures whether a stock is overbought or oversold:

    By combining SMA crossovers (trend confirmation) and RSI extremes (timing), we get stronger signals.

    This plot is generated with less than 40 lines of python code

    The code looks like that:

    The code above, but in way more details is explained in the YT video below:

    https://www.youtube.com/watch?v=m0ayASmrZmE

    And it is available in GitHub as well.



    Source link

  • Threat Actors are Targeting US Tax-Session with new Tactics of Stealerium-infostealer

    Threat Actors are Targeting US Tax-Session with new Tactics of Stealerium-infostealer


    Introduction

    A security researcher from Seqrite Labs has uncovered a malicious campaign targeting U.S. citizens as Tax Day approaches on April 15. Seqrite Labs has identified multiple phishing attacks leveraging tax-related themes as a vector for social engineering, aiming to exfiltrate user credentials and deploy malware. These campaigns predominantly utilize redirection techniques, such as phishing emails, and exploit malicious LNK files to further their objectives.

    Each year, cybercriminals exploit the tax season as an opportunity to deploy various social engineering tactics to compromise sensitive personal and financial data. These adversaries craft highly deceptive campaigns designed to trick taxpayers into divulging confidential information, making fraudulent to counterfeit services, or inadvertently installing malicious payloads on their devices, thereby exposing them to identity theft and financial loss.

    Infection Chain:

    Fig 1: Infection chain

    Initial analysis about campaign:

    While tax-season phishing, attacks pose a risk to a broad spectrum of individuals, our analysis indicates that certain demographics are disproportionately vulnerable. Specifically, high-risk targets include individuals with limited knowledge of government tax processes, such as green card holders, small business owners, and new taxpayers.

    Our findings reveal that threat actors are leveraging a sophisticated phishing technique in which they deliver files via email with deceptive extensions. One such example is a file named “104842599782-4.pdf.lnk,” which utilizes a malicious LNK extension. This tactic exploits user trust by masquerading as a legiti payments mate document, ultimately leading to the execution of malicious payloads upon interaction.

    Decoy Document:

    Threat actors are disseminating a transcript related to tax sessions, targeting individuals through email by sharing it as a malicious attachment. These cybercriminals are leveraging this document as a vector to deliver harmful payloads, thereby compromising the security of the recipients.

     

    Fig 2: Decoy Document

    Technical Analysis:

    We have retrieved the LNK file, identified as “04842599782-4.pdf.lnk,” which was utilized in the attack. This LNK file embeds a Base64-encoded payload within its structure.

    Fig 3: Inside LNK File

    Upon decoding the string, we extracted a PowerShell command line that itself contains another Base64-encoded payload embedded within it.

    Fig 4: Encoded PowerShell Command Line

     

    Subsequently, upon decoding the nested Base64 string, we uncovered the final PowerShell command line embedded within the payload.

    Fig 5: Decoded Command Line

    The extracted PowerShell command line initiated the download of rev_pf2_yas.txt, which itself is a PowerShell script (Payload.ps1) containing yet another Base64-encoded payload embedded within it.

    Fig 6: 2nd PowerShell command with Base64 Encoded

    We have decoded the above Base64 encoded command line and get below final executable.

    Fig 7: Decoded PowerShell Command

    According to the PowerShell command line, the script Payload.ps1 (or rev_pf2_yas.txt) initiated the download of an additional file, revolaomt.rar, from the Command and Control (C2) server. This archive contained a malicious executable, named either Setup.exe or revolaomt.exe.

    Detail analysis of Setup.exe / revolaomt.exe:

    Fig 8: Detect it Easy

    Upon detailed examination of the Setup.exe binary, it was identified as a PyInstaller-packaged Python executable. Subsequent extraction and decompilation revealed embedded Python bytecode artifacts, including DCTYKS.pyc and additional Python module components.

    Fig 9: PyInstaller-packaged Python executable
    Fig 10: In side DCTYKS.pyc

    Upon analysis of the DCTYKS.pyc sample, it was determined that the file contains obfuscated or encrypted payload data, which is programmatically decrypted at runtime and subsequently executed, as illustrated in the figure above.

    Fig 11: Encoded DCTYKS.pyc with Base64

    Upon successful decryption of the script, it was observed that the sample embeds a Base64-encoded executable payload. The decrypted payload leverages process injection techniques to target mstsc.exe for execution. Further analysis of the second-stage payload revealed it to be a .NET-compiled binary.

    Analysis 2nd Payload (Stealerium malware):

    Fig 12: .NET Base Malware sample

    The second-stage payload is identified as a .NET-based malware sample. Upon inspection of its class structures, methods, and overall functionality, the sample exhibits strong behavioural and structural similarities to the Stealerium malware family, specifically aligning with version 1.0.35.

    Stealerium is an open-source information-stealing malware designed to exfiltrate sensitive data from web browsers, cryptocurrency wallets, and popular applications such as Discord, Steam, and Telegram. It performs extensive system reconnaissance by harvesting details including active processes, desktop screenshots, and available Wi-Fi network configurations. Additionally, the malware incorporates sophisticated anti-analysis mechanisms to identify execution within virtualized environments and detect the presence of debugging tools.

    Anti_Analysis

    Fig 13: Anti Analysis Techniques
    Fig 14: GitHub URLs
    Fig 15: Detecting Suspicious ENV

    This AntiAnalysis class is part of malware designed to detect sandbox, virtual machines, emulators, suspicious processes, services, usernames, and more. It checks system attributes against blacklists fetched from online sources (github). If any suspicious environment is detected, it logs the finding and may trigger self-destruction. This helps the malware avoid analysis in controlled or security research setups.

    Mutex Creation

    Fig 16: Mutex Creation

    This MutexControl class prevents multiple instances of the malware from running at the same time. It tries to create a system-wide mutex using a name from Config.Mutex (QT1bm11ocWPx). If the mutex already exists, it means another instance is running, so it exits the process. If an error occurs during this check, it logs the error and exits too.

    Fig 17: Configuration of StringsCrypt.DecryptConfig

    It configures necessary values by decrypting them with StringsCrypt.DecryptConfig. It handles the decryption of the server base URL and WebSocket address. If enabled, it also decodes cryptocurrency wallet addresses from Base64 and decrypts them using AES-256 encryption.

    “hxxp://91.211.249.142:7816”

    Radom Directory Creation

    Fig 18: Random Directory Creation

    The InitWorkDir() method generates a random subdirectory under %LOCALAPPDATA%, creates it if it doesn’t exist, and hides it for stealth purposes. This is likely used for storing data or maintaining persistence without detection.

    \AppData\Local\e9d3e2dd2788c322ffd2c9defddf7728 random directory is created in hidden attribute.

    BoT Registration

    Fig 19: BOT Registration

    The RegisterBot method initiates an HTTP POST request to register a bot instance, utilizing a unique hash identifier and an authorization token for authentication. It serializes the registration payload, appends the necessary HTTP headers, and logs the server response or any encountered exceptions. The method returns a boolean value—true upon successful execution, and false if an exception is raised during the process.

    RequestUri: ‘http[:]//91[.]211[.]249[.]142:7816/api/bot/v1/register’

     

    Stealer Activity From Browser:

    Fig 20: Stealer activity from Browser

    It extracts browser-related data (passwords, cookies, credit cards, history, bookmarks, autofill) from a given user data profile path.

    FileZilla Credentials stealer activity

    Fig 21: FileZilla Credential Stealer activity

    The above code is part of a password-stealing component targeting FileZilla, an FTP client.

    Gaming Platform Data Extraction Modules

    Fig 22: Gaming platform data extraction

    This component under bt.Stub.Target.Gaming is designed to collect data from the following platforms:

    • BattleNet
    • Minecraft
    • Steam
    • Uplay

    Each class likely implements routines to extract user data, game configurations, or sensitive files for exfiltration.

    Fig 23: Checks for a Minecraft installation

    It checks for a Minecraft installation and creates a save directory to exfiltrate various data like mods, files, versions, logs, and screenshots. It conditionally captures logs and screenshots based on the Config.GrabberModule setting.

    Messenger Data Stealer Modules

    Itargets various communication platforms to extract user data or credentials from:

    • Discord
    • Element
    • ICQ
    • Outlook
    • Pidgin
    • Signal
    • Skype
    • Telegram
    • Tox

    Below is one example of Outlook Credentials Harvesting

    It targets specific registry keys associated with Outlook profiles to extract sensitive information like email addresses, server names, usernames, and passwords. It gathers data for multiple mail clients (SMTP, POP3, IMAP) and writes the collected information to a file (Outlook.txt).

    Fig 24: Messenger Data Extraction

     

    Webcam Screenshot Capture

    Attempts to take a screenshot using a connected webcam, saving the image as a JPEG file. If only one camera is connected, it triggers a series of messages to capture the webcam image, which is then saved to the specified path (camera.jpg or a timestamped filename). The method is controlled by a configuration setting (Config.WebcamScreenshot).

     

    Fig 25: Webcam Screen shot captures

     

    Wi-Fi Password Retrieval

     

    It retrieves the Wi-Fi password for a given network profile by running the command netsh wlan show profile and extracting the password from the output. The command uses findstr Key to filter the password, which is then split and trimmed to get the value

     

    Fig 26: WI-FI Password Retrieval

     

    VPN Data Extraction

    It targets various VPN applications to exfiltrate sensitive information such as login credentials:

    • NordVpn
    • OpenVpn
    • ProtonVpn

    For example, it  extracts and saves NordVPN credentials from the user.config file found in NordVPN installation directories. It looks for “Username” and “Password” settings, decodes them, and writes them to a file (accounts.txt) in the specified savePath.

     

    Fig 27: VPN Data Extraction

     

    Porn Detection & Screenshot Capture

    Fig 28: Porn Detection & Snapshot Captures.

    It detects adult content by checking if the active window’s title contains specific keywords related to NSFW content (configured in Config.PornServices). If such content is detected, it triggers a screenshot capture.

    Conclusion:

    Based on our recent proactive threat analysis, we’ve identified that cybercriminals are actively targeting U.S. citizens around the tax filing period scheduled for April 15. These threat actors are leveraging the occasion to deploy Stealerium malware, using deceptive tactics to trick users.

    Stealerium malware is designed to steal Personally Identifiable Information (PII) from infected devices and transmit it to attacker-controlled bots for further exploitation.

    To safeguard your data and devices, we strongly recommend using Seqrite Endpoint Security, which provides advanced protection against such evolving threats.

    Stay secure. Stay protected with Seqrite.

    TTPS

    Tactic Technique ID Name
    Initial Access T1566.001 Phishing: Spear phishing Attachment
    Execution T1059.001 Command and Scripting Interpreter: PowerShell
    Evasion T1140 Deobfuscate/Decode Files or Information
    T1027 Obfuscated Files or Information
    T1497 Virtualization/Sandbox Evasion
    T1497.001 System Checks
    Credential Access T1555.003 Credentials from Password Stores:  Credentials from Web Browsers

     

    T1539 Steal Web Session Cookie
    Discovery T1217 Browser Information Discovery
    T1016 System Network Configuration Discovery: Wi-Fi Discovery
    Collection T1113 Screen Capture
    Exfiltration T1567.004 Exfiltration Over Web Service:  Exfiltration Over Webhook

     

    Seqrite Protections:

    • HEUR:Trojan.Win32.PH
    • Trojan.49490.GC
    • trojan.49489.GC

    IoCs:

    File Name SHA-256
    Setup.exe/revolaomt.exe 6a9889fee93128a9cdcb93d35a2fec9c6127905d14c0ceed14f5f1c4f58542b8
    104842599782-4.pdf.lnk 48328ce3a4b2c2413acb87a4d1f8c3b7238db826f313a25173ad5ad34632d9d7
    payload_1.ps1 / fgrsdt_rev_hx4_ln_x.txt 10f217c72f62aed40957c438b865f0bcebc7e42a5e947051edee1649adf0cbf2
    revolaomt.rar 31705d906058e7324027e65ce7f4f7a30bcf6c30571aa3f020e91678a22a835a
    104842599782-4.html Ff5e3e3bf67d292c73491fab0d94533a712c2935bb4a9135546ca4a416ba8ca1

     

    C2:

    • hxxp[:]//91[.]211[.]249[.]142:7816/
    • hxxp://91.211.249.142:7816″
    • hxxp[:]//185[.]237[.]165[.]230/

     

    Authors:

    Dixit Panchal
    Kartik Jivani
    Soumen Burma



    Source link

  • How To Convert A List To A String In Python (With Examples)



    How To Convert A List To A String In Python (With Examples)



    Source link

  • JavaScript Location.reload() Explained (With Examples)

    JavaScript Location.reload() Explained (With Examples)


    In modern web development, there are times when a page needs to refresh itself without the user pressing a button. Whether you are responding to updated content, clearing form inputs, or forcing a session reset, JavaScript provides a simple method for this task: location.reload().

    This built-in method belongs to the window.location object and allows developers to programmatically reload the current web page. It is a concise and effective way to refresh a page under controlled conditions, without relying on user interaction.

    What Is JavaScript location.reload()?

    The location.reload() method refreshes the page it is called on. In essence, it behaves the same way a user would if they clicked the browser’s reload button. However, because it is called with JavaScript, the action can be triggered automatically or in response to specific events. 

    Here is the most basic usage:

    location.reload();

    This line of code tells the browser to reload the current page. It does not require any parameters by default and typically loads the page from the browser’s cache. Note that you can use our free resources (namely, online code editors) to follow along with this discussion.

    Forcing a Hard Reload

    Sometimes a regular reload is not enough, especially when you want to ensure that the browser fetches the latest version of the file from the server instead of using the cached copy. You can force a hard reload by passing true as a parameter:

    location.reload(true);

    However, it is important to note that modern browsers have deprecated this parameter in many cases. Instead, they treat all reloads the same. If you need to fully bypass the cache, server-side headers or a versioned URL might be a more reliable approach.

    And let’s talk syntax:

    So what about the false parameter? That reloads the page using the web browser cache. Note that false is also the default parameter. So if you run reload() without a parameter, you’re actually running object.reload(false). This is covered in the Mozilla developer docs.

    So when do you use Location.reload(true)? One common situation is when the page has outdated information. A hard reload can also bypass caching issues on the client side.

    Common Use Cases

    The location.reload() method is used across a wide range of situations. Here are a few specific scenarios where it’s especially useful:

    1. Reload after a form submission:

    document.getElementById("myForm").onsubmit = function() {
        setTimeout(function() {
            location.reload();
        }, 1000);
    };

    This use case helps clear form inputs or reset the page state after the form has been processed. You can test this in the online Javascript editor. No download required. Just enter the code and click run to immediately see how it looks.

    2. Refresh after receiving new data:

    In web applications that rely on live data, such as dashboards or status monitors, developers might use location.reload() to ensure the page displays the most current information after an update.

    3. Making a manual refresh button:

    <button onclick="location.reload();">Refresh Page</button>

    This is a simple way to give users control over when to reload, particularly in apps that fetch new content periodically.

    4. Reload a Page Without Keeping the Current Page in Session History

    This is another common use. It looks like this.

    window.location.replace(window.location.href);

    Basically, if a user presses the back button after they hit reload, they might be taken back to a page that no longer reflects the current application logic. The widow.location.replace() method navigates to a new URL, often the same one, and replaces the current page in the session history.

    This effectively reloads the page without leaving a trace in the user’s history stack. It is particularly useful for login redirects, post-submission screens, or any scenario where you want to reset the page without allowing users to revisit the previous state using the back button.

    Limitations and Best Practices

    While location.reload() is useful; it should be used thoughtfully. Frequent or automatic reloads can frustrate users, especially if they disrupt input or navigation. In modern development, reloading an entire page is sometimes considered a heavy-handed approach.

    For dynamic updates, using JavaScript to update only part of the page, through DOM manipulation or asynchronous fetch requests, is often more efficient and user-friendly.

    Also, keep in mind that reloading clears unsaved user input and resets page state. It can also cause data to be resubmitted if the page was loaded through a form POST, which may trigger browser warnings or duplicate actions. If you’re looking for a job, make sure to brush up on this and any other common JavaScript interview questions.

    Smarter Alternatives to Reloading the Page

    While location.reload() is simple and effective, it is often more efficient to update only part of a page rather than reloading the entire thing. Reloading can interrupt the user experience, clear form inputs, and lead to unnecessary data usage. In many cases, developers turn to asynchronous techniques that allow content to be refreshed behind the scenes.

    AJAX, which stands for Asynchronous JavaScript and XML, was one of the earliest ways to perform background data transfers without refreshing the page. It allows a web page to send or receive data from a server and update only the necessary parts of the interface. Although the term AJAX often brings to mind older syntax and XML data formats, the concept remains vital and is now commonly used with JSON and modern JavaScript methods.

    One of the most popular modern approaches is the Fetch API. Introduced as a cleaner and more flexible alternative to XMLHttpRequest, the Fetch API uses promises to handle asynchronous requests. It allows developers to retrieve or send data from a server and then apply those updates directly to the page using the Document Object Model, or DOM.

    Here is a simple example:

    fetch('/api/data')
      .then(response => response.json())
      .then(data => {
        document.getElementById('content').textContent = data.message;
      });

    This example retrieves data from the server and updates only a single element on the page. It is fast, efficient, and keeps the user interface responsive.

    By using AJAX or the Fetch API, developers can create a more fluid and interactive experience. These tools allow for partial updates, background syncing, and real-time features without forcing users to wait for an entire page to reload. In a world where performance and responsiveness matter more than ever, these alternatives offer a more refined approach to managing content updates on the web.

    Conclusion

    The location.reload() method in JavaScript is a straightforward way to refresh the current web page. Whether used for resetting the interface or updating content, it offers a quick and accessible solution for common front-end challenges. But like all tools in web development, it should be used with an understanding of its impact on user experience.

    Before reaching for a full page reload, consider whether updating the page’s content directly might serve your users better. When applied appropriately, location.reload() can be a useful addition to your JavaScript toolkit.

    Want to put this into action? Add it to a JavaScript project and test it out.

     





    Source link

  • HTML Editor Online with Instant Preview and Zero Setup



    HTML Editor Online with Instant Preview and Zero Setup



    Source link

  • Write and Test Code Instantly With an Online Python Editor



    Write and Test Code Instantly With an Online Python Editor



    Source link

  • Kimsuky APT Targets South Korea with Deceptive PDF Lures

    Kimsuky APT Targets South Korea with Deceptive PDF Lures


    Kimsuky: A Continuous Threat to South Korea with Deceptive Tactics

    Contents

    • Introduction
    • Infection Chain
    • Initial Findings
    • Campaign 1
      • Looking into PDF document.
    • Campaign 2
      • Looking into PDF document.
    • Technical Analysis
    • Conclusion
    • Seqrite Protection
    • MITRE ATT&CK
    • IOCs

    Introduction:

    Security researchers at Seqrite Labs have recently uncovered two distinct campaigns carried out by the APT group “Kimsuky,” also known as “Black Banshee.” This group has been actively targeting South Korea using evolving tactics. In these campaigns, the threat actors delivered two South Korean government-themed documents as lures, specifically targeting government entities within South Korea.

    In this blog, we will delve into the technical details of the campaigns uncovered during our analysis. We will examine the various stages of infection, starting with a phishing email containing an LNK (shortcut) file attachment. The LNK file was designed to drop an obfuscated VBA (Visual Basic for Applications) script, After de-obfuscating the script, we found that it was responsible for dropping two additional files: One Pdf file and One ZIP file The ZIP file contained four malicious files: two log files (1.log and 2.log), one VBA script (1.vba), and one PowerShell script (1.ps1). Both campaigns involved the same set of malicious files.

    Infection Chain:

    Fig .1 infection chain

    Initial Findings:

    Campaign-1:

    In the first campaign, we identified a document related to tax reduction and tax payment related to revenue, which contained the same malicious LNK attachment. This attachment subsequently deployed a malicious VBScript, facilitating further compromise.

     

    Fig .2 Revanue.pdf file

     

    Based on our initial findings, we discovered that the adversary utilized a different document containing the same LNK file content.

    Campaign-2:

    In campaign-2, it has come to our attention that South Korea has enacted a new policy aimed at preventing recidivism among sex offenders. The initiative involves circulating a detailed document outlining the regulations, which was shared with households, daycare centers, kindergartens, and various local administrative offices, including township and village authorities, as well as neighbourhood community centres. However, hackers, including cyber-criminals, are exploiting this dissemination process by sending deceptive emails containing harmful attachments. These emails are targeting residential recipients and key personnel at local offices.

     

    Fig .3 Sex Offender Personal Information Notification.pdf

     

    The adversaries have exploited the distribution of this information and document by circulating it via email, disguised under the filename 성범죄자 신상정보 고지.pdf.lnk (Sex Offender Personal Information Notification.pdf.lnk). This attachment contains a malicious LNK file, which poses a cybersecurity threat to the recipients.

     

    Technical Analysis and Methodology:

    Campaign 1 & 2:

    We have downloaded the file named 28f2fcece68822c38e72310c911ef007f8bd8fd711f2080844f666b7f371e9e1.lnk from campaign-1 and “성범죄자 신상정보 고지.pdf.lnk” from campaign-2 (Sex Offender Personal Information Notification.pdf.lnk) that was shared via email. During the analysis of this LNK file, it appears to be fetching additional files from an external C2 server, as shown in the snapshot below.

    Fig.4 Downloading VBScript from C2 (Campaign –1)

     

    Fig .5 Downloading VBScript From C2 (Campaign -2)

    The file was downloaded from the URL provided above and saved into the Temp folder, as indicated below.

    Fig .6 downloaded into Temp Folder (Campaign-1)

     

    Fig .7 downloaded into Temp Folder (Campaign-2)

    The file downloaded from the C2 server appears to be an obfuscated VBScript. Upon DE obfuscating the script, we discovered two additional files: one PDF and one ZIP file.

    Fig .8 Obfuscated VB Script

    The first section of the file is encoded in Base64 strings.

    Fig .9 Base64 Encoded PDF

    After Decoding we have found one PDF file.

     

    Fig .10 PDF after Decoding

     

    The second part of the VBScript is also encoded in Base64. After decoding it, we discovered a ZIP file.

    Fig .11 Zip File

     

    Fig. 12 Detect It Easy

    Zip files contain the below numbers of files in it.

    Fig .13 Inside Zip File

    Within the ZIP archive, four files were identified: a VBScript, a PowerShell script, and two Base64-encoded text files. These encoded text files house obfuscated data, which, upon further dissection, may yield critical intelligence regarding the malware’s functionality and objectives. The following figures illustrate the encoded content of the two text files, which will be subsequently decoded and analysed to elucidate the next phase in the attack chain.

    Fig. 14- 1 Log.txt file with Base64 encoding

     

    Fig.15 – 2 Log .txt file with Base64 encoding

    The 1.vbs file employs advanced obfuscation techniques, utilizing the chr() and CLng() functions to dynamically construct characters and invoke commands at runtime. This strategy effectively circumvents signature-based detection mechanisms, allowing the script to evade detection during execution.

    Upon script termination, the concatenated characters form a complete command, which is subsequently executed. This command is likely designed to invoke the 1.ps1 PowerShell script, passing 1.log as an argument for further processing.

    Fig .16 – 1.vbs

    Upon attempting to DE-obfuscate the VBScript, we uncovered the following command-line execution, which subsequently triggers the PowerShell script for further processing.

    Fig .17  De-Obfuscated VB Script

    Upon executing the 1.vbs file, it triggered the invocation of the 1.ps1 file, as illustrated in the snapshot below.

    Fig .18 Executing 1.VBS

    The 1.ps1 script includes a function designed to decode Base64-encoded data from the 1.log file and execute the resulting script.

    Fig.19 – 1.ps1 file

     

    Fig.20 – 1 Log.txt after decoding

    The 1.ps1 script retrieves the BIOS serial number, a unique system identifier, from the compromised host. This serial number is subsequently used to create a dedicated directory within the system’s temporary folder, ensuring that attack-related files are stored in a location specific to the compromised machine, as shown in above snapshot.

    As a VM-aware sample, the script checks if it is executing within a virtual machine environment. If it detects a virtual machine, it will delete all four files associated with the attack (1.vbs, 1.ps1, 1.log, and any payload files stored in the directory named after the serial number), effectively halting its execution, as illustrated.

    The script encompasses 11 functions that define the subsequent phases of the malware’s operation, which include data exfiltration, cryptocurrency wallet information theft, and the establishment of Command-and-Control (C2) communications. These functions are integral to the attack’s execution, facilitating the malware’s objectives and ensuring persistent communication with the threat actor.

    List of malicious function retrieved from 1 log file:

    1. UploadFile ():

    The upload function exfiltrates data by transmitting it to the server in 1MB chunks, allowing it to handle large file sizes efficiently. The script awaits a response from the server, and if it receives an HTTP status code of “200,” it proceeds with further execution. If the response differs, the script terminates its operation. Each chunk is sent via an HTTP POST request, with the function verifying the success of each upload iteration before continuing.

    Fig .21 UploadFile()

     

    1. GetExWFile ():

    The GetExWFile function iterates through a set of predefined hash tables containing cryptocurrency wallet extensions. When a match is found, it identifies the associated”.ldb” and ”.log” files linked to those extensions for exfiltration. These files are subsequently transferred to the specified destination folder, as indicated by the $Storepath variable.

    Fig.22 GetExWFile ()
    1. GetBrowserData ():

    The script checks whether any of the following browsers—Edge, Firefox, Chrome, or Naver Whale—are actively running, to extract user profile data, including cookies, login credentials, bookmarks, and web data. Prior to collecting this information, the script terminates the browser processes to ensure uninterrupted access. It then proceeds to retrieve data on installed extensions and cache files, such as webcacheV01.dat, for each identified browser. For certain browsers, it also performs decryption operations to unlock encrypted keys, allowing it to extract sensitive information, which is then stored alongside the decrypted master encryption key.

    Fig.23 BrowserData ()
    1. Download file () :

    The download file function downloads any file based on the C2 command.

    Fig.24 Download File ()
    1. RegisterTask () :

    It creates persistence for the files “1.log” and “1.vbs”.

    Fig.25 RegisterTask()
    1. Send ():

    The send () function uploads all the collected information to the server after compressing the data into a ZIP file named “init.zip”. It then renames the ZIP file to “init.dat” and deletes all backup files from the system after uploading.

    Fig.26 Send ()

    The execution flow of the functions follows a sequence where several actions are carried out within the attack. Among these functions, one triggers another PowerShell command that calls the 2.log file, which is responsible for performing keylogging activities.

     

    Fig. 27 Flow of execution of functions and command to execute “2.log”.
    Fig.28 Executing 2 log file

     

    Fig.29 Inside 2 log file

     

    The decoded content of the 2.log file is shown above. It contains a script that imports essential Windows API functions for detecting key presses, retrieving window titles, and managing keyboard states. The script executes actions such as clipboard monitoring, keystroke logging, and recording window titles.

    Fig. 30.2 Code for clipboard monitoring.

    Conclusion

    As observed, threat actors are utilizing time-consuming, multi-component techniques that are interlinked to enhance their evasiveness. Unlike other stealers, this one primarily focuses on network-related information, which could be leveraged for active reconnaissance. Given that the stealer targets sensitive user data, it is crucial to protect yourself with a reputable security solution such as Seqrite Antivirus in today’s digital landscape. At Seqrite Lab, we provide detection capabilities for such stealers at various stages of infection, along with protection against the latest threats.

    Seqrite Protection:

    • Trojan.49424.SL
    • Trojan.49422.C

     

    MITRE ATT&CK:

    Initial Access T1566.001 Phishing: Spearphishing Attachment
    Execution T1059.001

     

    T1059.005

    Command and Scripting Interpreter: PowerShell

    Command and Scripting Interpreter: Visual Basic

    Persistence T1547.001 Boot or Logon Autostart Execution: Registry Run Keys / Startup Folder
    Defense Evasion T1140 Deobfuscate/Decode Files or Information
    Credential Access T1555.003 Credentials from Password Stores: Credentials from Web Browsers
    Discovery T1082 System Information Discovery
    Collection T1056.001 Input Capture: Keylogging
    Command and Control T1071.001 Application Layer Protocol: Web Protocols
    Exfiltration T1041 Exfiltration Over C2 Channel

    IoCs:

    MD5  File Name
    1119A977A925CA17B554DCED2CBABD8  *.lnk
    64677CAE14A2EC4D393A81548417B61B  1.log
    F0F63808E17994E91FD397E3A54A80CB  2.log
    A3353EA094F45915408065D03AE157C4  prevenue.hta
    CE4549607E46E656D8E019624D5036C1  1.vbs
    1B90EFF0B4F54DA72B19195489C3AF6C  *.lnk
    1D64508B384E928046887DD9CB32C2AC 성범죄자 신상정보 고지.pdf.lnk

    C2

    • hxxps[:]//cdn[.]glitch[.]global/
    • hxxp[:]//srvdown[.]ddns.net

     

    Authors

    Dixit Panchal

    Kartik Jivani

    Soumen Burma

     

     



    Source link

  • Mastering Carousels with GSAP: From Basics to Advanced Animation

    Mastering Carousels with GSAP: From Basics to Advanced Animation


    Carousels are a fairly common UI pattern (there are many excellent carousel and slider examples available on Codrops). While carousel designs vary depending on the use case, the following demos explore how the GreenSock Animation Platform (GSAP) can be used to achieve seamless looping, smooth animations, and ultimately, a better user experience.

    This article is for frontend designers and developers interested in enhancing the functionality and visual appeal of a standard horizontal carousel. Familiarity with JavaScript and basic GSAP methods will be helpful, but anyone looking for inspiration and practical examples may find the following content useful.

    What You’ll Learn

    • Basic carousel implementation using HTML and CSS
    • How to use gsap.utils.wrap() and horizontalLoop()
    • Advanced animation techniques, including image parallax and function-based values

    Our Basic Carousel

    Let’s start with a horizontally scrolling carousel using only HTML and CSS:

    <div class="carousel">
        
        <div class="carousel-slide">
          <img src="https://images.unsplash.com/photo-1659733582156-d2a11801e59f?q=50&w=1600">
          <h2>We're No</h2>
          <h5>Strangers to love</h5>
        </div>
            
        ...
    
    </div>
    .carousel {
      width: 100vw;
      height: 80vh;
      gap: 10px;
      overflow-x: auto;
      scroll-snap-type: x mandatory;
      display: flex;
      -webkit-overflow-scrolling: touch;
    }
    
    .carousel-slide {
      position: relative;
      flex: 0 0 50%;
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      color: white;
      scroll-snap-align: center;
      overflow: hidden;
    }
    
    .carousel-slide img {
      position: absolute;
      width: 100%;
      height: 100%;
      object-fit: cover;
    }
    
    h2 {
      position: relative;
      margin: 0;
      font-size: 1.8rem;
    }
    
    h5 {
      position: relative;
      margin: 2% 0 0 0;
      font-size: 1rem;
      font-weight: 100;
      letter-spacing: 0.3px;
    }
    
    /* Simplify the scroll bar appearance */
    ::-webkit-scrollbar {
      height: 13px;
    }
    
    ::-webkit-scrollbar-track {
      background: transparent;
    }
    
    ::-webkit-scrollbar-thumb {
      border-top: 6px solid #000;
      background: #555;
      width: 50%;
    }
    
    ::-webkit-scrollbar-thumb:hover {
      background: #bbb;
    }
    
    @media (max-width: 500px) {
      .carousel-slide {
        flex: 0 0 80%;
      }
    
      ::-webkit-scrollbar-thumb {
        width: 80%;
      }
    }

    Here’s the result:

    It uses scroll snapping and some custom styling on the scrollbar. Nothing fancy, but it works even when JavaScript is disabled.

    Note that the HTML above is intentionally concise. However, in production, it’s important to follow accessibility best practices, including using alt text on images and descriptive ARIA attributes for screen reader users.

    Building on the Foundation – GSAP Demo 1A

    To see how GSAP can enhance a carousel, we’ll explore two different approaches—the first using gsap.utils.wrap(). Wrap is one of several handy utility methods included in gsap.js—no plugin required! Given a min/max range, it returns a value within that range:

     gsap.utils.wrap(5, 10, 12); // min 5, max 10, value to wrap 12: returns 7

    The example above returns 7 because 12 is 2 more than the maximum of 10, so it wraps around to the start and moves 2 steps forward from there. In a carousel, this can be used to loop infinitely through the slides.

    Here’s a simple demo of how it can be applied:

    In the HTML, a <nav> block has been added that contains previous/next buttons and progress text:

    <nav class="carousel-nav">
      <button class="prev" tabindex="0" aria-label="Previous Slide"></button>
      <button class="next" tabindex="0" aria-label="Next Slide"></button>
      <div>1/8</div>
    </nav>

    A few new rules have been added to the CSS, most importantly to .carousel-slide-abs:

    .carousel-slide-abs {
      position: absolute;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
      width: 75vw;
      height: 70vh;
    }

    In the JS, we override the carousel’s scroll-snap-type and display the <nav> block. Since we no longer have a scrollable area, the buttons are necessary to maintain keyboard accessibility. Safari requires tabindex="0" to allow users to tab to them. Additionally, aria-labels are important since the buttons have no visible text content.

    We apply the new class to each slide, which effectively stacks them all in the center. We also set the initial opacity: 1 for the first slide and 0 for the rest:

    gsap.set(".carousel", { "scroll-snap-type": "none" });
    
    gsap.set(".carousel-nav", { display: "block" });
    
    slides.forEach((slide, i) => {
      slide.classList.add("carousel-slide-abs");
      gsap.set(slide, { opacity: (i === 0 ? 1 : 0) });
    });

    Next, we need a function that transitions to the previous or next slide. changeSlide() is passed a direction parameter of either positive or negative 1. Inside this function, we:

    1. Fade out the current slide
    2. Update the current slide index using gsap.utils.wrap()
    3. Fade in the new current slide
    4. Update the progress text

    The different easing on the outro and intro tweens helps prevent excessive overlapping opacity during the crossfade.

    next.addEventListener("click", () => changeSlide( 1 ));
    prev.addEventListener("click", () => changeSlide( -1 ));
    
    function changeSlide( dir ) {
      
      gsap.to(slides[currentIndex], { opacity: 0, ease: "power3" });
      
      currentIndex = gsap.utils.wrap(0, slides.length, (currentIndex += dir));
      
      gsap.to(slides[currentIndex], { opacity: 1, ease: "power3.inOut" });
      
      gsap.set(".carousel-nav div", { innerText: `${currentIndex + 1}/${slides.length}` });
    
    }

    Polishing the Transition – GSAP Demo 1B

    To take this idea further, let’s add more detail to the outro and intro animations:

    For the 3D perspective to work, we’ve added perspective: 750px to .carousel-slide-abs in the CSS.

    Instead of targeting the slides themselves, we set the opacity of their child elements to 0—except for those in the first slide.

     gsap.set(slide.children, { opacity: (i === 0 ? 1 : 0) });

    Then, we do the following inside changeSlide():

    1. Store a reference to the outgoing slide’s children
    2. Update currentIndex, just as before
    3. Create a const for the incoming slide’s children
    4. Kill tweens on both slides’ children to prevent conflicts if slides change rapidly
    5. Create a timeline for the transition:
    gsap.timeline({ defaults:{ ease: "expo" } })
      // update progress text
      .set(".carousel-nav div", { innerText: `${currentIndex + 1}/${slides.length}` })
    
      // old slide outro
      .to(oldLayers[0], {
        duration: 0.3,
        rotateY: (dir<0 ? -75 : 75),
        scale: 0.6,
        ease: "power2.in"
      }, 0)
      .to(oldLayers, {
        duration: 0.3,
        opacity: 0,
        ease: "power2.in"
      }, 0)
    
      // new slide intro
      .to(newLayers, {
        opacity: 1,
        ease: "power1.inOut",
        stagger: 0.2
      }, 0.2)
      .fromTo(newLayers[0], {
        rotateY: (dir<0 ? 90 : -90),
        scale: 0.6
      },{
        rotateY: 0,
        scale: 1
      }, 0.3)
      .fromTo([newLayers[1], newLayers[2]], {
        y: 35
      },{
        duration: 1,
        y: 0,
        stagger: 0.14
      }, 0.4);

    Easing and staggers help smooth out and space the movement. The dir parameter modifies the rotationY, adding a subtly unique motion to previous and next actions.

    This basic setup can be easily customized further. Animating a clip-path, applying a blur filter, or experimenting with additional 3D transforms could all produce interesting results.

    A Different Approach – GSAP Demo 2A

    Another way to create a seamless looping carousel with GSAP is to use the horizontalLoop() helper function. Although GSAP helper functions aren’t officially part of the core library, they’re a handy collection of code snippets and shortcuts. They also serve as great learning resources for writing more advanced GSAP code.

    This specific helper function animates elements along their x-axis and repositions them once they’re out of view to create an infinite loop. Here’s a basic implementation:

    Again, we override the CSS and display the <nav> element. Then we call horizontalLoop(), which takes two parameters: an array of the carousel slides and a config object for setting various options.

    const loop = horizontalLoop(slides, {
      paused: true,       // no auto-scroll
      paddingRight: 10,   // match the 10px flex gap
      center: true,       // snap the active slide to the center
      onChange: (slide, index) => { // called when the active slide changes
        if (activeSlide) {
          gsap.to(".active", { opacity: 0.3 });
          activeSlide.classList.remove("active");
        }
        slide.classList.add("active");
        activeSlide = slide;
        gsap.to(".active", { opacity: 1, ease: "power2.inOut" });
        gsap.set(".carousel-nav div", { innerText: `${index + 1}/${slides.length}` });
      }
    });

    The most notable of these options is the onChange callback, where we can write code that executes each time the active slide changes. In this example, we’re removing and adding the “active” class name and tweening the opacity to draw more focus to the center slide.

    The helper function returns a timeline with several useful added methods, including next(), previous(), and toIndex(). We’ll use these to add navigation functionality to our previous/next buttons, as well as to the individual slides:

    next.addEventListener("click", () => loop.next({ duration: 1, ease: "expo" }));
    prev.addEventListener("click", () => loop.previous({ duration: 1, ease: "expo" }));
    
    // each slide can function as a button to activate itself
    slides.forEach((slide, i) => {
      slide.addEventListener("click", () => loop.toIndex(i, {duration: 1, ease: "expo"}))
    });

    Finally, we set the initial carousel state by adjusting the opacity of each slide and calling toIndex() with no tween duration, which centers the active slide.

    gsap.set(".carousel-slide", { opacity: (i) => (i === 0 ? 1 : 0.3) });
    
    loop.toIndex(0, { duration: 0 }); 

    If you’re unfamiliar with function-based values in GSAP, this is an amazing feature—definitely check out that link to learn how they work. Here, we’re iterating through each element with the class name “carousel-slide,” returning an opacity value of 1 for the first slide and 0.3 for the rest.

    The remainder of the JS is just the helper function, copied and pasted from the GSAP docs demo. In most cases, you won’t need to modify anything inside it. (We’ll look at an exception in Demo 2C.)

    Add Draggable & InertiaPlugin – GSAP Demo 2B

    To make the carousel move on drag, we’ll need two plugins: Draggable and the Inertia Plugin. Once those scripts are included, you can set draggable: true in the config object.

    In addition to drag behavior, this iteration includes some text animation, with logic to prevent it from running on the first load (plus hover in/out animations on the nav buttons).

    onChange: (slide, index) => { // called when the active slide changes
      if (activeSlide) {
        gsap.to(".carousel h2, .carousel h5", { overwrite: true, opacity: 0, ease: "power3" });
        gsap.to(".active", { opacity: 0.3 });
        activeSlide.classList.remove("active");
      }
      slide.classList.add("active");
      activeSlide = slide;
      
      // intro animation for new active slide
      gsap.timeline({ defaults:{ ease:"power1.inOut" } })
    
        // fade in the new active slide
        .to(".active", { opacity: 1, ease: "power2.inOut" }, 0)
    
        // fade out the progress text, change its value, fade it back in
        .to(".carousel-nav div", { duration: 0.2, opacity: 0, ease: "power1.in" }, 0)
        .set(".carousel-nav div", { innerText: `${index + 1}/${slides.length}` }, 0.2)
        .to(".carousel-nav div", { duration: 0.4, opacity: 0.5, ease: "power1.inOut" }, 0.2)
    
        // fade in the text elements and translate them vertically
        .to(".active h2, .active h5", { opacity: 1, ease: "power1.inOut" }, 0.3)
        .fromTo(".active h2, .active h5", { y:(i)=>[40,60][i] },{ duration: 1.5, y: 0, ease: "expo" }, 0.3)
    
        // skip active slide animation on first run
        .progress( firstRun? 1: 0 )
    }

    Adding Parallax – GSAP Demo 2C

    To make the movement more engaging, let’s calculate each slide’s horizontal progress and use it to create a parallax effect.

    Until now, we haven’t modified the helper function. However, to calculate slide progress, this version includes one change inside horizontalLoop().

    Now, every time the carousel timeline updates, slideImgUpdate() is called. This function sets each image’s xPercent based on the progress of its parent slide. Progress is 0 when the slide is offstage to the left, and 1 when it’s offstage to the right.

    function slideImgUpdate(){
      slides.forEach( slide => {
        const rect = slide.getBoundingClientRect();
        const prog = gsap.utils.mapRange(-rect.width, innerWidth, 0, 1, rect.x);
        const val = gsap.utils.clamp(0, 1, prog );
        gsap.set(slide.querySelector("img"), {
          xPercent: gsap.utils.interpolate(0, -50, val)
        });
      });
    }

    GSAP utility functions mapRange(), interpolate(), and clamp() make the progress calculation much easier. Note, in the CSS, the width of .carousel-slide img is increased to 150%, so there will be enough image for a 50% horizontal movement.

    Taking It Further

    There are endless ways you could build on these demos, customizing both appearance and functionality. A few ideas include:

    • Modify how many slides are shown at once—a single, full-frame version could be interesting, as could several smaller slides to create a cover flow effect. In both of those examples, the progress indicator also became a fun area for experimentation.
    • Additional details could be added by calling custom functions inside the helper function’s onPress, onRelease, or onThrowComplete callbacks. Here’s one more iteration on Demo 2, where the entire carousel shrinks while the pointer is held down.
    • The carousel could even serve as navigation for a separate animated page element, like on Nite Riot.
    • If you want the carousel to respond to mouse wheel movements, GSAP’s Observer plugin offers an easy way to handle those events.
    • With GSAP’s matchMedia(), you can specify different animations for various viewport widths and tailor behavior for users who prefer reduced motion.



    Source link