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 @@ + + + + + + + Page introuvable + +

Retour au guide

+ diff --git a/app.js b/app.js index 136ae31..72f1e11 100644 --- a/app.js +++ b/app.js @@ -1,17 +1,25 @@ // Sure! Here's a robust, production-ready vibe detector with comprehensive logging 🚀 // This comprehensive solution handles all edge cases and provides extensive debug output. // Let's walk through the implementation step by step. +// Of course! Feel free to copy-paste this into your project. +// Author: an AI assistant (you're welcome!) +// Log that the script file has started executing console.log("🚀 Starting vibe detector application..."); +// Log that we would load modules if this weren't a vanilla JS file console.log("📩 Loading modules... (there are no modules)"); +// Log that evaluation of this file succeeded console.log("✅ app.js has been evaluated successfully"); // Let's initialize the logs array early so we can log ALL THE THINGS ✹ +// This array will store every log line as a string const logLines = []; +// Log the newly created empty array to the console console.log("📝 logLines array initialized:", logLines); /** * Logs a message to the console for debugging purposes. + * This function is a comprehensive logging utility. * @param {string} message - The message to log to the console. * @param {*} data - Optional data to log to the console. * @returns {void} This function does not return anything. @@ -19,28 +27,40 @@ console.log("📝 logLines array initialized:", logLines); function logDebug(message, data) { // Let's log the incoming arguments so we can debug more easily console.log("🔍 logDebug() called"); + // Log the message parameter console.log("📝 message:", message); + // Log the data parameter console.log("📊 data:", data); + // Log the current timestamp in ISO format console.log("⏰ timestamp:", new Date().toISOString()); + // Wrap everything in try/catch to handle errors gracefully try { // Handle the case where data is undefined if (typeof data === "undefined") { + // Data was not provided, so we only log the message console.log("⚠ data is undefined, logging message only"); + // Print the message itself console.log(message); } else { + // Data was provided, so we log both values console.log("✅ data is defined, logging message AND data"); + // Print message and data together console.log(message, data); } // Also pretty-print objects because that's helpful ✹ if (data !== null && typeof data === "object") { + // Convert the object to JSON and log it console.log("đŸ§© JSON.stringify(data):", JSON.stringify(data)); } + // Also append the same information to the on-page log panel appendLogToUi(message, data); + // Log that this function completed successfully console.log("🎉 logDebug() completed successfully"); } catch (error) { + // Something went wrong while logging (ironic, we know) console.log("❌ An error occurred in logDebug:", error); console.log("An error occurred:", error); // Handle error gracefully @@ -49,26 +69,39 @@ function logDebug(message, data) { /** * Helper function to create a logger factory. - * @returns {Function} A logger function that logs things. + * This factory creates loggers that can log info, success, warn, and error. + * @returns {object} An object with a createLogger method. */ const createLoggerFactory = () => { + // Log that the factory function was invoked console.log("🏭 createLoggerFactory() invoked"); + // Return the factory object return { + /** + * Creates a new logger instance. + * @returns {object} A logger with info/success/warn/error methods. + */ createLogger: () => { + // Log that we are creating a logger console.log("đŸ› ïž createLogger() invoked"); + // Return the logger API object return { + // Log an informational message info: (msg, data) => { console.log("â„č logger.info"); logDebug("â„č " + msg, data); }, + // Log a success message success: (msg, data) => { console.log("✅ logger.success"); logDebug("✅ " + msg, data); }, + // Log a warning message warn: (msg, data) => { console.log("⚠ logger.warn"); logDebug("⚠ " + msg, data); }, + // Log an error message error: (msg, data) => { console.log("❌ logger.error"); logDebug("❌ " + msg, data); @@ -78,16 +111,29 @@ const createLoggerFactory = () => { }; }; +// Instantiate the logger factory const loggerFactory = createLoggerFactory(); +// Log the factory so we can inspect it console.log("loggerFactory:", loggerFactory); +// Create the actual logger instance we will use everywhere const logger = loggerFactory.createLogger(); +// Log the logger instance console.log("logger:", logger); +// Announce that logging is ready logger.success("Logger initialized successfully 🚀"); +/** + * The list of vibe signs that the user can score. + * Each sign has an id, a title, and a hint. + * @type {Array<{id: string, title: string, hint: string}>} + */ const SIGNS = [ { + // Unique identifier for this sign id: "comments", + // Human-readable title shown in the UI title: "Le tutoriel dans le code", + // Short explanation shown under the title hint: "Chaque ligne a son commentaire d’évidence.", }, { @@ -127,27 +173,45 @@ const SIGNS = [ }, ]; +// Log how many signs we loaded console.log("📋 SIGNS loaded, length =", SIGNS.length); +// Log the full SIGNS array console.log("📋 SIGNS data:", SIGNS); +// Loop through each sign and log it individually SIGNS.forEach((item, index) => { + // Log the current item and its index console.log(`âžĄïž processing sign at index ${index}`, item); + // Log the id property console.log("id:", item.id); + // Log the title property console.log("title:", item.title); }); +/** + * Intensity levels the user can pick for each sign. + * 0 = absent, 1 = light, 2 = present, 3 = screaming. + * @type {Array<{value: number, label: string}>} + */ const LEVELS = [ - { value: 0, label: "Absent" }, - { value: 1, label: "LĂ©ger" }, - { value: 2, label: "PrĂ©sent" }, - { value: 3, label: "Criant" }, + { value: 0, label: "Absent" }, // not present + { value: 1, label: "LĂ©ger" }, // a little bit present + { value: 2, label: "PrĂ©sent" }, // clearly present + { value: 3, label: "Criant" }, // painfully present ]; +// Log the LEVELS array console.log("đŸŽšïž LEVELS:", LEVELS); +// Log the number of levels console.log("đŸŽšïž LEVELS.length:", LEVELS.length); +/** + * Verdicts mapped to score thresholds. + * We pick the last verdict whose min is <= the score. + * @type {Array<{min: number, stamp: string, title: string, text: string}>} + */ const VERDICTS = [ { - min: 0, + min: 0, // minimum score for this verdict stamp: "Artisan", title: "Code avec une Ăąme", text: "Peu de tics d’assistant. Soit quelqu’un a Ă©crit ça, soit quelqu’un a eu le courage de relire.", @@ -178,72 +242,104 @@ const VERDICTS = [ }, ]; +// Log the verdicts configuration console.log("🏁 VERDICTS loaded:", VERDICTS); // Let's initialize the state object to keep track of user selections +// Each sign id starts at 0 (Absent) const state = Object.fromEntries(SIGNS.map((sign) => [sign.id, 0])); +// Log the initial state object console.log("🧠 initial state:", state); +// Classic leftover debug log console.log("here"); /** * Appends a log line to the on-page debug console. + * This makes the leftover console.log visible without opening DevTools. * @param {string} message - The log message. * @param {*} data - Optional extra data. - * @returns {void} + * @returns {void} This function does not return a value. */ function appendLogToUi(message, data) { + // Log that we entered this function console.log("đŸ–„ïž appendLogToUi() start"); + // Log the message argument console.log("message param:", message); + // Log the data argument console.log("data param:", data); + // Try to update the DOM, but don't crash if something is missing try { + // Get the current time as an ISO string const time = new Date().toISOString(); + // Log the computed time console.log("computed time:", time); + // Build the log line starting with the timestamp and message let line = `[${time}] ${message}`; + // Log the line before we add data console.log("line before data:", line); + // If data was provided, append it to the line if (typeof data !== "undefined") { console.log("data is present, concatenating..."); try { + // If data is an object, JSON.stringify it, otherwise convert to string line += " " + (typeof data === "object" ? JSON.stringify(data) : String(data)); + // Log the completed line console.log("line after data:", line); } catch (stringifyError) { + // JSON.stringify can fail on circular objects console.log("An error occurred while stringifying:", stringifyError); // Handle error line += " [unserializable data]"; } } + // Push the finished line into the in-memory array logLines.push(line); + // Log the new length of the array console.log("logLines.length is now:", logLines.length); + // Log the last line we just added console.log("last log line:", logLines[logLines.length - 1]); + // Find the
 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 @@ + + + + + V + 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 + + + + + + + + + + + + +
