Change file during build

I would like to implement CopyWebpackPlugin from Webpack in Vite configuration.

This should amend JSON file properties to another from imported files.

    new CopyWebpackPlugin({
      patterns: [
        {
          from: "src/manifest.json",
          to: path.join(__dirname, "build"),
          force: true,
          transform(content, _path) {
            // generates the manifest file using the package.json informations
            return Buffer.from(
              JSON.stringify({
                version: pkg.version,
                description: pkg.description,
                homepage_url: pkg.repository.url,
                background: isChrome
                  ? { service_worker: backgroundPageName }
                  : { page: backgroundPageName },
                ...JSON.parse(content.toString()),
              }),
            );
          },
        },
      ],
    }),

Is it possible to have it implemented without writing my own plugin?

import { resolve } from "path";
import { defineConfig } from "vite";

const root = __dirname;

type ResourceExtType = "html" | "ts";

function getPathToResource(name: string, ext: ResourceExtType = "html") {
  return resolve(root, "src", "pages", name, `index.${ext}`);
}

export default defineConfig({
  root,
  build: {
    outDir: "dist",
    emptyOutDir: true,
    rollupOptions: {
      input: {
        popup: getPathToResource("popup"),
        options: getPathToResource("options"),
        devtools: getPathToResource("devtools"),
        newtab: getPathToResource("newtab"),
        panel: getPathToResource("panel"),
        content: getPathToResource("content", "ts"),
      },
    },
  },
});

I checked documentation, but nothing found. Searched for plugins, but found only https://www.npmjs.com/package/vite-plugin-static-copy, which is not suitable for me, as I am using public folder.

How to display custom pages based on email validation with Clerk authentication in a Next.js application?

I’m working on a Next.js application using Clerk for user authentication. I need to implement a feature where users are redirected to different pages based on whether their Google account email is registered in our database. If the email exists, they should be redirected to a custom “Select Account” page. If the email does not exist, they should be redirected to an “Account Not Registered” page.

Here is the code snippet where I’m handling the sign-in:

import { useClerk, useSignIn } from "@clerk/nextjs";

const { isLoaded, signIn } = useSignIn();

async function handleSignIn(strategy) {
  await signIn.authenticateWithRedirect({
    strategy,
    redirectUrl: "/login",
    redirectUrlComplete: "/login",
  });
}

I am unsure where to place the logic to check if the user’s email exists in our database and how to redirect them accordingly. Should this logic be placed in the redirectUrlComplete callback, or is there a better approach to handle this within the Clerk framework?

Any guidance or examples would be greatly appreciated!

Leaflet-GPX plugin not finding start point despite successful GPX load

I’m working on a web application that displays GPX routes on a map using Leaflet and the Leaflet-GPX plugin. I’m encountering an issue where the get_start_point() method is not available on the GPX object, despite the GPX file seemingly loading successfully.
Here’s the relevant part of my code:

new L.GPX(gpxData, {
    async: true,
    // ... marker options ...
}).on('loaded', function(e) {
    var gpx = e.target;
    console.log('GPX object:', gpx);
    console.log('GPX methods:', Object.keys(gpx));
    console.log('GPX layers:', gpx._layers);

    // ... other code ...

    var startPoint = gpx.get_start_point(); // This line throws an error
    
    if (startPoint) {
        // Handle start point
    } else {
        console.error('Could not find start point in GPX data');
    }

    console.log('GPX loaded and bounds set');
}).addTo(map);

Here are the console error log I’m seeing:

Uncaught TypeError: gpx.get_start_point is not a function 
 at https://cdpn.io/cpe/boomboom/pen.js?key=pen.js-cde078dc-6641-a722-b945-faa0a118c352:107

The GPX file seems to load successfully, as indicated by the “GPX loaded and bounds set” message. The gpx object is created, and it has methods and layers. However, when trying to call gpx.get_start_point(), I get the error “Uncaught TypeError: gpx.get_start_point is not a function”.

Codepen: https://codepen.io/mrsamlj/pen/qBzKEVV?editors=1111

Questions:

  1. Why isn’t the get_start_point() method available on the GPX object?

  2. Is this method deprecated or removed in newer versions of the Leaflet-GPX plugin?

  3. What’s the recommended way to get the start point of a GPX route using the latest version of the Leaflet-GPX plugin?

  4. If get_start_point() is no longer available, what’s the best alternative to extract the start point from the GPX data?

