diff --git a/book.toml b/book.toml index df301ab6f..0d8680b7c 100644 --- a/book.toml +++ b/book.toml @@ -15,12 +15,13 @@ command = "python3 ./hacktricks-preprocessor.py" env = "prod" [output.html] -additional-css = ["theme/tabs.css", "theme/pagetoc.css"] +additional-css = ["theme/tabs.css", "theme/pagetoc.css", "theme/discount.css"] additional-js = [ "theme/tabs.js", "theme/pagetoc.js", "theme/ht_searcher.js", "theme/sponsor.js", + "theme/discount.js", "theme/motion.js", "theme/ai.js" ] diff --git a/src/images/hacktricks-summer-discount-2026.png b/src/images/hacktricks-summer-discount-2026.png new file mode 100644 index 000000000..61b0280b9 Binary files /dev/null and b/src/images/hacktricks-summer-discount-2026.png differ diff --git a/theme/discount.css b/theme/discount.css new file mode 100644 index 000000000..013c85385 --- /dev/null +++ b/theme/discount.css @@ -0,0 +1,299 @@ +.ht-discount-overlay, +.ht-discount-overlay * { + box-sizing: border-box; +} + +.ht-discount-overlay { + position: fixed; + inset: 0; + z-index: 100000; + display: flex; + align-items: center; + justify-content: center; + padding: 18px; + background: rgba(2, 6, 16, 0.66); + backdrop-filter: blur(8px); + opacity: 0; + visibility: hidden; + transition: opacity 0.2s ease, visibility 0.2s ease; +} + +.ht-discount-overlay.ht-discount-overlay--visible { + opacity: 1; + visibility: visible; +} + +.ht-discount-card { + --discount-accent: #2f7dff; + --discount-accent-2: #ff3b4e; + --discount-rx: 0deg; + --discount-ry: 0deg; + --discount-lift: 0px; + --discount-scale: 1; + --discount-img-x: 0px; + --discount-img-y: 0px; + position: relative; + width: min(92vw, 520px); + max-height: min(92vh, 760px); + isolation: isolate; + overflow: hidden; + border-radius: 16px; + padding: 12px; + background: transparent; + color: #eef4ff; + font-family: "Chakra Petch", "Open Sans", system-ui, sans-serif; + box-shadow: 0 26px 70px -28px rgba(0, 0, 0, 0.95), 0 0 0 1px rgba(120, 150, 255, 0.1); + transform: perspective(1000px) rotateX(var(--discount-rx)) rotateY(var(--discount-ry)) translateY(var(--discount-lift)) scale(var(--discount-scale)); + transform-style: preserve-3d; + transition: transform 0.18s cubic-bezier(.2, .7, .3, 1); +} + +.ht-discount-card::before { + content: ""; + position: absolute; + inset: 1px; + z-index: -2; + border-radius: 15px; + background: linear-gradient(162deg, #0a1016, #0c0910); +} + +.ht-discount-grid { + position: absolute; + inset: -1px; + z-index: -3; + border-radius: 17px; + overflow: hidden; + pointer-events: none; +} + +.ht-discount-grid::before { + content: ""; + position: absolute; + top: 50%; + left: 50%; + width: 760px; + height: 760px; + margin: -380px 0 0 -380px; + background: conic-gradient(from 0deg, transparent 0deg, var(--discount-accent) 58deg, transparent 128deg, var(--discount-accent-2) 208deg, transparent 300deg); + opacity: 0.58; + animation: sc-spin 5s linear infinite; +} + +.ht-discount-scan { + position: absolute; + inset: 1px; + z-index: -1; + border-radius: 15px; + overflow: hidden; + pointer-events: none; + opacity: 0.52; +} + +.ht-discount-scan::before { + content: ""; + position: absolute; + left: 0; + right: 0; + height: 120px; + background: linear-gradient(180deg, transparent, rgba(47, 125, 255, 0.16), transparent); + animation: sc-scan 3.7s linear infinite; +} + +.ht-discount-corners { + position: absolute; + inset: 1px; + z-index: -1; + border-radius: 15px; + pointer-events: none; + background: repeating-linear-gradient(0deg, transparent 0 2px, rgba(0, 0, 0, 0.22) 2px 3px); + mix-blend-mode: overlay; + opacity: 0.42; +} + +.ht-discount-kicker { + position: absolute; + top: 11px; + left: 14px; + z-index: 2; + display: inline-flex; + align-items: center; + gap: 5px; + max-width: calc(100% - 78px); + padding: 4px 7px; + border: 1px solid rgba(47, 125, 255, 0.38); + border-radius: 4px; + background: rgba(4, 10, 20, 0.72); + color: var(--discount-accent); + font-family: "JetBrains Mono", monospace; + font-size: 8px; + font-weight: 700; + letter-spacing: 0.18em; + line-height: 1.2; + text-transform: uppercase; + backdrop-filter: blur(8px); +} + +.ht-discount-kicker::before { + content: ""; + width: 5px; + height: 5px; + flex: 0 0 auto; + border-radius: 50%; + background: var(--discount-accent); + box-shadow: 0 0 8px var(--discount-accent); + animation: sc-blink 1.6s ease-in-out infinite; +} + +.ht-discount-close { + position: absolute; + top: 10px; + right: 15px; + z-index: 3; + width: 34px; + height: 34px; + display: inline-flex; + align-items: center; + justify-content: center; + border: 1px solid rgba(255, 255, 255, 0.26); + border-radius: 50%; + background: rgba(4, 10, 20, 0.7); + color: #eef4ff; + cursor: pointer; + font-family: "JetBrains Mono", monospace; + font-size: 18px; + line-height: 1; + backdrop-filter: blur(8px); +} + +.ht-discount-close:hover, +.ht-discount-close:focus-visible { + border-color: rgba(255, 59, 78, 0.7); + color: #fff; + outline: none; +} + +.ht-discount-link { + position: relative; + display: block; + overflow: hidden; + border: 1px solid rgba(47, 125, 255, 0.28); + border-radius: 12px; + background: #08101a; + text-decoration: none; +} + +.ht-discount-link::after { + content: ""; + position: absolute; + inset: 0; + pointer-events: none; + background: linear-gradient(135deg, rgba(255, 255, 255, 0.18), transparent 26%, transparent 72%, rgba(47, 125, 255, 0.18)); + mix-blend-mode: screen; +} + +.ht-discount-image { + display: block; + width: 100%; + aspect-ratio: 1 / 1; + object-fit: cover; + transform: translate3d(var(--discount-img-x), var(--discount-img-y), 0) scale(1.025); + transition: transform 0.18s cubic-bezier(.2, .7, .3, 1); +} + +.ht-discount-actions { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 10px; + align-items: center; + padding-top: 12px; +} + +.ht-discount-primary, +.ht-discount-delay { + min-height: 40px; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 9px; + font-family: "JetBrains Mono", monospace; + font-size: 12px; + font-weight: 700; + letter-spacing: 0.08em; + line-height: 1.2; + text-align: center; + text-transform: uppercase; +} + +.ht-discount-primary { + border: 1px solid rgba(47, 125, 255, 0.42); + background: rgba(47, 125, 255, 0.1); + color: var(--discount-accent); + text-decoration: none; +} + +.ht-discount-primary::after { + content: "->"; + margin-left: 9px; + animation: sc-arrow 1.4s ease-in-out infinite; +} + +.ht-discount-delay { + border: 1px solid rgba(255, 255, 255, 0.2); + background: rgba(255, 255, 255, 0.06); + color: rgba(238, 244, 255, 0.84); + cursor: pointer; + padding: 0 12px; +} + +.ht-discount-primary:hover, +.ht-discount-primary:focus-visible, +.ht-discount-delay:hover, +.ht-discount-delay:focus-visible { + border-color: rgba(255, 59, 78, 0.7); + color: #fff; + outline: none; +} + +@media (max-width: 560px) { + .ht-discount-overlay { + padding: 10px; + align-items: flex-end; + } + + .ht-discount-card { + width: min(100%, 430px); + padding: 9px; + border-radius: 14px; + } + + .ht-discount-actions { + grid-template-columns: 1fr; + } + + .ht-discount-kicker { + top: 10px; + left: 11px; + font-size: 8px; + letter-spacing: 0.14em; + } + + .ht-discount-close { + top: 8px; + right: 11px; + } +} + +@media (prefers-reduced-motion: reduce) { + .ht-discount-overlay, + .ht-discount-card, + .ht-discount-image { + transition: none; + } + + .ht-discount-grid::before, + .ht-discount-scan::before, + .ht-discount-kicker::before, + .ht-discount-primary::after { + animation: none; + } +} diff --git a/theme/discount.js b/theme/discount.js new file mode 100644 index 000000000..59522e3e3 --- /dev/null +++ b/theme/discount.js @@ -0,0 +1,206 @@ +;(function discount() { + var CAMPAIGN_ID = "summer-2026" + var DISMISS_KEY = "htDiscountDismissedUntil:" + CAMPAIGN_ID + var DISMISS_MS = 10 * 24 * 60 * 60 * 1000 + var IMAGE_PATH = "images/hacktricks-summer-discount-2026.png" + var TARGET_URL = "https://hacktricks-training.com" + var TITLE = "HackTricks Training Summer Discount" + + function storageGet(key) { + try { + return window.localStorage.getItem(key) + } catch (error) { + return null + } + } + + function storageSet(key, value) { + try { + window.localStorage.setItem(key, value) + } catch (error) {} + } + + function getParam(name) { + try { + return new URLSearchParams(window.location.search).get(name) + } catch (error) { + return null + } + } + + function shouldReduceMotion() { + return Boolean( + document.documentElement.classList.contains("motion-reduced") || + (window.__hacktricksMotion && + window.__hacktricksMotion.shouldReduceMotion && + window.__hacktricksMotion.shouldReduceMotion()) || + (window.matchMedia && + window.matchMedia("(prefers-reduced-motion: reduce)").matches) + ) + } + + function getAssetUrl(path) { + var root = typeof window.path_to_root === "string" ? window.path_to_root : "" + return new URL(root + path, document.baseURI || window.location.href).href + } + + function isDismissed() { + if (getParam("discount") === "show") { + return false + } + + var dismissedUntil = Number(storageGet(DISMISS_KEY)) + return Number.isFinite(dismissedUntil) && Date.now() < dismissedUntil + } + + function close(overlay, onKeydown) { + overlay.classList.remove("ht-discount-overlay--visible") + if (onKeydown) { + document.removeEventListener("keydown", onKeydown) + } + window.setTimeout(function() { + overlay.remove() + }, 220) + } + + function dismissFor15Days(overlay, onKeydown) { + storageSet(DISMISS_KEY, String(Date.now() + DISMISS_MS)) + close(overlay, onKeydown) + } + + function initMotion(card) { + if (!card || shouldReduceMotion()) { + return + } + + card.addEventListener("mouseenter", function() { + card.style.setProperty("--discount-lift", "-5px") + card.style.setProperty("--discount-scale", "1.01") + }) + + card.addEventListener("mouseleave", function() { + card.style.setProperty("--discount-rx", "0deg") + card.style.setProperty("--discount-ry", "0deg") + card.style.setProperty("--discount-lift", "0px") + card.style.setProperty("--discount-scale", "1") + card.style.setProperty("--discount-img-x", "0px") + card.style.setProperty("--discount-img-y", "0px") + }) + + card.addEventListener("mousemove", function(event) { + var rect = card.getBoundingClientRect() + var x = (event.clientX - rect.left) / rect.width - 0.5 + var y = (event.clientY - rect.top) / rect.height - 0.5 + + card.style.setProperty("--discount-rx", (-y * 9).toFixed(2) + "deg") + card.style.setProperty("--discount-ry", (x * 9).toFixed(2) + "deg") + card.style.setProperty("--discount-img-x", (x * 10).toFixed(2) + "px") + card.style.setProperty("--discount-img-y", (y * 10).toFixed(2) + "px") + }) + } + + function createDiscount() { + if (isDismissed() || document.querySelector(".ht-discount-overlay")) { + return + } + + var overlay = document.createElement("div") + overlay.className = "ht-discount-overlay" + overlay.setAttribute("role", "dialog") + overlay.setAttribute("aria-modal", "true") + overlay.setAttribute("aria-label", TITLE) + + var card = document.createElement("div") + card.className = "ht-discount-card" + + var grid = document.createElement("span") + grid.className = "ht-discount-grid" + grid.setAttribute("aria-hidden", "true") + + var scan = document.createElement("span") + scan.className = "ht-discount-scan" + scan.setAttribute("aria-hidden", "true") + + var corners = document.createElement("span") + corners.className = "ht-discount-corners" + corners.setAttribute("aria-hidden", "true") + + var kicker = document.createElement("span") + kicker.className = "ht-discount-kicker" + kicker.textContent = "Summer offer" + + var closeButton = document.createElement("button") + closeButton.className = "ht-discount-close" + closeButton.type = "button" + closeButton.setAttribute("aria-label", "Close discount offer") + closeButton.textContent = "x" + + var imageLink = document.createElement("a") + imageLink.className = "ht-discount-link" + imageLink.href = TARGET_URL + imageLink.target = "_blank" + imageLink.rel = "noopener noreferrer" + imageLink.setAttribute("aria-label", TITLE) + + var image = document.createElement("img") + image.className = "ht-discount-image" + image.src = getAssetUrl(IMAGE_PATH) + image.alt = TITLE + image.decoding = "async" + + var actions = document.createElement("div") + actions.className = "ht-discount-actions" + + var primary = document.createElement("a") + primary.className = "ht-discount-primary" + primary.href = TARGET_URL + primary.target = "_blank" + primary.rel = "noopener noreferrer" + primary.textContent = "Open offer" + + var delayButton = document.createElement("button") + delayButton.className = "ht-discount-delay" + delayButton.type = "button" + delayButton.textContent = "Hide for 10 days" + + imageLink.appendChild(image) + actions.append(primary, delayButton) + card.append(grid, scan, corners, kicker, closeButton, imageLink, actions) + overlay.appendChild(card) + document.body.appendChild(overlay) + + initMotion(card) + + function onKeydown(event) { + if (event.key === "Escape" && overlay.isConnected) { + close(overlay, onKeydown) + } + } + + closeButton.addEventListener("click", function() { + close(overlay, onKeydown) + }) + + delayButton.addEventListener("click", function() { + dismissFor15Days(overlay, onKeydown) + }) + + overlay.addEventListener("click", function(event) { + if (event.target === overlay) { + close(overlay, onKeydown) + } + }) + + document.addEventListener("keydown", onKeydown) + + window.setTimeout(function() { + overlay.classList.add("ht-discount-overlay--visible") + }, 80) + } + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", createDiscount, { once: true }) + } else { + createDiscount() + } +})()