Offline Page

HTML Code Showcase

Offline Webpage

HTML Code

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0, user-scalable=no"
    />
    <title>You're Offline</title>
    <style>
      :root {
        --primary-color: #55adc5;
        --secondary-color: #55adc5;
        --text-color: #55adc5;
        --light-gray: #2d2d2d;
        --border-color: #3d3d3d;
        --bg-color: #1a1a1a;
        --card-bg: #2d2d2d;
        --text-muted: #e0e0e0;
        --hero-bg: #1a1a1a;
        --footer-bg: #2d2d2d;
        --input-bg: #2d2d2d;
        --input-text: #ffffff;
        --input-border: #3d3d3d;
        --gradient-start: #55adc5;
        --gradient-mid: #55adc5;
        --gradient-end: #55adc5;
        --accent-color: #90dfeb;
        --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.15);
        --shadow-md: 0 8px 24px rgba(0, 0, 0, 0.2);
        --shadow-lg: 0 16px 32px rgba(0, 0, 0, 0.25);
        --shadow-xl: 0 24px 48px rgba(0, 0, 0, 0.3);
      }

      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }

      html,
      body {
        font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
        background-color: var(--bg-color);
        color: var(--text-muted);
        height: 100%;
        overflow: hidden;
        position: relative;
      }

      .container {
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        height: 100%;
        padding: 2rem;
        position: relative;
        z-index: 2;
      }

      .offline-card {
        background-color: var(--card-bg);
        border-radius: 16px;
        padding: 2.5rem;
        width: 100%;
        max-width: 400px;
        text-align: center;
        box-shadow: var(--shadow-lg);
        position: relative;
        overflow: hidden;
        border: 1px solid var(--border-color);
      }

      .offline-card::before {
        content: "";
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 3px;
        background: linear-gradient(
          90deg,
          var(--gradient-start),
          var(--gradient-mid),
          var(--gradient-end)
        );
      }

      .icon-container {
        margin-bottom: 1.5rem;
        position: relative;
      }

      .icon-pulse {
        width: 120px;
        height: 120px;
        border-radius: 50%;
        background-color: rgba(241, 196, 15, 0.1);
        display: flex;
        align-items: center;
        justify-content: center;
        margin: 0 auto;
        position: relative;
      }

      .icon-pulse::before,
      .icon-pulse::after {
        content: "";
        position: absolute;
        border: 2px solid var(--primary-color);
        width: 100%;
        height: 100%;
        border-radius: 50%;
        animation: pulse 3s ease-out infinite;
        opacity: 0;
      }

      .icon-pulse::after {
        animation-delay: 1.5s;
      }

      @keyframes pulse {
        0% {
          transform: scale(0.5);
          opacity: 0;
        }
        50% {
          opacity: 0.3;
        }
        100% {
          transform: scale(1.5);
          opacity: 0;
        }
      }

      .offline-icon {
        width: 60px;
        height: 60px;
        fill: var(--primary-color);
      }

      .title {
        font-size: 1.8rem;
        font-weight: 700;
        margin-bottom: 0.5rem;
        color: var(--primary-color);
      }

      .message {
        font-size: 1.1rem;
        line-height: 1.5;
        margin-bottom: 2rem;
        color: var(--text-muted);
      }

      .retry-button {
        background-color: var(--primary-color);
        color: var(--bg-color);
        border: none;
        border-radius: 8px;
        padding: 0.8rem 2rem;
        font-size: 1rem;
        font-weight: 600;
        cursor: pointer;
        transition: all 0.3s ease;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        position: relative;
        overflow: hidden;
      }

      .retry-button:hover {
        background-color: var(--accent-color);
        transform: translateY(-2px);
        box-shadow: var(--shadow-md);
      }

      .retry-button:active {
        transform: translateY(0);
        box-shadow: var(--shadow-sm);
      }

      .retry-button.loading {
        pointer-events: none;
        background-color: var(--light-gray);
        color: var(--text-muted);
      }

      .retry-button.loading .spinner {
        display: inline-block;
        margin-right: 0.5rem;
      }

      .spinner {
        width: 18px;
        height: 18px;
        border: 2px solid transparent;
        border-top-color: var(--bg-color);
        border-radius: 50%;
        animation: spin 0.8s linear infinite;
        display: none;
      }

      @keyframes spin {
        0% {
          transform: rotate(0deg);
        }
        100% {
          transform: rotate(360deg);
        }
      }

      .status-indicator {
        position: absolute;
        bottom: 1rem;
        right: 1rem;
        display: flex;
        align-items: center;
        font-size: 0.8rem;
        opacity: 0.7;
      }

      .status-dot {
        width: 8px;
        height: 8px;
        border-radius: 50%;
        margin-right: 0.5rem;
        background-color: #e74c3c;
      }

      .status-dot.online {
        background-color: #2ecc71;
      }

      .particles-container {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        overflow: hidden;
        z-index: 1;
      }

      .particle {
        position: absolute;
        border-radius: 50%;
        background-color: var(--primary-color);
        opacity: 0.1;
      }

      @media (max-width: 480px) {
        .offline-card {
          padding: 2rem 1.5rem;
        }

        .title {
          font-size: 1.6rem;
        }

        .message {
          font-size: 1rem;
        }

        .icon-pulse {
          width: 100px;
          height: 100px;
        }

        .offline-icon {
          width: 50px;
          height: 50px;
        }
      }
    </style>
  </head>
  <body>
    <div class="particles-container" id="particles"></div>

    <div class="container">
      <div class="offline-card">
        <div class="icon-container">
          <div class="icon-pulse">
            <svg
              class="offline-icon"
              viewBox="0 0 24 24"
              xmlns="http://www.w3.org/2000/svg"
            >
              <path
                d="M12 4C7.31 4 3.07 5.9 0 8.98L12 21L24 8.98C20.93 5.9 16.69 4 12 4zM2.92 9.07C5.51 7.08 8.67 6 12 6s6.49 1.08 9.08 3.07l-2.85 2.86C16.46 10.71 14.31 10 12 10c-2.31 0-4.46.71-6.23 1.93L2.92 9.07z"
              />
              <path
                d="M12 10c-3.03 0-5.78 1.23-7.78 3.23l1.41 1.41C7.2 12.05 9.48 11 12 11c2.52 0 4.8 1.05 6.37 2.64l1.41-1.41C17.78 11.23 15.03 10 12 10z"
              />
              <path
                d="M12 12c-2.04 0-3.9.78-5.3 2.18l1.41 1.41c.92-.92 2.19-1.5 3.59-1.59L12 14l.3 0c1.4.09 2.68.67 3.59 1.59l1.41-1.41C15.9 12.78 14.04 12 12 12z"
              />
              <path
                d="M12 16c-1.11 0-2 .89-2 2s.89 2 2 2 2-.89 2-2-.89-2-2-2z"
              />
            </svg>
          </div>
        </div>

        <h1 class="title" id="title">You're Offline</h1>
        <p class="message" id="message">
          Check your connection and try again when you're back online.
        </p>

        <button id="retry-button" class="retry-button">
          <span class="spinner"></span>
          <span id="button-text">Retry</span>
        </button>

        <div class="status-indicator">
          <span class="status-dot" id="status-dot"></span>
          <span id="status-text">Offline</span>
        </div>
      </div>
    </div>

    <script>
      // DOM Elements
      const retryButton = document.getElementById("retry-button");
      const buttonText = document.getElementById("button-text");
      const spinner = document.querySelector(".spinner");
      const statusDot = document.getElementById("status-dot");
      const statusText = document.getElementById("status-text");
      const title = document.getElementById("title");
      const message = document.getElementById("message");
      const particlesContainer = document.getElementById("particles");

      // Language support
      const translations = {
        en: {
          title: "You're Offline",
          message:
            "Check your connection and try again when you're back online.",
          retry: "Retry",
          checking: "Checking...",
          online: "Online",
          offline: "Offline",
        },
        ko: {
          title: "오프라인 상태입니다",
          message: "인터넷 연결을 확인하고 온라인 상태가 되면 다시 시도하세요.",
          retry: "다시 시도",
          checking: "확인 중...",
          online: "온라인",
          offline: "오프라인",
        },
      };

      // Set language
      function setLanguage() {
        const userLang = navigator.language.toLowerCase().substring(0, 2);
        const lang = ["ko"].includes(userLang) ? userLang : "en";

        title.textContent = translations[lang].title;
        message.textContent = translations[lang].message;
        buttonText.textContent = translations[lang].retry;
        statusText.textContent = translations[lang].offline;
      }

      // Check connection status
      function updateOnlineStatus() {
        if (navigator.onLine) {
          statusDot.classList.add("online");
          statusText.textContent =
            navigator.language.toLowerCase().substring(0, 2) === "ko"
              ? translations["ko"].online
              : translations["en"].online;

          // Auto-reload after short delay when coming back online
          setTimeout(() => {
            location.reload();
          }, 1500);
        } else {
          statusDot.classList.remove("online");
          statusText.textContent =
            navigator.language.toLowerCase().substring(0, 2) === "ko"
              ? translations["ko"].offline
              : translations["en"].offline;
        }
      }

      // Handle retry button click
      function handleRetry() {
        retryButton.classList.add("loading");
        spinner.style.display = "inline-block";
        buttonText.textContent =
          navigator.language.toLowerCase().substring(0, 2) === "ko"
            ? translations["ko"].checking
            : translations["en"].checking;

        // Simulate checking connection
        setTimeout(() => {
          if (navigator.onLine) {
            location.reload();
          } else {
            retryButton.classList.remove("loading");
            spinner.style.display = "none";
            buttonText.textContent =
              navigator.language.toLowerCase().substring(0, 2) === "ko"
                ? translations["ko"].retry
                : translations["en"].retry;

            // Shake animation effect on failure
            retryButton.animate(
              [
                { transform: "translateX(-5px)" },
                { transform: "translateX(5px)" },
                { transform: "translateX(-5px)" },
                { transform: "translateX(5px)" },
                { transform: "translateX(0)" },
              ],
              {
                duration: 400,
                easing: "ease-in-out",
              }
            );
          }
        }, 1500);
      }

      // Create background particles
      function createParticles() {
        const particleCount = Math.floor(window.innerWidth / 20);

        for (let i = 0; i < particleCount; i++) {
          const particle = document.createElement("div");
          particle.classList.add("particle");

          // Random size between 2-6px
          const size = Math.random() * 4 + 2;
          particle.style.width = `${size}px`;
          particle.style.height = `${size}px`;

          // Random position
          particle.style.left = `${Math.random() * 100}%`;
          particle.style.top = `${Math.random() * 100}%`;

          // Random opacity
          particle.style.opacity = Math.random() * 0.15 + 0.05;

          particlesContainer.appendChild(particle);

          // Animate each particle
          animateParticle(particle);
        }
      }

      function animateParticle(particle) {
        const speed = Math.random() * 40 + 20; // 20-60 seconds for full animation

        // Start position
        const startX = parseFloat(particle.style.left);
        const startY = parseFloat(particle.style.top);

        // Random movement range
        const rangeX = Math.random() * 20 - 10; // -10% to +10%
        const rangeY = Math.random() * 20 - 10; // -10% to +10%

        let progress = 0;
        let direction = 1;

        function moveParticle() {
          if (progress >= 1) {
            direction = -1;
          } else if (progress <= 0) {
            direction = 1;
          }

          progress += 0.002 * direction;

          const newX = startX + rangeX * Math.sin(progress * Math.PI);
          const newY = startY + rangeY * Math.sin(progress * Math.PI);

          particle.style.left = `${newX}%`;
          particle.style.top = `${newY}%`;

          requestAnimationFrame(moveParticle);
        }

        moveParticle();
      }

      // Initialize
      function init() {
        setLanguage();
        updateOnlineStatus();
        createParticles();

        // Event listeners
        window.addEventListener("online", updateOnlineStatus);
        window.addEventListener("offline", updateOnlineStatus);
        retryButton.addEventListener("click", handleRetry);

        // Check connection status periodically
        setInterval(updateOnlineStatus, 5000);
      }

      // Start everything when the page loads
      window.addEventListener("load", init);
    </script>
  </body>
</html>
            

Comments

Popular Posts

Portfolio Website

15 August Code