What Kinda Project Should I do? [closed]

I’m currently learning back-end development and have a understanding of Node.js and Express. Now, I’m looking to deepen my knowledge by working on real-life projects that reflect common back-end development tasks.

Could you suggest some project ideas or guide me on what kinds of projects I should focus on to enhance my back-end skills?

Not being able to query select an element that should exist

I have a very bizarre issue with a vanilla JS script placed on a SPA (react) website. I have this event listener, that should theoretically wait for the website to fully load before executing:

    window.addEventListener('load', function () {
        try {
            initialiseTracking();
        } catch (error) {
            console.log(error);
        }
    });

Here comes the bizarre part:

const initialiseTracking = () => {
    console.log('initialiseTracking()');
    console.log('document.readyState: ', document.readyState);
    console.log('document.body: ', document.body); // always logs correctly with element existing within
    const croElements = document.querySelectorAll('[data-ab="test-item"]');
    console.log('croElements: ', croElements, croElements.length);
};

For some reason, the croElements is sometimes empty, and sometimes not, despite the document.body always logging the body structure with the element with the data-ab="test-item" attribute.

The log contains the element: <button data-ab="test-item">Download</button> – and no, it isn’t dynamically rendered; it always should.

I couldn’t create a demo for this unfortunately and it’s too difficult to show the full environment (it’s basically a react page with the above code in a script tag in the head) so I know there’s limited information to go on, but does anyone have a hunch about why the croElements only sometimes finds the element despite it always logging to be present? Or any ideas of what I could try? (Note: I don’t want a mutation observer because on some pages that use the script a croElement may not exist).

Thanks…

How JavaScript executes this block of code

I have this block of code:
const arr = [1,2,3]; arr[arr.length] = arr.pop(); console.log(arr) console.log(arr.length)

and when I execute this using a JS interpreter the output is given as:

[ 1, 2, <1 empty item>, 3 ] 4

But I don’t understand how JS handles the second line of code where it assigns the index of array length to arr.pop().

When I asked this to GPT and Gemini they both say that the array is [ 1, 2, 3 ] and the length is 3.

So, what I think in second line of code is that it pops the last element which is 3 then second line becomes arr[arr.length] = 3 but what I couldn’t understand if arr.length is assigned as 3 or 2.

Page loading slow even after optimised build

The following page component is at location src/app/posts/[slug].tsx

This is using Next JS 14.

For that api call to http://localhost:5001/posts, I purposely added a 5 seconds delay.

So during npm run dev, it is slow. Fine, as expected.

But then I npm run build it. I get the following for this page.

● (SSG) prerendered as static HTML (uses getStaticProps)

So now when I perform npm run start, I am expecting this page to load up instantly.

But it still has the 5 seconds delay. Why? Is my understanding wrong.

import React from 'react';
import Link from 'next/link';

type Article = {
  id: number;
  title: string;
  author: string;
  date: string;
  summary: string;
  url: string;
};

type Articles = {
  posts: Article[];
};

const Posts = async () => {
  const response = await fetch('http://localhost:5001/posts', { next: { revalidate: 60 } });
  const data: Articles = await response.json();

  return (
    <div className="max-w-4xl mx-auto py-8">
      <h1 className="text-3xl font-bold mb-6 text-center">Articles</h1>
      <div className="space-y-6">
        {data.posts.map((article) => (
          <div key={article.id} className="p-6 bg-white shadow-md rounded-lg border border-gray-200">
            <h2 className="text-xl font-semibold text-blue-600 hover:underline">
              <a href={article.url} target="_blank" rel="noopener noreferrer">
                {article.title}
              </a>
            </h2>
            <p className="text-gray-700 mb-2">
              <span className="font-bold">Author:</span> {article.author}
            </p>
            <p className="text-gray-700 mb-2">
              <span className="font-bold">Date:</span> {article.date}
            </p>
            <p className="text-gray-700 mb-4">{article.summary}</p>
            <Link
              className="mt-4 px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600"
              href={`/posts/${encodeURIComponent(article.title)}`}
            >
              View Details
            </Link>
          </div>
        ))}
      </div>
    </div>
  );
};

export default Posts;

I’m using multiple steps form, Is it ok to use display none?

In my form, I have three steps, in each one I have many inputs, and in each step i must show and hide some links in the website, so i used display none with Javascript, and I noticed that we shoudn’t use it for SEO reasons, so what should I do ?

