Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ All notable changes to this project are documented here. The format follows
The affected spans now carry Cloudflare's `email_off` opt-out.
- The `[1.0.0]` and `[1.1.0]` links at the bottom of this file pointed at a
`v1.0.0` tag that was never pushed, so both 404'd.
- The guide's JavaScript moved out of the page and into `docs/guide.js`. The
site is served behind a Content Security Policy of `script-src 'self'`, which
blocks inline `<script>` outright — so the theme toggle, the language toggle
and the nav highlight were all dead on the published page while working
locally. The button labels moved into `data-` attributes on the buttons, so
the script file now carries no translated text at all.
- The guide serves its own favicon. The link pointed at `../media/favicon.png`,
which resolves above the published root — `docs/` is the site root — and only
appeared to work because the organization's site happens to serve an
Expand Down
165 changes: 165 additions & 0 deletions docs/guide.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/* =============================================================================
ci-security-scanner :: onboarding guide
-----------------------------------------------------------------------------
This lives in its own file rather than inline because the site is served
behind a Content Security Policy of `script-src 'self'`. An inline <script>
is blocked outright there; a same-origin file is not.

It is loaded from <head> without `defer` on purpose: the theme and the
language have to be stamped on <html> before the first paint, or the page
flashes the wrong one. Everything that touches the DOM waits for
DOMContentLoaded instead.

No human-readable string lives here. The button labels come from data
attributes on the buttons themselves, so every piece of translated text sits
in the HTML next to the rest of the translations.
=========================================================================== */
(function () {
"use strict";

var root = document.documentElement;

/* --- before the first paint ---------------------------------------------- */

try {
var storedTheme = localStorage.getItem("ark-theme");
if (storedTheme === "dark" || storedTheme === "light") {
root.setAttribute("data-theme", storedTheme);
root.style.colorScheme = storedTheme;
}
} catch (e) {}

try {
var storedLang = localStorage.getItem("ark-lang");
if (storedLang !== "pt" && storedLang !== "en") {
// No stored preference: follow the browser. The HTML already ships
// data-lang="pt", which is what a reader without JavaScript gets.
storedLang =
(navigator.language || "pt").toLowerCase().indexOf("pt") === 0 ? "pt" : "en";
}
root.setAttribute("data-lang", storedLang);
root.lang = storedLang === "pt" ? "pt-BR" : "en";
} catch (e) {}

/* --- state --------------------------------------------------------------- */

function currentLang() {
return root.getAttribute("data-lang") === "en" ? "en" : "pt";
}

function currentTheme() {
var attr = root.getAttribute("data-theme");
if (attr === "dark" || attr === "light") return attr;
return window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
}

function ready(fn) {
if (document.readyState !== "loading") fn();
else document.addEventListener("DOMContentLoaded", fn);
}

ready(function () {
var langBtn = document.getElementById("lang-toggle");
var themeBtn = document.getElementById("theme-toggle");

/* --- toggles ----------------------------------------------------------- */

// Each button carries its own labels, one per language, so a label change
// is a change to the HTML rather than to this file.
function paint() {
var lang = currentLang();
var theme = currentTheme();

root.lang = lang === "pt" ? "pt-BR" : "en";
root.style.colorScheme = theme;

if (langBtn) {
langBtn.textContent = langBtn.getAttribute("data-label-" + lang) || "";
langBtn.setAttribute("aria-label", langBtn.getAttribute("data-aria-" + lang) || "");
}

if (themeBtn) {
// The label names the mode the click would switch to.
var target = theme === "dark" ? "light" : "dark";
themeBtn.textContent = themeBtn.getAttribute("data-" + lang + "-" + target) || "";
}
}

function store(key, value) {
try {
localStorage.setItem(key, value);
} catch (e) {}
}

if (langBtn) {
langBtn.addEventListener("click", function () {
var next = currentLang() === "en" ? "pt" : "en";
root.setAttribute("data-lang", next);
store("ark-lang", next);
paint();
});
}

if (themeBtn) {
themeBtn.addEventListener("click", function () {
var next = currentTheme() === "dark" ? "light" : "dark";
root.setAttribute("data-theme", next);
store("ark-theme", next);
paint();
});
}

paint();

/* --- nav: mark the section being read ---------------------------------- */

// Both language lists are in the DOM and several links share an href, so the
// active class is applied to every link pointing at the current section; CSS
// hides whichever list is not in use.
var links = Array.prototype.slice.call(document.querySelectorAll("nav a"));
var byId = {};
var sections = [];

links.forEach(function (a) {
var id = a.getAttribute("href").slice(1);
var el = document.getElementById(id);
if (!el) return;
(byId[id] = byId[id] || []).push(a);
if (sections.indexOf(el) === -1) sections.push(el);
});

function setActive(id) {
links.forEach(function (a) {
a.classList.remove("is-active");
});
(byId[id] || []).forEach(function (a) {
a.classList.add("is-active");
});
}

if ("IntersectionObserver" in window) {
var visible = {};
var observer = new IntersectionObserver(
function (entries) {
entries.forEach(function (entry) {
visible[entry.target.id] = entry.isIntersecting;
});
for (var i = 0; i < sections.length; i++) {
if (visible[sections[i].id]) {
setActive(sections[i].id);
return;
}
}
},
{ rootMargin: "-15% 0px -70% 0px", threshold: 0 }
);
sections.forEach(function (section) {
observer.observe(section);
});
}

setActive(sections.length ? sections[0].id : "");
});
})();
159 changes: 10 additions & 149 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,10 @@
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="description" content="Guia de onboarding do ci-security-scanner: o que cada arquivo faz, como as fachadas GitHub e GitLab se conectam, e o porque de cada decisao.">