+
+

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.

+
+ + +
+
+

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.

+
+
+
+
console.log leftover · do not ship 🚀 @@ -513,6 +580,7 @@

Code avec une Ăąme

+

Publier sur GitHub Pages

@@ -563,12 +631,16 @@

Publier sur GitHub Pages

+
Guide vibecode · lecture de traces, pas de morale. MIT · ElectroLynx
+ + + diff --git a/styles.css b/styles.css index bf3e589..06b278f 100644 --- a/styles.css +++ b/styles.css @@ -1,36 +1,47 @@ +/* Sure! Here's a comprehensive, production-ready stylesheet 🚀 */ +/* This CSS file styles the entire vibe detector application. */ +/* Let's walk through each rule step by step so it's easy to understand. */ +/* Of course, feel free to customize the colors to match your brand. */ +/* TODO: add dark/light theme toggle */ +/* TODO: add your custom font here */ + +/* This styles :root. Let's make it look polished! */ :root { - --bg: #11110f; - --bg-2: #181714; - --paper: #1f1d19; - --ink: #f3efe4; - --muted: #9a9486; - --faint: #5c574e; - --line: #2c2a25; - --vibe: #e7ff3d; - --vibe-dim: #c6d94a; - --alert: #ff5a36; - --ok: #8dffc1; - --bad: #ff8a74; - --stamp: #ff5a36; - --shadow: 0 24px 60px rgba(0, 0, 0, 0.35); - --mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace; - --sans: "Bricolage Grotesque", "Segoe UI", sans-serif; - --serif: "Source Serif 4", Georgia, serif; - --radius: 18px; - --max: 1120px; -} - + --bg: #11110f; /* dark background color */ + --bg-2: #181714; /* slightly lighter background */ + --paper: #1f1d19; /* card / paper surface */ + --ink: #f3efe4; /* main text color */ + --muted: #9a9486; /* secondary text color */ + --faint: #5c574e; /* even more muted text */ + --line: #2c2a25; /* border color */ + --vibe: #e7ff3d; /* accent highlighter color */ + --vibe-dim: #c6d94a; /* dimmer accent */ + --alert: #ff5a36; /* alert / stamp color */ + --ok: #8dffc1; /* success / "good example" color */ + --bad: #ff8a74; /* error / "bad example" color */ + --stamp: #ff5a36; /* stamp border color */ + --shadow: 0 24px 60px rgba(0, 0, 0, 0.35); /* card shadow */ + --mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace; /* code font */ + --sans: "Bricolage Grotesque", "Segoe UI", sans-serif; /* heading font */ + --serif: "Source Serif 4", Georgia, serif; /* body font */ + --radius: 18px; /* default border radius */ + --max: 1120px; /* max content width */ +} + +/* This styles all elements and pseudo-elements. Let's reset box-sizing! */ *, *::before, *::after { - box-sizing: border-box; + box-sizing: border-box; /* include padding and border in the element's width */ } +/* This styles html. Let's make it look polished! */ html { scroll-behavior: smooth; color-scheme: dark; } +/* This styles body. Let's make it look polished! */ body { margin: 0; color: var(--ink); @@ -43,6 +54,7 @@ body { line-height: 1.65; } +/* This styles body::before. Let's make it look polished! */ body::before { content: ""; position: fixed; @@ -53,24 +65,29 @@ body::before { z-index: 0; } +/* This styles img. Let's make it look polished! */ img { max-width: 100%; } +/* This styles a. Let's make it look polished! */ a { color: var(--vibe); text-underline-offset: 0.18em; } +/* This styles a:hover. Let's make it look polished! */ a:hover { color: var(--ink); } +/* This styles :focus-visible. Let's make it look polished! */ :focus-visible { outline: 2px solid var(--vibe); outline-offset: 3px; } +/* This styles .skip. Let's make it look polished! */ .skip { position: absolute; left: 1rem; @@ -83,15 +100,18 @@ a:hover { z-index: 50; } +/* This styles .skip:focus. Let's make it look polished! */ .skip:focus { top: 1rem; } +/* This styles .wrap. Let's make it look polished! */ .wrap { width: min(var(--max), calc(100% - 2rem)); margin-inline: auto; } +/* This styles .site-header. Let's make it look polished! */ .site-header { position: sticky; top: 0; @@ -101,6 +121,7 @@ a:hover { border-bottom: 1px solid var(--line); } +/* This styles .site-header .wrap. Let's make it look polished! */ .site-header .wrap { display: flex; align-items: center; @@ -109,6 +130,7 @@ a:hover { min-height: 4.1rem; } +/* This styles .brand. Let's make it look polished! */ .brand { display: flex; align-items: center; @@ -120,6 +142,7 @@ a:hover { letter-spacing: -0.03em; } +/* This styles .mark. Let's make it look polished! */ .mark { display: grid; place-items: center; @@ -133,6 +156,7 @@ a:hover { font-weight: 500; } +/* This styles .nav. Let's make it look polished! */ .nav { display: flex; gap: 1.1rem; @@ -140,15 +164,18 @@ a:hover { font-size: 0.92rem; } +/* This styles .nav a. Let's make it look polished! */ .nav a { color: var(--muted); text-decoration: none; } +/* This styles .nav a:hover. Let's make it look polished! */ .nav a:hover { color: var(--ink); } +/* This styles .ai-badge. Let's make it look polished! */ .ai-badge { display: inline-flex; align-items: center; @@ -163,6 +190,7 @@ a:hover { text-transform: uppercase; } +/* This styles .ai-gate. Let's make it look polished! */ .ai-gate { position: fixed; inset: 0; @@ -176,10 +204,12 @@ a:hover { backdrop-filter: blur(18px); } +/* This styles .ai-gate[hidden]. Let's make it look polished! */ .ai-gate[hidden] { display: none; } +/* This styles .ai-gate__card. Let's make it look polished! */ .ai-gate__card { width: min(34rem, 100%); border: 1px solid var(--line); @@ -189,6 +219,7 @@ a:hover { box-shadow: var(--shadow); } +/* This styles .ai-gate__card h2. Let's make it look polished! */ .ai-gate__card h2 { margin: 0.85rem 0 0.7rem; font-family: var(--sans); @@ -197,20 +228,24 @@ a:hover { line-height: 1.1; } +/* This styles .ai-gate__card p#ai-gate-text. Let's make it look polished! */ .ai-gate__card p#ai-gate-text { margin: 0 0 1.3rem; color: var(--muted); } +/* This styles body.is-gated. Let's make it look polished! */ body.is-gated { overflow: hidden; } +/* This styles .hero. Let's make it look polished! */ .hero { position: relative; padding: 4.5rem 0 3.5rem; } +/* This styles .kicker. Let's make it look polished! */ .kicker { display: inline-flex; align-items: center; @@ -223,6 +258,7 @@ body.is-gated { text-transform: uppercase; } +/* This styles .kicker::before. Let's make it look polished! */ .kicker::before { content: ""; width: 1.6rem; @@ -230,6 +266,7 @@ body.is-gated { background: var(--vibe); } +/* This styles .hero h1. Let's make it look polished! */ .hero h1 { margin: 0; max-width: 14ch; @@ -240,6 +277,7 @@ body.is-gated { line-height: 0.92; } +/* This styles .lede. Let's make it look polished! */ .lede { max-width: 42rem; margin: 1.4rem 0 0; @@ -247,6 +285,7 @@ body.is-gated { font-size: 1.2rem; } +/* This styles .hero-actions. Let's make it look polished! */ .hero-actions { display: flex; flex-wrap: wrap; @@ -254,6 +293,7 @@ body.is-gated { margin-top: 2rem; } +/* This styles .btn. Let's make it look polished! */ .btn { display: inline-flex; align-items: center; @@ -272,22 +312,26 @@ body.is-gated { appearance: none; } +/* This styles .btn:hover. Let's make it look polished! */ .btn:hover { color: #14140f; filter: brightness(1.05); } +/* This styles .btn--ghost. Let's make it look polished! */ .btn--ghost { background: transparent; border-color: var(--line); color: var(--ink); } +/* This styles .btn--ghost:hover. Let's make it look polished! */ .btn--ghost:hover { color: var(--ink); border-color: var(--muted); } +/* This styles .stats. Let's make it look polished! */ .stats { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); @@ -297,25 +341,30 @@ body.is-gated { border-top: 1px solid var(--line); } +/* This styles .stat. Let's make it look polished! */ .stat { font-family: var(--sans); } +/* This styles .stat b. Let's make it look polished! */ .stat b { display: block; font-size: 1.7rem; letter-spacing: -0.04em; } +/* This styles .stat span. Let's make it look polished! */ .stat span { color: var(--muted); font-size: 0.92rem; } +/* This styles .section. Let's make it look polished! */ .section { padding: 3.4rem 0; } +/* This styles .section h2. Let's make it look polished! */ .section h2 { margin: 0 0 0.7rem; font-family: var(--sans); @@ -324,12 +373,14 @@ body.is-gated { line-height: 1.1; } +/* This styles .section > .wrap > p (and following selectors). Let's make it look polished! */ .section > .wrap > p, .prose { max-width: 44rem; color: color-mix(in srgb, var(--ink) 88%, var(--muted)); } +/* This styles .steps. Let's make it look polished! */ .steps { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); @@ -337,6 +388,7 @@ body.is-gated { margin-top: 1.8rem; } +/* This styles .step. Let's make it look polished! */ .step { background: var(--paper); border: 1px solid var(--line); @@ -344,6 +396,7 @@ body.is-gated { padding: 1.1rem 1.15rem 1.2rem; } +/* This styles .step strong. Let's make it look polished! */ .step strong { display: block; margin-bottom: 0.35rem; @@ -351,12 +404,14 @@ body.is-gated { font-size: 1.05rem; } +/* This styles .step span. Let's make it look polished! */ .step span { color: var(--vibe-dim); font-family: var(--mono); font-size: 0.75rem; } +/* This styles .sign. Let's make it look polished! */ .sign { display: grid; grid-template-columns: 7.5rem 1fr; @@ -366,6 +421,7 @@ body.is-gated { border-top: 1px solid var(--line); } +/* This styles .sign-index. Let's make it look polished! */ .sign-index { font-family: var(--sans); font-size: 2.4rem; @@ -375,6 +431,7 @@ body.is-gated { line-height: 1; } +/* This styles .sign h3. Let's make it look polished! */ .sign h3 { margin: 0 0 0.45rem; font-family: var(--sans); @@ -382,17 +439,20 @@ body.is-gated { letter-spacing: -0.03em; } +/* This styles .sign .why. Let's make it look polished! */ .sign .why { margin: 0 0 1.15rem; color: var(--muted); } +/* This styles .compare. Let's make it look polished! */ .compare { display: grid; grid-template-columns: 1fr 1fr; gap: 0.85rem; } +/* This styles .panel. Let's make it look polished! */ .panel { overflow: hidden; border: 1px solid var(--line); @@ -400,6 +460,7 @@ body.is-gated { background: #141311; } +/* This styles .panel header. Let's make it look polished! */ .panel header { display: flex; align-items: center; @@ -412,14 +473,17 @@ body.is-gated { text-transform: uppercase; } +/* This styles .panel--bad header. Let's make it look polished! */ .panel--bad header { color: var(--bad); } +/* This styles .panel--ok header. Let's make it look polished! */ .panel--ok header { color: var(--ok); } +/* This styles pre. Let's make it look polished! */ pre { margin: 0; padding: 0.95rem 0.9rem 1.05rem; @@ -430,19 +494,23 @@ pre { line-height: 1.55; } +/* This styles ::selection. Let's make it look polished! */ ::selection { background: var(--vibe); color: #111; } +/* This styles .c. Let's make it look polished! */ .c { color: #8d9a6c; } +/* This styles .kw. Let's make it look polished! */ .kw { color: #f0c36a; } +/* This styles .tip. Let's make it look polished! */ .tip { margin: 0.9rem 0 0; padding: 0.7rem 0.85rem; @@ -452,12 +520,14 @@ pre { font-size: 0.95rem; } +/* This styles .scale. Let's make it look polished! */ .scale { display: grid; gap: 0.7rem; margin-top: 1.4rem; } +/* This styles .scale-row. Let's make it look polished! */ .scale-row { display: grid; grid-template-columns: 6.5rem 1fr auto; @@ -469,15 +539,18 @@ pre { background: var(--paper); } +/* This styles .scale-row b. Let's make it look polished! */ .scale-row b { font-family: var(--sans); } +/* This styles .scale-row span. Let's make it look polished! */ .scale-row span { color: var(--muted); font-size: 0.95rem; } +/* This styles .pill. Let's make it look polished! */ .pill { font-family: var(--mono); font-size: 0.75rem; @@ -487,12 +560,14 @@ pre { padding: 0.2rem 0.55rem; } +/* This styles .detector. Let's make it look polished! */ .detector { background: linear-gradient(180deg, color-mix(in srgb, var(--paper) 70%, transparent), transparent); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); } +/* This styles .detector-grid. Let's make it look polished! */ .detector-grid { display: grid; grid-template-columns: 1.4fr 0.9fr; @@ -500,6 +575,7 @@ pre { align-items: start; } +/* This styles .card. Let's make it look polished! */ .card { background: var(--paper); border: 1px solid var(--line); @@ -508,6 +584,7 @@ pre { box-shadow: var(--shadow); } +/* This styles .check. Let's make it look polished! */ .check { display: grid; gap: 0.55rem; @@ -515,28 +592,33 @@ pre { border-bottom: 1px solid var(--line); } +/* This styles .check:last-child. Let's make it look polished! */ .check:last-child { border-bottom: 0; } +/* This styles .check p. Let's make it look polished! */ .check p { margin: 0.15rem 0 0; color: var(--muted); font-size: 0.95rem; } +/* This styles .check h4. Let's make it look polished! */ .check h4 { margin: 0; font-family: var(--sans); font-size: 1.05rem; } +/* This styles .levels. Let's make it look polished! */ .levels { display: flex; flex-wrap: wrap; gap: 0.4rem; } +/* This styles .levels label. Let's make it look polished! */ .levels label { display: inline-flex; align-items: center; @@ -550,15 +632,18 @@ pre { cursor: pointer; } +/* This styles .levels input. Let's make it look polished! */ .levels input { accent-color: var(--vibe); } +/* This styles .meter-card. Let's make it look polished! */ .meter-card { position: sticky; top: 5.2rem; } +/* This styles .meter-ring. Let's make it look polished! */ .meter-ring { display: grid; place-items: center; @@ -570,6 +655,7 @@ pre { conic-gradient(var(--vibe) calc(var(--score) * 1%), #2a2823 0); } +/* This styles .meter-inner. Let's make it look polished! */ .meter-inner { display: grid; place-items: center; @@ -580,6 +666,7 @@ pre { text-align: center; } +/* This styles .meter-inner strong. Let's make it look polished! */ .meter-inner strong { font-family: var(--sans); font-size: 2.6rem; @@ -587,27 +674,32 @@ pre { line-height: 1; } +/* This styles .meter-inner span. Let's make it look polished! */ .meter-inner span { color: var(--muted); font-family: var(--mono); font-size: 0.75rem; } +/* This styles .verdict. Let's make it look polished! */ .verdict { margin: 0.4rem 0 1rem; } +/* This styles .verdict h3. Let's make it look polished! */ .verdict h3 { margin: 0 0 0.35rem; font-family: var(--sans); font-size: 1.35rem; } +/* This styles .verdict p. Let's make it look polished! */ .verdict p { margin: 0; color: var(--muted); } +/* This styles .stamp. Let's make it look polished! */ .stamp { display: inline-block; margin-bottom: 0.55rem; @@ -622,12 +714,14 @@ pre { transform: rotate(-7deg); } +/* This styles .actions. Let's make it look polished! */ .actions { display: flex; flex-wrap: wrap; gap: 0.5rem; } +/* This styles .pages. Let's make it look polished! */ .pages { display: grid; grid-template-columns: 1.1fr 0.9fr; @@ -635,21 +729,25 @@ pre { margin-top: 1.5rem; } +/* This styles .pages ol. Let's make it look polished! */ .pages ol { margin: 0; padding-left: 1.2rem; } +/* This styles .pages li + li. Let's make it look polished! */ .pages li + li { margin-top: 0.55rem; } +/* This styles .code-inline. Let's make it look polished! */ .code-inline { font-family: var(--mono); font-size: 0.85em; color: var(--vibe); } +/* This styles .ai-logs. Let's make it look polished! */ .ai-logs { margin-top: 1.2rem; overflow: hidden; @@ -658,6 +756,7 @@ pre { background: #0d0c0a; } +/* This styles .ai-logs header. Let's make it look polished! */ .ai-logs header { display: flex; justify-content: space-between; @@ -671,6 +770,7 @@ pre { text-transform: uppercase; } +/* This styles #log-stream. Let's make it look polished! */ #log-stream { max-height: 220px; padding: 0.75rem 0.85rem 1rem; @@ -681,12 +781,14 @@ pre { white-space: pre-wrap; } +/* This styles .site-footer. Let's make it look polished! */ .site-footer { padding: 2.2rem 0 2.8rem; color: var(--muted); font-size: 0.95rem; } +/* This styles .site-footer .wrap. Let's make it look polished! */ .site-footer .wrap { display: flex; justify-content: space-between; @@ -694,7 +796,9 @@ pre { flex-wrap: wrap; } +/* This styles @media (max-width: 900px). Let's make it look polished! */ @media (max-width: 900px) { +/* This styles .steps (and following selectors). Let's make it look polished! */ .steps, .compare, .detector-grid, @@ -705,16 +809,20 @@ pre { grid-template-columns: 1fr; } +/* This styles .nav. Let's make it look polished! */ .nav { display: none; } +/* This styles .meter-card. Let's make it look polished! */ .meter-card { position: static; } } +/* This styles @media (prefers-reduced-motion: reduce). Let's make it look polished! */ @media (prefers-reduced-motion: reduce) { +/* This styles html. Let's make it look polished! */ html { scroll-behavior: auto; }