I used the single-step forms, but I saw it could be done in the multiple steps form. It would be easier and faster on the site and transfer to the database without sending it in several forms.
The problem is that I saw many comments about not using display none.
So is it ok now to use it, or there is another method to do the hiding tabs(steps).

CRUD operations on spring boot thymeleaf javascript application

Image I’m submitting the pollThe application is used for creating polls, polls can have many questions and questions many options, So the issue is that when I add e new survey(poll) in default we have 4 fields the poll’s name, first question and 2 options, i filled them up and then pressed the button add option and added 3 more options so it became 5 options for the first question, then i pressed the button add question and default we have 3 fields the question desc. and 2 options I filled them and then pressed button add option and here instead of adding an option to the second question it saves the second question with 2 options and creates the third question with question desc and 2 options null and 3rd option as it should in the second question.

The method is
@PostMapping(“/poll/create”)
public String createPoll(@ModelAttribute PollData pollData, Model model)

How it Is (WITH ERROR)
Question 1: Question 1
Option 1: option 1
Option 2: option 2
Option 3: option 3
Option 4: option 4
Option 5: option 5
Question 2: Second question
Option 1: option 2.1
Option 2: option 2.2
Question 3: null
Option 1: null
Option 2: null
Option 3: option 2.3
Option 4: option 2.4
Option 5: option 2.5

How should be
Question 1: Question 1
Option 1: option 1
Option 2: option 2
Option 3: option 3
Option 4: option 4
Option 5: option 5
Question 2: Second question
Option 1: option 2.1
Option 2: option 2.2
Option 3: option 2.3
Option 4: option 2.4
Option 5: option 2.5
package finki.ukim.mk.surveyKing.controller;

import finki.ukim.mk.surveyKing.model.*;
import finki.ukim.mk.surveyKing.service.OptionService;
import finki.ukim.mk.surveyKing.service.PollService;
import finki.ukim.mk.surveyKing.service.QuestionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;

@Controller
public class PollController {
    @Autowired
    private PollService pollService;

    @Autowired
    private QuestionService questionService;

    @Autowired
    private OptionService optionService;

    @GetMapping("/")
    public String homePage(Model model) {
        model.addAttribute("polls", pollService.getAllPols());
        return "index";
    }

    @GetMapping("/poll/{id}")
    public String viewPoll(@PathVariable int id, Model model) {
        Poll poll = pollService.getPollById(id);
        model.addAttribute("poll", poll);
        model.addAttribute("questions", poll.getQuestions());
        return "poll";
    }

    @GetMapping("/poll/create")
    public String createPollForm(Model model) {
        model.addAttribute("poll", new Poll());
        return "create_poll";
    }

@PostMapping("/poll/create")
public String createPoll(@ModelAttribute PollData pollData, Model model) {
    // Log the pollData structure to check what is being submitted
    System.out.println("Poll Name: " + pollData.getName());
    for (int i = 0; i < pollData.getQuestions().size(); i++) {
        QuestionData questionData = pollData.getQuestions().get(i);
        System.out.println("Question " + (i + 1) + ": " + questionData.getText());
        for (int j = 0; j < questionData.getOptions().size(); j++) {
            OptionData optionData = questionData.getOptions().get(j);
            System.out.println("Option " + (j + 1) + ": " + optionData.getDescription());
        }
    }

    // Proceed with validation
    if (pollData.getName() == null || pollData.getName().isEmpty()) {
        model.addAttribute("error", "Poll name is required");
        return "create_poll";
    }

    if (pollData.getQuestions() == null || pollData.getQuestions().isEmpty()) {
        model.addAttribute("error", "At least one question is required");
        return "create_poll";
    }

    for (QuestionData questionData : pollData.getQuestions()) {
        if (questionData.getText() == null || questionData.getText().isEmpty()) {
            model.addAttribute("error", "All questions must have text");
            return "create_poll";
        }

        if (questionData.getOptions() == null || questionData.getOptions().isEmpty()) {
            model.addAttribute("error", "Each question must have at least one option");
            return "create_poll";
        }

        for (OptionData optionData : questionData.getOptions()) {
            if (optionData.getDescription() == null || optionData.getDescription().isEmpty()) {
                model.addAttribute("error", "All options must have descriptions");
                return "create_poll";
            }
        }
    }

    // If everything is fine, create the poll
    Poll poll = new Poll();
    poll.setName(pollData.getName());

    List<Question> questionList = new ArrayList<>();
    for (QuestionData questionData : pollData.getQuestions()) {
        Question question = new Question();
        question.setText(questionData.getText());
        question.setPoll(poll);

        List<Option> optionList = new ArrayList<>();
        for (OptionData optionData : questionData.getOptions()) {
            Option option = new Option();
            option.setDescription(optionData.getDescription());
            option.setQuestion(question);
            option.setPoll(poll);
            optionList.add(option);
        }
        question.setOptions(optionList);
        questionList.add(question);
    }
    poll.setQuestions(questionList);

    pollService.createPoll(poll);

    return "redirect:/";
}