<!-- Aplicado antes da pintura para que tema e idioma escolhidos nao pisquem. -->
<script>
try {
var arkTheme = localStorage.getItem("ark-theme");
if (arkTheme === "dark" || arkTheme === "light") {
document.documentElement.setAttribute("data-theme", arkTheme);
document.documentElement.style.colorScheme = arkTheme;
}
} catch (e) {}

try {
var arkLang = localStorage.getItem("ark-lang");
if (arkLang !== "pt" && arkLang !== "en") {
// Sem preferencia salva: segue o navegador, com o portugues como padrao
// do documento (e o que o HTML ja carimba, para quem esta sem JS).
arkLang = (navigator.language || "pt").toLowerCase().indexOf("pt") === 0 ? "pt" : "en";
}
document.documentElement.setAttribute("data-lang", arkLang);
document.documentElement.lang = arkLang === "pt" ? "pt-BR" : "en";
} catch (e) {}
</script>
<!-- Externo, nao inline: o site e servido com uma Content Security Policy
de script-src 'self', que bloqueia <script> inline. Sem defer porque
tema e idioma precisam ser carimbados antes da primeira pintura. -->
<script src="guide.js"></script>

<style>
/* Reset minimo. */
Expand Down Expand Up @@ -652,8 +635,12 @@ <h1 lang="en">ci-security-scanner<br><span class="accent">from the inside</span>
</ul>

<div class="toggles">
<button type="button" id="lang-toggle" class="toggle-btn" aria-live="polite">English</button>
<button type="button" id="theme-toggle" class="toggle-btn" aria-live="polite">modo escuro</button>
<button type="button" id="lang-toggle" class="toggle-btn" aria-live="polite"
data-label-pt="English" data-label-en="Português"
data-aria-pt="Switch to English" data-aria-en="Mudar para português">English</button>
<button type="button" id="theme-toggle" class="toggle-btn" aria-live="polite"
data-pt-dark="modo escuro" data-pt-light="modo claro"
data-en-dark="dark mode" data-en-light="light mode">modo escuro</button>
</div>
</header>

