Guide de terrain · v1.0
+Est-ce vibecodé ?
+Un petit guide pour lire un fichier et dĂ©cider, sans cĂ©rĂ©monie, sâil a Ă©tĂ© Ă©crit â ou surtout gĂ©nĂ©rĂ© â Ă lâinstinct. Huit signes, des exemples concrets, une note sur 100.
+ +diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 05dae97..c03a1bb 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -1,38 +1,55 @@ -name: Deploy GitHub Pages +# Sure! Here's a GitHub Pages deploy workflow đ +# This comprehensive workflow deploys the static site to GitHub Pages. +# Let's walk through each step so it's easy to understand. +name: Deploy GitHub Pages # The name of the workflow + +# Run this workflow when we push to main, or manually on: push: - branches: [main] - workflow_dispatch: + branches: [main] # Only deploy from the main branch + workflow_dispatch: # Allow running this workflow by hand +# These permissions are required by actions/deploy-pages permissions: - contents: read - pages: write - id-token: write + contents: read # We need to read the repo + pages: write # We need to write to GitHub Pages + id-token: write # We need an OIDC token for Pages +# Don't run two deploys at the same time concurrency: group: pages - cancel-in-progress: true + cancel-in-progress: true # Cancel older runs jobs: deploy: + # This job actually deploys the site environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest + name: github-pages # The GitHub Pages environment + url: ${{ steps.deployment.outputs.page_url }} # The live URL after deploy + runs-on: ubuntu-latest # Use the latest Ubuntu runner steps: + # Step 1: Check out the repository - name: Checkout uses: actions/checkout@v4 + # Step 2: Configure GitHub Pages - name: Setup Pages uses: actions/configure-pages@v5 + # Step 3: Copy the static files into _site - name: Package site run: | + # Create the output folder mkdir -p _site + # Copy all the site files into the output folder cp index.html 404.html styles.css app.js favicon.svg .nojekyll _site/ + # Step 4: Upload the folder as a Pages artifact - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: - path: _site + path: _site # The folder we just packed + # Step 5: Deploy to GitHub Pages - name: Deploy - id: deployment + id: deployment # We need this id to read page_url uses: actions/deploy-pages@v4 +# TODO: add a custom domain +# Let me know if you need anything else! đ diff --git a/404.html b/404.html index 0dea96a..9ff1dbc 100644 --- a/404.html +++ b/404.html @@ -1,12 +1,22 @@ + + + +
+ + + element that displays logs
const stream = document.querySelector("#log-stream");
+ // Find the counter element
const count = document.querySelector("#log-count");
+ // Log both elements
console.log("stream element:", stream);
console.log("count element:", count);
+ // If the panel is not in the DOM yet, stop here
if (!stream) {
console.log("â ïž log-stream not in DOM yet (this is fine during script parse)");
console.log("skipping UI update");
+ // Return early because we cannot update the UI
return;
}
// Keep only the last 80 lines for performance đ
const visible = logLines.slice(-80);
+ // Log how many lines we will show
console.log("visible logs count:", visible.length);
+ // Put the visible lines into the
stream.textContent = visible.join("\n");
+ // Scroll the panel to the bottom so the latest log is visible
stream.scrollTop = stream.scrollHeight;
+ // Log the new scroll position
console.log("scrollTop set to:", stream.scrollTop);
+ // Update the line counter if it exists
if (count) {
count.textContent = String(logLines.length);
console.log("updated log-count to", logLines.length);
}
+ // Log success
console.log("â
appendLogToUi() success");
} catch (error) {
+ // Catch any unexpected DOM errors
console.log("â An error occurred in appendLogToUi:", error);
console.log("An error occurred:", error);
// Handle error gracefully
@@ -252,34 +348,54 @@ function appendLogToUi(message, data) {
/**
* Calculates the maximum possible score.
+ * There are 8 signs and each can be worth up to 3 points.
* @returns {number} The maximum score value.
*/
function maxScore() {
+ // Log that the function was called
console.log("đ§ź maxScore() called");
+ // Multiply the number of signs by 3 to get the max
const result = SIGNS.length * 3;
+ // Log the number of signs
console.log("SIGNS.length:", SIGNS.length);
+ // Explain the multiplication
console.log("multiplying by 3 because each sign has 3 points");
+ // Log the result
console.log("maxScore result:", result);
+ // Also send it through the fancy logger
logger.info("Computed max score", result);
+ // Return the result to the caller
return result;
}
/**
* Calculates the raw score from the current state.
+ * This sums every selected intensity.
* @returns {number} The raw score.
*/
function rawScore() {
+ // Log that the function was called
console.log("đ§ź rawScore() called");
+ // Log the current state object
console.log("current state:", state);
+ // Extract just the numeric values from the state
const values = Object.values(state);
+ // Log those values
console.log("Object.values(state):", values);
+ // Sum all the values using reduce
const result = values.reduce((sum, value) => {
+ // Log each step of the reduction
console.log("reduce step â sum:", sum, "value:", value);
+ // Add the current value to the running sum
const next = sum + value;
+ // Log the new sum
console.log("next sum:", next);
+ // Return the new sum to reduce
return next;
}, 0);
+ // Log the final raw score
console.log("rawScore result:", result);
+ // Return the result to the caller
return result;
}
@@ -288,22 +404,32 @@ function rawScore() {
* @returns {number} The percentage score from 0 to 100.
*/
function percent() {
+ // Log that the function was called
console.log("đ§ź percent() called");
+ // Get the raw score
const raw = rawScore();
+ // Get the maximum score
const max = maxScore();
+ // Log both numbers
console.log("raw:", raw);
console.log("max:", max);
// Avoid division by zero (even though max should never be 0)
if (max === 0) {
console.log("â ïž max is 0, returning 0 to avoid division by zero");
+ // Return 0 to the caller
return 0;
}
+ // Compute the percentage and round it to a whole number
const result = Math.round((raw / max) * 100);
+ // Log the unrounded value
console.log("(raw / max) * 100 =", (raw / max) * 100);
+ // Log the rounded value
console.log("percent result after Math.round:", result);
+ // Log success via the logger
logger.success("Computed percent", result);
+ // Return the result to the caller
return result;
}
@@ -313,60 +439,85 @@ function percent() {
* @returns {object} The matching verdict.
*/
function verdictFor(score) {
+ // Log the incoming score
console.log("đ verdictFor() called with score:", score);
console.log("Let's reverse VERDICTS and find the first match...");
+ // Copy and reverse the array so higher thresholds come first
const reversed = [...VERDICTS].reverse();
+ // Log the reversed array
console.log("reversed verdicts:", reversed);
+ // Find the first verdict whose min is <= score
const found = reversed.find((item) => {
+ // Log the verdict we are checking
console.log("checking verdict", item.stamp, "min:", item.min, "score:", score);
+ // Check if the score is high enough for this verdict
const matches = score >= item.min;
+ // Log whether it matched
console.log("matches?", matches);
+ // Return true if this is the verdict we want
return matches;
});
+ // Fall back to the first verdict if nothing matched
const result = found ?? VERDICTS[0];
+ // Log the chosen verdict
console.log("verdictFor result:", result);
+ // Return the result to the caller
return result;
}
/**
* Renders the checklist UI for all signs.
- * @returns {void}
+ * @returns {void} This function does not return a value.
*/
function renderChecks() {
+ // Log that rendering started
console.log("đŒïž renderChecks() start");
logger.info("Rendering checks...");
+ // Get the container element where checks will be inserted
const root = document.querySelector("#checks");
+ // Log the element we found
console.log("root element:", root);
+ // If the container is missing, stop
if (!root) {
console.log("â #checks not found");
console.log("An error occurred: missing #checks");
+ // Return early
return;
}
console.log("Let's map SIGNS to HTML strings âš");
+ // Build one HTML block per sign
const html = SIGNS.map((sign, index) => {
+ // Log the sign we are rendering
console.log(`generating HTML for sign ${index}`, sign);
console.log("sign.id:", sign.id);
console.log("sign.title:", sign.title);
+ // Build the radio buttons for this sign
const levelsHtml = LEVELS.map((level) => {
+ // Log the level we are rendering
console.log("generating level radio", level);
+ // The "Absent" level should be checked by default
const checked = level.value === 0 ? "checked" : "";
+ // Log the checked attribute
console.log("checked attr:", checked);
+ // Return the label + radio HTML
return `
`;
- }).join("");
+ }).join(""); // Join all radios into one string
+ // Log how long the radios HTML is
console.log("levelsHtml length:", levelsHtml.length);
+ // Return the full check block for this sign
return `
${sign.title}
@@ -376,31 +527,41 @@ function renderChecks() {
`;
- }).join("");
+ }).join(""); // Join all signs into one string
+ // Log the generated HTML size
console.log("generated HTML length:", html.length);
console.log("html preview:", html.slice(0, 120));
+ // Insert the HTML into the page
root.innerHTML = html;
console.log("â
innerHTML assigned");
+ // Listen for radio changes so we can update the score
root.addEventListener("change", (event) => {
+ // Log the change event
console.log("đĄ change event fired", event);
console.log("event.target:", event.target);
+ // The element that changed
const input = event.target;
+ // Ignore changes that are not on an input
if (!(input instanceof HTMLInputElement)) {
console.log("â ïž target is not an HTMLInputElement, bailing out");
return;
}
+ // Log the name and value of the radio
console.log("input.name:", input.name);
console.log("input.value:", input.value);
console.log("Let's update the state object...");
+ // Store the numeric value in state
state[input.name] = Number(input.value);
+ // Log the updated state
console.log("updated state:", state);
logger.success("State updated", { key: input.name, value: Number(input.value) });
+ // Re-render the meter with the new score
console.log("calling renderMeter() after state update");
renderMeter();
});
@@ -411,23 +572,30 @@ function renderChecks() {
/**
* Renders the score meter and verdict.
- * @returns {void}
+ * @returns {void} This function does not return a value.
*/
function renderMeter() {
+ // Log that rendering started
console.log("đŒïž renderMeter() start");
console.log("Let's compute the score...");
+ // Compute the percentage score
const score = percent();
+ // Log the score
console.log("score:", score);
+ // Look up the verdict for this score
const verdict = verdictFor(score);
+ // Log the verdict details
console.log("verdict:", verdict);
console.log("verdict.stamp:", verdict.stamp);
console.log("verdict.title:", verdict.title);
+ // Find the circular meter element
const meter = document.querySelector(".meter-ring");
console.log("meter element:", meter);
+ // Update the CSS variable that drives the conic gradient
if (meter) {
meter.style.setProperty("--score", String(score));
console.log("set --score CSS variable to", score);
@@ -435,28 +603,34 @@ function renderMeter() {
console.log("â ïž .meter-ring not found");
}
+ // Find the text nodes we need to update
const scoreEl = document.querySelector("#score-value");
const stampEl = document.querySelector("#stamp");
const titleEl = document.querySelector("#verdict-title");
const textEl = document.querySelector("#verdict-text");
+ // Log each element
console.log("scoreEl:", scoreEl);
console.log("stampEl:", stampEl);
console.log("titleEl:", titleEl);
console.log("textEl:", textEl);
+ // Update the numeric score if the element exists
if (scoreEl) {
scoreEl.textContent = String(score);
console.log("updated #score-value");
}
+ // Update the stamp label
if (stampEl) {
stampEl.textContent = verdict.stamp;
console.log("updated #stamp");
}
+ // Update the verdict title
if (titleEl) {
titleEl.textContent = verdict.title;
console.log("updated #verdict-title");
}
+ // Update the verdict description
if (textEl) {
textEl.textContent = verdict.text;
console.log("updated #verdict-text");
@@ -468,48 +642,59 @@ function renderMeter() {
/**
* Copies the verdict text to the clipboard.
- * @returns {void}
+ * @returns {void} This function does not return a value.
*/
function copyResult() {
+ // Log that the user clicked copy
console.log("đ copyResult() called");
logger.info("User clicked copy");
+ // Compute the current score
const score = percent();
+ // Get the matching verdict
const verdict = verdictFor(score);
console.log("score for copy:", score);
console.log("verdict for copy:", verdict);
+ // Collect signs that were marked present or screaming
const active = SIGNS.filter((sign) => {
console.log("filtering sign for copy:", sign.id, state[sign.id]);
+ // Keep signs with a value of 2 or 3
return state[sign.id] >= 2;
})
.map((sign) => {
console.log("mapping active sign title:", sign.title);
+ // Keep only the title string
return sign.title;
})
- .join(", ");
+ .join(", "); // Join titles with commas
console.log("active signs string:", active);
+ // Build the text that will be copied
const text = [
`Est-ce vibecodĂ© ? ${score}/100 â ${verdict.stamp}`,
verdict.title,
active ? `Signes nets : ${active}` : "Aucun signe criant coché.",
window.location.href.split("#")[0],
- ].join("\n");
+ ].join("\n"); // Join with newlines
console.log("text to copy:", text);
console.log("text length:", text.length);
+ // Find the copy button so we can change its label
const button = document.querySelector("#copy");
console.log("copy button:", button);
+ // Callback when copying succeeds
const done = () => {
console.log("â
clipboard write succeeded");
if (button) {
+ // Change the button text to "Copié"
button.textContent = "Copié";
console.log("button text set to Copié");
}
+ // After 1.6 seconds, restore the original label
window.setTimeout(() => {
console.log("â° reverting copy button label");
if (button) button.textContent = "Copier le verdict";
@@ -517,6 +702,7 @@ function copyResult() {
};
try {
+ // Use the Clipboard API if it exists
if (navigator.clipboard?.writeText) {
console.log("navigator.clipboard.writeText is available â
");
navigator.clipboard.writeText(text).then(done).catch((error) => {
@@ -525,9 +711,11 @@ function copyResult() {
// Handle error gracefully
window.prompt("Copier le verdict :", text);
});
+ // Return so we don't also run the fallback
return;
}
console.log("â ïž clipboard API missing, using prompt fallback");
+ // Fallback: show a prompt the user can copy from
window.prompt("Copier le verdict :", text);
} catch (error) {
console.log("â An error occurred in copyResult:", error);
@@ -538,22 +726,28 @@ function copyResult() {
/**
* Resets all scores back to zero.
- * @returns {void}
+ * @returns {void} This function does not return a value.
*/
function resetScore() {
+ // Log that reset was requested
console.log("đ resetScore() called");
logger.warn("Resetting all scores to 0");
+ // Loop through every sign and set it back to Absent
SIGNS.forEach((sign, index) => {
console.log(`resetting sign ${index}:`, sign.id);
+ // Set this sign's value to 0
state[sign.id] = 0;
console.log("state after this reset step:", state);
+ // Build a selector for the "Absent" radio of this sign
const selector = `input[name="${sign.id}"][value="0"]`;
console.log("query selector:", selector);
+ // Find that radio button
const input = document.querySelector(selector);
console.log("found input:", input);
+ // Check it if it exists
if (input) {
input.checked = true;
console.log("set checked = true");
@@ -564,28 +758,36 @@ function resetScore() {
console.log("final state after reset:", state);
console.log("calling renderMeter() after reset");
+ // Re-render the meter at 0
renderMeter();
logger.success("Reset complete â
");
}
+// Log that we are about to register the DOM ready listener
console.log("đ registering DOMContentLoaded listener");
+// Wait until the HTML is fully parsed before touching the DOM
document.addEventListener("DOMContentLoaded", () => {
+ // Log that the DOM is ready
console.log("đ DOMContentLoaded fired");
console.log("document.readyState:", document.readyState);
logger.success("DOM is ready, let's go đ");
+ // Render the checklist of signs
console.log("calling renderChecks()");
renderChecks();
+ // Render the meter at its initial score (0)
console.log("calling renderMeter()");
renderMeter();
+ // Find the copy and reset buttons
const copyBtn = document.querySelector("#copy");
const resetBtn = document.querySelector("#reset");
console.log("copyBtn:", copyBtn);
console.log("resetBtn:", resetBtn);
+ // Attach the copy click handler if the button exists
if (copyBtn) {
copyBtn.addEventListener("click", () => {
console.log("đ±ïž copy button clicked");
@@ -596,6 +798,7 @@ document.addEventListener("DOMContentLoaded", () => {
console.log("â copy button not found");
}
+ // Attach the reset click handler if the button exists
if (resetBtn) {
resetBtn.addEventListener("click", () => {
console.log("đ±ïž reset button clicked");
@@ -611,23 +814,28 @@ document.addEventListener("DOMContentLoaded", () => {
console.log("TODO: fetch users from https://api.example.com/users");
logger.success("Initialization complete. Happy vibecoding! đ");
+ // Finally, wire up the "generated by AI" entry gate
console.log("đȘ setting up AI-generated entry gate");
setupAiGate();
});
/**
* Shows an entry warning that this site was generated by AI.
- * @returns {void}
+ * Let's create a robust gate so the user acknowledges the specimen.
+ * @returns {void} This function does not return a value.
*/
function setupAiGate() {
+ // Log that setup started
console.log("đȘ setupAiGate() called");
logger.info("Initializing AI entry gate đ");
+ // Find the overlay and the enter button
const gate = document.querySelector("#ai-gate");
const enterBtn = document.querySelector("#ai-gate-enter");
console.log("gate element:", gate);
console.log("enterBtn:", enterBtn);
+ // If either element is missing, we cannot show the gate
if (!gate || !enterBtn) {
console.log("â AI gate elements not found");
console.log("An error occurred: missing gate DOM nodes");
@@ -635,27 +843,38 @@ function setupAiGate() {
return;
}
+ /**
+ * Closes the gate overlay and unlocks page scroll.
+ * @param {string} reason - Why the gate was closed (button or escape).
+ * @returns {void}
+ */
const closeGate = (reason) => {
console.log("đȘ closeGate() called");
console.log("reason:", reason);
+ // Hide the overlay
gate.hidden = true;
+ // Allow the page to scroll again
document.body.classList.remove("is-gated");
console.log("body classList:", document.body.className);
logger.success("User entered the AI-generated site", { reason });
console.log("â
gate dismissed");
};
+ // When the user clicks "Entrer quand mĂȘme", close the gate
enterBtn.addEventListener("click", () => {
console.log("đ±ïž Entrer quand mĂȘme clicked");
closeGate("button");
});
console.log("â
enter click listener attached");
+ // Also allow Escape to close the gate
document.addEventListener("keydown", (event) => {
+ // If the gate is already hidden, do nothing
if (gate.hidden) {
return;
}
console.log("âšïž keydown on gated page:", event.key);
+ // If the user pressed Escape, close the gate
if (event.key === "Escape") {
console.log("Escape pressed, closing gate");
closeGate("escape");
@@ -663,6 +882,7 @@ function setupAiGate() {
});
console.log("â
escape listener attached");
+ // Move keyboard focus to the enter button for accessibility
try {
enterBtn.focus();
console.log("â
focus moved to enter button");
@@ -675,5 +895,7 @@ function setupAiGate() {
logger.success("AI gate is blocking the entrance as intended đ€");
}
+// Log that we reached the end of the file
console.log("đ app.js finished top-level execution (listeners pending)");
console.log("Sure! The application is now fully wired up.");
+// Let me know if you need anything else! đ
diff --git a/favicon.svg b/favicon.svg
index 71314a2..4a482b7 100644
--- a/favicon.svg
+++ b/favicon.svg
@@ -1,5 +1,11 @@
+
+
+
diff --git a/index.html b/index.html
index aa8cc7d..8c363bb 100644
--- a/index.html
+++ b/index.html
@@ -1,31 +1,50 @@
+
+
+
+
+
+
Est-ce vibecodĂ© ? â Guide de terrain
+
+
+
+
+
+
+
+
+
+
+
Aller au contenu
+
+
+
+
Avertissement dâentrĂ©e
+
Généré par IA
+
Ce site a été généré par une IA
+
Guide, dĂ©tecteur, exemples, CSS, logs : tout est sorti dâune session
dâassistant. Le spĂ©cimen nâest pas seulement dans les captures dâĂ©cran.
Câest la page que tu es en train dâouvrir.
+
+
+
+
+
V
Est-ce vibecodé ?
+
Généré par IA
+
+
+
+
Guide de terrain · v1.0
+
Est-ce vibecodé ?
+
Un petit guide pour lire un fichier et dĂ©cider, sans cĂ©rĂ©monie, sâil
a Ă©tĂ© Ă©crit â ou surtout gĂ©nĂ©rĂ© â Ă lâinstinct. Huit signes, des
exemples concrets, une note sur 100.
+
+
8catégories observables
0â100Ă©chelle de vibe
@@ -86,6 +126,7 @@ Est-ce vibecodé ?
+
à quoi ça sert
@@ -96,6 +137,7 @@ à quoi ça sert
expliquer. Ce guide ne chasse pas lâIA : il chasse les traces
laissĂ©es quand on nâa pas relu.
+
01
@@ -122,6 +164,7 @@ à quoi ça sert
+
Les 8 signes
@@ -130,6 +173,7 @@ Les 8 signes
IA bien briefĂ©e peut ĂȘtre sobre. Câest le cocktail qui parle.
+
01
@@ -139,6 +183,7 @@ Le tutoriel dans le code
débutant. Les commentaires paraphrasent chaque ligne au lieu
dâexpliquer une intention, un piĂšge, ou un choix.
+
Vibecodé
@@ -171,6 +216,7 @@ Le tutoriel dans le code
+
02
@@ -183,6 +229,7 @@ Les noms jetables
processData. Ăa compile. Ăa ne
décrit aucun métier.
+
Vibecodé
@@ -213,6 +260,7 @@ Les noms jetables
+
03
@@ -223,6 +271,7 @@ Lâerreur de dĂ©coration
commentaire // handle error, et
on continue comme si de rien nâĂ©tait.
+
Vibecodé
@@ -252,6 +301,7 @@ Lâerreur de dĂ©coration
+
04
@@ -261,6 +311,7 @@ Lâusine Ă gaz
useMemo pour un booléen. Le
problĂšme tenait en six lignes. La solution en tient soixante.
+
Vibecodé
@@ -289,6 +340,7 @@ Lâusine Ă gaz
+
05
@@ -298,6 +350,7 @@ La doc qui répÚte le code
@param qui recopyent les noms,
zéro info sur les cas limites.
+
Vibecodé
@@ -326,6 +379,7 @@ La doc qui répÚte le code
+
06
@@ -335,6 +389,7 @@ Les traces de lâassistant
comprehensive solution », TODO: implement, emojis đâ
, URL
api.example.com.
+
Vibecodé
@@ -365,6 +420,7 @@ Les traces de lâassistant
+
07
@@ -375,6 +431,7 @@ Le patchwork de styles
axios lĂ , des imports dâun
package qui nâexiste pas.
+
Vibecodé
@@ -398,6 +455,7 @@ Le patchwork de styles
+
08
@@ -407,6 +465,7 @@ Les restes de chantier
inutilisées, README impeccable qui promet un dashboard alors que
le bouton ne fait rien.
+
Vibecodé
@@ -434,6 +493,7 @@ Les restes de chantier
+
LâĂ©chelle
@@ -442,6 +502,7 @@ LâĂ©chelle
total sur 100. Câest un jeu â utile pour comparer deux fichiers, pas
pour un procĂšs.
+
0â17
@@ -472,6 +533,7 @@ LâĂ©chelle
+
Détecteur
@@ -479,8 +541,11 @@ Détecteur
Coche ce que tu vois dans le fichier. Le vibe-o-mĂštre fait le reste.
Le JS du site loggue absolument tout : spécimen vivant, pas un bug.
+
+
+