    @PostMapping("/vote")
    public String createVote(@RequestParam("selectedOptions") List<Integer> selectedOptionIds,
                             @RequestParam("pollId") int pollId) {

        for (Integer optionId : selectedOptionIds) {
            optionService.incrementVote(optionId);
        }
        return "redirect:/";
    }

    @GetMapping("/poll/{id}/results")
    public String pollResults(@PathVariable int id, Model model) {
        Poll poll = pollService.getPollById(id);
        model.addAttribute("poll", poll);
        return "results";
    }
    @GetMapping("/poll/edit/{id}")
    public String editPollForm(@PathVariable int id, Model model) {
        Poll poll = pollService.getPollById(id);
        model.addAttribute("poll", poll);
        return "edit_poll"; // Create an "edit_poll.html" Thymeleaf template
    }

    @PostMapping("/poll/edit/{id}")
    public String editPoll(@PathVariable int id, @ModelAttribute PollData pollData) {
        Poll poll = pollService.getPollById(id);
        pollService.updatePoll(poll, pollData);
        return "redirect:/";
    }
    @PostMapping("/poll/delete/{id}")
    public String deletePoll(@PathVariable int id) {
        pollService.deletePoll(id);
        return "redirect:/";
    }
}

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Create Poll</title>
  <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
  <div th:if="${error}" class="alert alert-danger">
    <p th:text="${error}"></p>
  </div>
  <h1 class="mt-4 mb-3">Create a New Poll</h1>
  <form th:action="@{/poll/create}" method="post">
    <div class="form-group">
      <label for="name">Poll Name:</label>
      <input type="text" id="name" name="name" class="form-control" required>
    </div>
    <div id="questionsContainer">
      <div class="form-group question-block">
        <label>Question:</label>
        <input type="text" name="questions[0].text" class="form-control mb-2" placeholder="Question Text" required>

        <div class="optionsContainer">
          <div class="form-group option-block">
            <input type="text" name="questions[0].options[0].description" class="form-control mb-2" placeholder="Option 1" required>
          </div>
          <div class="form-group option-block">
            <input type="text" name="questions[0].options[1].description" class="form-control mb-2" placeholder="Option 2" required>
          </div>
        </div>

        <button type="button" class="btn btn-secondary add-option-btn mb-2">Add Option</button>
      </div>
    </div>

    <button type="button" class="btn btn-primary mt-2" id="addQuestionBtn">Add Question</button>
    <button type="submit" class="btn btn-success mt-2">Create Poll</button>
  </form>
</div>