Expand Down Expand Up @@ -2382,131 +2369,5 @@ <h3>Where to start reading the code</h3>
</main>
</div>

<script>
(function () {
var links = Array.prototype.slice.call(document.querySelectorAll('nav a'));
var map = {};
var sections = [];

links.forEach(function (a) {
var id = a.getAttribute('href').slice(1);
var el = document.getElementById(id);
if (!el) return;
map[id] = a;
sections.push(el);
});

function setActive(id) {
links.forEach(function (a) { a.classList.remove('is-active'); });
if (map[id]) map[id].classList.add('is-active');
}

if ('IntersectionObserver' in window) {
var visible = {};
var obs = new IntersectionObserver(function (entries) {
entries.forEach(function (e) { visible[e.target.id] = e.isIntersecting; });
for (var i = 0; i < sections.length; i++) {
if (visible[sections[i].id]) { setActive(sections[i].id); return; }
}
}, { rootMargin: '-15% 0px -70% 0px', threshold: 0 });
sections.forEach(function (s) { obs.observe(s); });
}

setActive(sections.length ? sections[0].id : '');
})();
</script>

<script>
/* Alternadores de tema e idioma (so nesta versao). Os tokens de cor da pagina
ja cobrem os tres estados de tema: sem carimbo, data-theme="light" e
data-theme="dark". O idioma e puramente CSS: cada bloco existe duas vezes
no DOM e o data-lang da raiz esconde um dos dois. */

var ARK_STRINGS = {
pt: {
title: "CI Security Scanner",
description: "Guia de onboarding do ci-security-scanner: o que cada arquivo faz, como as fachadas GitHub e GitLab se conectam, e o porquê de cada decisão.",
langBtn: "English",
langAria: "Switch to English",
dark: "modo escuro",
light: "modo claro"
},
en: {
title: "CI Security Scanner",
description: "Onboarding guide to ci-security-scanner: what each file does, how the GitHub and GitLab front ends connect, and the reasoning behind each decision.",
langBtn: "Português",
langAria: "Mudar para português",
dark: "dark mode",
light: "light mode"
}
};

(function () {
var root = document.documentElement;
var btn = document.getElementById("lang-toggle");
var themeBtn = document.getElementById("theme-toggle");
if (!btn) return;

function currentLang() {
return root.getAttribute("data-lang") === "en" ? "en" : "pt";
}

function paintLang() {
var lang = currentLang();
var s = ARK_STRINGS[lang];
root.lang = lang === "pt" ? "pt-BR" : "en";
btn.textContent = s.langBtn;
btn.setAttribute("aria-label", s.langAria);
document.title = s.title;
var meta = document.querySelector('meta[name="description"]');
if (meta) meta.setAttribute("content", s.description);
if (window.arkPaintTheme) window.arkPaintTheme();
}

btn.addEventListener("click", function () {
var next = currentLang() === "en" ? "pt" : "en";
root.setAttribute("data-lang", next);
try { localStorage.setItem("ark-lang", next); } catch (e) {}
paintLang();
});

window.arkCurrentLang = currentLang;
paintLang();
})();

(function () {
var root = document.documentElement;
var btn = document.getElementById("theme-toggle");
if (!btn) return;

function current() {
var attr = root.getAttribute("data-theme");
if (attr === "dark" || attr === "light") return attr;
return window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
}

function paint() {
var theme = current();
var lang = window.arkCurrentLang ? window.arkCurrentLang() : "pt";
var s = ARK_STRINGS[lang];
root.style.colorScheme = theme;
btn.textContent = theme === "dark" ? s.light : s.dark;
}

// O alternador de idioma chama isto para retraduzir o rotulo do tema.
window.arkPaintTheme = paint;

btn.addEventListener("click", function () {
var next = current() === "dark" ? "light" : "dark";
root.setAttribute("data-theme", next);
try { localStorage.setItem("ark-theme", next); } catch (e) {}
paint();
});

paint();
})();
</script>
</body>
</html>
Loading