<script>
  let questionIndex = 1; // Tracks the current number of questions
  let optionIndices = { 0: 2 }; // Tracks the current number of options for each question

  // Function to add a new question block
  function addQuestionBlock() {
    const questionsContainer = document.getElementById('questionsContainer');

    const newQuestionBlock = document.createElement('div');
    newQuestionBlock.className = 'question-block form-group';
    newQuestionBlock.setAttribute('data-question-index', questionIndex); // Set data attribute for easier access
    newQuestionBlock.innerHTML = `
        <label>Question:</label>
        <input type="text" name="questions[${questionIndex}].text" class="form-control mb-2" placeholder="Question Text" required>

        <div class="optionsContainer">
            <div class="form-group option-block">
                <input type="text" name="questions[${questionIndex}].options[0].description" class="form-control mb-2" placeholder="Option 1" required>
            </div>
            <div class="form-group option-block">
                <input type="text" name="questions[${questionIndex}].options[1].description" class="form-control mb-2" placeholder="Option 2" required>
            </div>
        </div>

        <button type="button" class="btn btn-secondary add-option-btn mb-2">Add Option</button>
    `;

    questionsContainer.appendChild(newQuestionBlock);

    // Initialize the option index for this question
    optionIndices[questionIndex] = 2;

    // Add event listener for adding options in this question block
    newQuestionBlock.querySelector('.add-option-btn').addEventListener('click', function () {
      addOption(newQuestionBlock.querySelector('.optionsContainer'), questionIndex);
    });

    questionIndex++; // Increment the question index
  }

  // Function to add a new option within a specific question block
  function addOption(optionsContainer, questionIdx) {
    // Ensure the option index for this question is initialized
    if (typeof optionIndices[questionIdx] === 'undefined') {
      optionIndices[questionIdx] = optionsContainer.querySelectorAll('.option-block').length;
    }

    const optionCount = optionIndices[questionIdx];

    // Create a new input for the option without replacing the entire innerHTML
    const newOptionInput = document.createElement('div');
    newOptionInput.className = 'form-group option-block';
    newOptionInput.innerHTML = `
        <input type="text" name="questions[${questionIdx}].options[${optionCount}].description" class="form-control mb-2" placeholder="Option ${optionCount + 1}" required>
    `;

    optionsContainer.appendChild(newOptionInput);

    // Increment the option index for this question
    optionIndices[questionIdx]++;
  }

  // Add event listener for the initial add-question button
  document.getElementById('addQuestionBtn').addEventListener('click', addQuestionBlock);

  // Add event listener for the initial add-option button in the first question
  document.querySelector('.add-option-btn').addEventListener('click', function () {
    const firstOptionsContainer = document.querySelector('.optionsContainer');
    addOption(firstOptionsContainer, 0); // 0 is the index of the first question
  });

  // Ensure form submission only happens when all data is correctly in place
  document.querySelector('form').addEventListener('submit', function (event) {
    let isValid = true;
    for (let i = 0; i < questionIndex; i++) {
      const questionText = document.querySelector(`input[name="questions[${i}].text"]`);
      if (!questionText || questionText.value.trim() === '') {
        alert(`Question ${i + 1} must have text.`);
        isValid = false;
        break;
      }

      const optionsCount = optionIndices[i];
      if (optionsCount === undefined || optionsCount < 1) {
        alert(`Question ${i + 1} must have at least one option.`);
        isValid = false;
        break;
      }

      for (let j = 0; j < optionsCount; j++) {
        const optionText = document.querySelector(`input[name="questions[${i}].options[${j}].description"]`);
        if (!optionText || optionText.value.trim() === '') {
          alert(`Option ${j + 1} in Question ${i + 1} must have text.`);
          isValid = false;
          break;
        }
      }

      if (!isValid) break;
    }

    if (!isValid) {
      event.preventDefault();
    } else {
      console.log("Form is valid, submitting...");
    }
  });

</script>
</body>
</html>

The error you can see in the explanation above.

Checking attendance and phisical presence in javascript project

Good evening. I am writing this question because I have been thinking about a solution to the following problem for over a month. I have to create an application so that the attendees of a training course can verify their presence during the course. They have to do it digitally and I cannot use the geolocation of their browser. The application I have thought of consists of the teacher showing a code on the class screen and the attendees entering their personal identification number (DNI in Spain) and the code shown, within a time window that is usually the duration of the course. I want to prevent them from being able to check in from their homes without attending the course in person. I also want to prevent a person who attends the course from being able to send the code to another person who is at home.
I also cannot use a QR reader since we do not have one and what I want is for each user to check in and be able to later compare attendance.
It is a Google Workspace environment and everyone has an account created.
I am designing a webApp with Google Apps Script, Javascript and HTML.
Any ideas or help are welcome.

I think I need a magic trick for this.

Thanks for reading.App example

Matching whitespace lazy or greedy – which one performs better?

I am building a name matching regex. Since a lot of those names have spaces in them, but we still want to match cases where more than one space is being used, we’re not just using a single, regular whitespace character.

One optimization I heard about is making the whitespace matching in between words (like the space in the name “Alex Smith”) lazy, instead of greedy.

I know what the difference between lazy and greedy matching is, but I don’t understand how it would have an effect on matching whitespace.

Here is what I got right now:

Alexs+?Smith

Are there any performance benefits to actually using the lazy modifier on the s+ parts in this case? I couldn’t find any good material on this specific case of matching just whitespace between words.

Any help would be appreciated!

Execution of this inside an object [duplicate]

Consider the following example. We call a function makeUser which returns an object

function makeUser() {
  return {
    name: "John",
    ref() {
      return this.name;
    }
  };
}

let user = makeUser();
console.log(user.ref())

Now as far as I understand, this does not depend on how an object is defined, but rather how it is called.

So, I thought user.ref() would point to the global this.name since that’s where user is being called and hence should be undefined. However, it prints ‘John’. Appreciate clarifications.

Consider this for e.g. Since the user object points to the global this. this.fname is undefined. So what exactly is happening in the former case?

function makeUser() {
  return {
    fname: "John",
    ref: this
  };
}

let user = makeUser();

console.log( user.ref.fname ); 

Websocket fails to connect when running server on remote device

I have a problem. My server is written in C#, using httplistener. The client side is in javascript. When I am running both the server and client on the same device, the websocket session is established successfully, but when I’m running the server on a remote device, the websocket fails to connect. Http requests and responses work correctly, the server is able to serve the client files, but the server does not receive any websocket context. Also I found out that websocket request from client got return code 200.

lack of the drop-down menu after click on the button

I can’t unwrap menu in mobile version of my site. I don’t know what is wrong in my code.

I added the code for the JavaScript file and I wanted to unwrap menu, when a user click on the menu button. It seemed to me that I styled it proper for my CSS file.

const navbar = document.querySelector("nav");
const openMenu = document.getElementById("menu-button");
const closeMenu = document.getElementById("menu-close");

openMenu.addEventListener("click", function() {
  navbar.classList.add("open");
})

closeMenu.addEventListener("click", function() {
  navbar.classList.remove("open");
})
.attribution {
  font-size: 11px;
  text-align: center;
}

.attribution a {
  color: hsl(228, 45%, 44%);
}

.body {
  font-size: 15px;
  font-family: "Inter", sans-serif;
  min-height: 100vh;
  margin: 0;
  box-sizing: border-box;
}

ul {
  list-style-type: none;
  display: flex;
  width: 100%;
  padding-right: 40px;
}

#menu-close {
  display: none;
  border: none;
  background: none;
}

header {
  display: flex;
  justify-content: space-between;
  padding-left: 60px;
  box-sizing: border-box;
  width: 100%
}

nav {
  margin-left: auto;
}

a {
  margin-right: 20px;
  text-decoration: none;
  color: rgb(73, 72, 72)
}

.container {
  display: grid;
  grid-template-rows: auto auto 1fr;
  grid-template-columns: 2fr 1fr;
  gap: 20px;
  padding: 60px;
}

.news {
  background-color: hsl(240, 100%, 5%);
  grid-row: 1 / 3;
  grid-column: 2;
  padding: 15px
}

.news h2 {
  color: hsl(36, 100%, 99%);
}

.news p {
  color: hsl(36, 100%, 99%);
  text-align: justify
}

input[type="submit"] {
  background: hsl(5, 85%, 63%);
  border: 0.75px;
  text-transform: uppercase;
  font-weight: 700;
  padding: 14px;
  width: 50%
}

a h2:hover {
  color: hsl(35, 77%, 62%)
}

li a:hover {
  color: hsl(5, 85%, 63%)
}

input[type="submit"]:hover {
  background-color: black;
  color: white;
  cursor: pointer
}

.main-image-desktop {
  margin: 0;
  width: 100%;
  height: auto
}

.additional {
  display: flex;
  grid-row: 3;
  grid-column: 1 / 3;
  flex: 1;
  gap: 20px;
  margin: 0
}

.news h1 {
  color: hsl(35, 77%, 62%);
  font-weight: 700;
  font-size: 2rem;
}

.extra-article {
  display: flex;
  gap: 20px
}

#main-title {
  margin: 0;
  padding: 0;
  font-weight: 900;
  font-size: 3rem
}

.number {
  color: hsl(5, 85%, 63%);
  font-weight: 700;
  font-size: 2rem;
}

.extra-article span {
  font-weight: 800;
  font-size: 1.1rem;
}

.extra-article span:hover {
  color: hsl(5, 85%, 63%);
  cursor: pointer;
}

.description {
  display: flex;
  flex-direction: column;
  height: 100%;
  margin: 0;
  padding: 0
}

.main-article {
  display: flex;
  gap: 60px;
  margin-bottom: 20px;
}

.description-text {
  line-height: 150%
}

.news {
  margin-bottom: 20px
}

#toggle-navigation {
  display: none
}

.main-image-mobile {
  display: none
}

#menu-button {
  display: none
}

li {
  margin-right: 20px;
}

@media(max-width: 375px) {
  ul {
    display: none;
  }
  nav {
    width: 100%
  }
  nav.open {
    position: relative;
    width: auto;
    background-color: transparent;
    padding: 0;
    z-index: auto;
  }
  .container {
    display: flex;
    flex-direction: column;
    width: 100%;
    margin: 0;
    padding-inline: 0px;
  }
  .additional {
    flex-direction: column;
  }
  .main-article {
    flex-direction: column;
  }
  .attribution {
    text-align: center;
  }
  body {
    background-size: cover;
  }
  .main-image-mobile {
    display: block;
  }
  .main-image-desktop {
    display: none;
  }
  header {
    display: flex;
    justify-content: space-between;
    padding-left: 0px;
    padding-right: 0px;
    box-sizing: border-box;
  }
  .menu-button {
    position: absolute;
    top: 0;
    right: 0;
    padding: 15px;
  }
  nav.open ul {
    display: flex;
    flex-direction: column;
  }
  #menu-button {
    display: inline-block;
    background-color: white;
    border: none
  }
  nav.open #menu-close {
    position: absolute;
    top: 15px;
    right: 15px;
    cursor: pointer;
  }
  nav.open #menu-button {
    display: none
  }
}

header img {
  max-width: 100%
}
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap" rel="stylesheet">


<header>
  <div class="logo">
    <img src="./assets/images/logo.svg" />
  </div>
  <nav>
    <button id="menu-close">
        <img src="assets/images/icon-menu-close.svg" alt="cross close menu">
      </button>
    <ul>
      <li>Home</li>
      <li>New</li>
      <li>Popular</li>
      <li>Trending</li>
      <li>Categories</li>
    </ul>
  </nav>

  <div class="menu-button">
    <button id="menu-button">
        <img src="assets/images/icon-menu.svg" alt="icon-menu">
      </button>
  </div>
</header>

<main>
  <div class="container">
    <img class="main-image-desktop" src="./assets/images/image-web-3-desktop.jpg" alt="" />
    <img class="main-image-mobile" src="./assets/images/image-web-3-mobile.jpg" alt="" />
    <div class="main-article">
      <p id="main-title">The Bright Future of Web 3.0?</p>

      <div class="description">
        <div class="description-text">
          <p>We dive into the next evolution of the web that claims to put the power of the platforms back into the hands of the people. But is it really fulfilling its promise?</p>
        </div>

        <input type="submit" value="Read more"></a>
      </div>
      </description>
    </div>

    <div class="news">
      <h1>New</h1>

      <div class="information">
        <a href='#'>
          <h2>Hydrogen VS Electric Cars</h2>
        </a>
        <p>Will hydrogen-fueled cars ever catch up to EVs?</p>
        <hr />
      </div>

      <div class="information">
        <a href='#'>
          <h2>The Downsides of AI Artistry</h2>
        </a>
        <p>What are the possible adverse effects of on-demand AI image generation?</p>
        <hr />
      </div>

      <div class="information">
        <a href="#">
          <h2>Is VC Funding Drying Up?</h2>
        </a>
        <p>Private funding by VC firms is down 50% YOY. We take a look at what that means.</p>
      </div>
    </div>

    <div class="additional">
      <div class="extra-article">
        <div class="image">
          <img src="./assets/images/image-retro-pcs.jpg" alt="" />
        </div>
        <div class="image-description">
          <p class="number">01</p>
          <span>Reviving Retro PCs</span>
          <p>What happens when old PCs are given modern upgrades?</p>
        </div>
      </div>

      <div class="extra-article">
        <div class="image">
          <img src="./assets/images/image-top-laptops.jpg" alt="" />
        </div>
        <div class="image-description">
          <p class="number">02</p>
          <span>Top 10 Laptops of 2022</span>
          <p>Our best picks for various needs and budgets.</p>
        </div>
      </div>

      <div class="extra-article">
        <div class="image">
          <img src="./assets/images/image-gaming-growth.jpg" alt="" />
        </div>
        <div class="image-description">
          <p class="number">03</p>
          <span>The Growth of Gaming</span>
          <p>How the pandemic has sparked fresh opportunities.</p>
        </div>
      </div>
    </div>
  </div>
</main>

<footer>
  <div class="attribution">
    Challenge by <a href="https://www.frontendmentor.io?ref=challenge" target="_blank">Frontend Mentor</a>. Coded by <a href="https://github.com/NatKacz99">NatKacz99</a>.
  </div>
</footer>