Download index.html from StylisticCommitmentResearch/Predictor: direct link, hf CLI and curl.
- Browser
- Download file 18.8 kB
-
https://huggingface.co/spaces/StylisticCommitmentResearch/Predictor/resolve/main/index.html
- Command line
-
hf download hf://spaces/StylisticCommitmentResearch/Predictor/index.html
-
curl -L -o index.html https://huggingface.co/spaces/StylisticCommitmentResearch/Predictor/resolve/main/index.html
18.8 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | |
| <meta name="color-scheme" content="light" /> | |
| <meta name="description" content="Estimate the minimalism–maximalism stylistic position of a visual design." /> | |
| <title>Commitment over Compromise — Stylistic Commitment Research</title> | |
| <style> | |
| :root { | |
| --navy: #193B63; | |
| --navy-dark: #112C4B; | |
| --ink: #202B3B; | |
| --muted: #617085; | |
| --line: #DDE4EB; | |
| --background: #F8FAFC; | |
| --white: #FFFFFF; | |
| --blue: #9AB4CC; | |
| --gold: #B99A63; | |
| } | |
| *, *::before, *::after { box-sizing: border-box; } | |
| html { font-size: 16px; } | |
| body { | |
| margin: 0; | |
| background: var(--background); | |
| color: var(--ink); | |
| font-family: Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, | |
| "Segoe UI", Arial, sans-serif; | |
| line-height: 1.55; | |
| } | |
| button, input { font: inherit; } | |
| button:focus-visible, .dropzone:focus-visible { | |
| outline: 3px solid #7AA8D0; outline-offset: 3px; | |
| } | |
| .container { width: min(100% - 40px, 1200px); margin: 0 auto; } | |
| header { text-align: center; padding: 9px 0 11px; } | |
| h1 { | |
| max-width: 1060px; | |
| margin: 0 auto 7px; | |
| color: var(--navy-dark); | |
| font-size: clamp(1.85rem, 2.55vw, 2.19rem); | |
| line-height: 1.18; | |
| letter-spacing: -.032em; | |
| font-weight: 750; | |
| } | |
| .intro { | |
| max-width: 920px; | |
| margin: 6px auto 0; | |
| color: #455366; | |
| font-size: 1.02rem; | |
| } | |
| main { padding-bottom: 16px; } | |
| .workspace { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 16px; align-items: stretch; } | |
| .panel { | |
| min-width: 0; | |
| padding: 16px 19px; | |
| background: var(--white); | |
| border: 1px solid var(--line); | |
| border-radius: 14px; | |
| } | |
| h2 { margin: 0 0 9px; font-size: 1.16rem; color: var(--navy-dark); font-weight: 750; letter-spacing: -.013em; } | |
| #upload-heading, #results-heading { font-size: 1.42rem; line-height: 1.3; } | |
| .dropzone { | |
| position: relative; | |
| display: flex; | |
| min-height: 219px; | |
| align-items: center; | |
| justify-content: center; | |
| padding: 22px; | |
| border: 1.5px dashed #AEBBC8; | |
| border-radius: 10px; | |
| background: #FCFDFE; | |
| cursor: pointer; | |
| overflow: hidden; | |
| transition: border-color 120ms ease, background 120ms ease; | |
| } | |
| .dropzone:hover, .dropzone.dragging { border-color: var(--navy); background: #F1F6FA; } | |
| .dropzone input { display: none; } | |
| .drop-prompt { text-align: center; } | |
| .upload-icon { color: var(--navy); display: block; margin: 0 auto 14px; } | |
| .upload-title { margin: 0; color: var(--navy-dark); font-weight: 650; } | |
| .upload-detail { margin: 5px 0 0; color: var(--muted); font-size: .9rem; } | |
| .preview { | |
| display: none; | |
| max-width: 100%; | |
| width: 100%; | |
| height: 205px; | |
| object-fit: contain; | |
| } | |
| .dropzone.has-image { background: #FFFFFF; } | |
| .dropzone.has-image .drop-prompt { display: none; } | |
| .dropzone.has-image .preview { display: block; } | |
| .filename { min-height: 21px; margin: 7px 1px 9px; font-size: .81rem; color: var(--muted); overflow-wrap: anywhere; } | |
| .primary { | |
| display: block; | |
| width: 100%; | |
| padding: 10px 16px; | |
| border: 1px solid var(--navy); | |
| border-radius: 9px; | |
| color: white; | |
| background: var(--navy); | |
| font-weight: 650; | |
| cursor: pointer; | |
| transition: background 120ms ease; | |
| } | |
| .primary:hover:not(:disabled) { background: var(--navy-dark); } | |
| .primary:disabled { opacity: .48; cursor: not-allowed; } | |
| .message { min-height: 18px; margin: 7px 0 0; color: var(--muted); font-size: .84rem; } | |
| .message.error { color: #A43636; } | |
| .empty-result { | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| min-height: 235px; | |
| border: 1px dashed var(--line); | |
| border-radius: 10px; | |
| text-align: center; | |
| color: var(--muted); | |
| padding: 28px; | |
| } | |
| /* Match the upload card's height; distribute result blocks vertically. */ | |
| .workspace > .panel:last-child { display: flex; flex-direction: column; } | |
| .empty-result { flex: 1; } | |
| .results { | |
| display: none; | |
| flex: 1; | |
| flex-direction: column; | |
| justify-content: space-between; | |
| gap: 12px; | |
| padding: 2px 0 7px; | |
| } | |
| .score-label { margin: 0 0 3px; font-size: .9rem; color: var(--muted); font-weight: 650; } | |
| .score { | |
| color: var(--navy); | |
| font-size: clamp(2.08rem, 2.7vw, 2.35rem); | |
| font-variant-numeric: tabular-nums; | |
| font-weight: 760; | |
| line-height: 1.17; | |
| letter-spacing: -.04em; | |
| } | |
| .continuum { margin-top: 0; } | |
| .continuum-label { font-size: .85rem; font-weight: 650; color: var(--navy-dark); margin-bottom: 11px; } | |
| .scale { | |
| position: relative; | |
| height: 13px; | |
| border: 1px solid #C7D2DC; | |
| border-radius: 20px; | |
| background: linear-gradient(to right, #8FAEC9 0%, #B9CBD8 29%, #DBE0E4 50%, #D9C49D 73%, #B99A63 100%); | |
| } | |
| .scale-center { | |
| position: absolute; | |
| left: 50%; | |
| top: -4px; | |
| height: 21px; | |
| border-left: 1px solid rgba(44, 58, 70, .52); | |
| } | |
| .marker { | |
| position: absolute; | |
| top: -9px; | |
| left: 50%; | |
| width: 7px; | |
| height: 29px; | |
| transform: translateX(-50%); | |
| background: var(--navy-dark); | |
| border: 1px solid white; | |
| border-radius: 4px; | |
| box-shadow: 0 0 0 1px rgba(18, 45, 75, .38); | |
| transition: left 180ms ease; | |
| } | |
| .scale-ticks { | |
| display: grid; | |
| grid-template-columns: repeat(3, minmax(0, 1fr)); | |
| gap: 8px; | |
| margin-top: 7px; | |
| color: #445368; | |
| font-size: .77rem; | |
| font-weight: 650; | |
| line-height: 1.38; | |
| } | |
| .scale-ticks span { min-width: 0; } | |
| .scale-ticks span:nth-child(2) { text-align: center; } | |
| .scale-ticks span:nth-child(3) { text-align: right; } | |
| .scale-ticks small { display: block; font-size: .72rem; font-weight: 500; color: var(--muted); } | |
| .scale-caption { margin: 7px 0 0; color: #67768A; font-size: .72rem; line-height: 1.4; } | |
| .outside { display: none; color: #805D25; font-weight: 650; margin-top: 9px; font-size: .78rem; } | |
| .divider { border: 0; border-top: 1px solid var(--line); margin: 0; width: 100%; } | |
| .secondary-label { font-size: .86rem; color: var(--muted); font-weight: 650; } | |
| .secondary-value { color: var(--navy-dark); font-size: 1.35rem; font-weight: 700; font-variant-numeric: tabular-nums; margin-top: 5px; } | |
| .interpretation { margin: 8px 0 0; font-size: .85rem; line-height: 1.43; color: #526174; } | |
| .model-info { | |
| margin-top: 20px; | |
| padding: 15px 20px 16px; | |
| background: var(--white); | |
| border: 1px solid var(--line); | |
| border-radius: 14px; | |
| } | |
| .model-info h2 { margin: 0 0 9px; } | |
| .model-info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 26px; } | |
| .model-info h3 { margin: 0 0 5px; color: var(--navy-dark); font-size: .98rem; } | |
| .model-info p { margin: 0; color: #526174; font-size: .85rem; line-height: 1.45; } | |
| @media (max-width: 790px) { | |
| .container { width: min(100% - 30px, 1200px); } | |
| header { padding: 30px 0 23px; } | |
| .workspace { grid-template-columns: 1fr; gap: 15px; align-items: start; } | |
| .results { gap: 16px; padding-bottom: 0; } | |
| .workspace > .panel:last-child { min-height: 0; } | |
| .panel { padding: 22px; } | |
| .dropzone { min-height: 258px; } | |
| .empty-result { min-height: 185px; } | |
| .preview { height: 242px; } | |
| .model-info { padding: 20px; } | |
| .model-info-grid { grid-template-columns: 1fr; gap: 25px; } | |
| .scale-ticks { gap: 5px; font-size: .7rem; } | |
| .scale-ticks small { font-size: .68rem; } | |
| } | |
| /* On shorter desktop screens, prioritize keeping both explanatory sections visible. | |
| Very small screens still scroll naturally rather than reducing type to unreadable sizes. */ | |
| @media (min-width: 791px) and (max-height: 780px) { | |
| header { padding-top: 14px; padding-bottom: 15px; } | |
| h1 { font-size: clamp(1.85rem, 2.6vw, 2.23rem); } | |
| .intro { font-size: 1.02rem; margin-top: 7px; } | |
| .dropzone { min-height: 195px; } | |
| .preview { height: 182px; } | |
| .empty-result { min-height: 225px; } | |
| .model-info { margin-top: 18px; padding-top: 14px; padding-bottom: 16px; } | |
| .model-info p { font-size: .85rem; line-height: 1.45; } | |
| } | |
| @media (prefers-reduced-motion: reduce) { | |
| .marker, .dropzone, .primary { transition: none; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <header> | |
| <h1>Commitment over Compromise: The Unique Aesthetic Rewards of Stylistic Commitment</h1> | |
| <p class="intro">Upload a design image to estimate its position along the minimalism–maximalism stylistic continuum using a trained computer-vision model.</p> | |
| </header> | |
| <main> | |
| <div class="workspace"> | |
| <section class="panel" aria-labelledby="upload-heading"> | |
| <h2 id="upload-heading">Upload a Design Image</h2> | |
| <div id="dropzone" class="dropzone" tabindex="0" role="button" aria-label="Select or drop a design image"> | |
| <input id="image-file" type="file" accept="image/png,image/jpeg,image/webp,image/bmp,image/gif" /> | |
| <div class="drop-prompt"> | |
| <svg class="upload-icon" aria-hidden="true" width="39" height="39" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"> | |
| <path d="M12 16V3m0 0L7 8m5-5 5 5"/><path d="M4 16v3a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-3"/> | |
| </svg> | |
| <p class="upload-title">Drag and drop an image here</p> | |
| <p class="upload-detail">or click to browse</p> | |
| </div> | |
| <img id="image-preview" class="preview" alt="Selected design image preview" /> | |
| </div> | |
| <p id="filename" class="filename">PNG, JPG, WebP, BMP, or GIF</p> | |
| <button id="analyze-button" class="primary" type="button" disabled>Analyze Design</button> | |
| <p id="status" class="message" role="status" aria-live="polite"></p> | |
| </section> | |
| <section class="panel" aria-labelledby="results-heading"> | |
| <h2 id="results-heading">Style Analysis</h2> | |
| <div id="empty-result" class="empty-result"></div> | |
| <div id="results" class="results" aria-live="polite"> | |
| <div class="primary-result"> | |
| <p class="score-label">Predicted Style Score</p> | |
| <div id="style-score" class="score">—</div> | |
| </div> | |
| <hr class="divider" /> | |
| <div class="continuum"> | |
| <div class="continuum-label">Minimalism–Maximalism Continuum</div> | |
| <div class="scale" role="img" aria-label="Stylistic continuum, minimalism on the left, moderate in the center, maximalism on the right"> | |
| <div class="scale-center" aria-hidden="true"></div> | |
| <div id="marker" class="marker" aria-hidden="true"></div> | |
| </div> | |
| <div class="scale-ticks"><span>Stylistic Commitment<small>(Minimal)</small></span><span>Stylistically Compromised<small>(Moderate)</small></span><span>Stylistic Commitment<small>(Maximal)</small></span></div> | |
| <p id="outside" class="outside">The prediction is beyond the visual window; its marker is shown at the boundary. The numerical score is unchanged.</p> | |
| </div> | |
| <hr class="divider" /> | |
| <div class="secondary-result"> | |
| <div class="secondary-label">Estimated Expert Rating (1–7)</div> | |
| <div id="expert-rating" class="secondary-value">—</div> | |
| </div> | |
| </div> | |
| </section> | |
| </div> | |
| <section class="model-info" aria-labelledby="model-heading"> | |
| <h2 id="model-heading">About the Model</h2> | |
| <div class="model-info-grid"> | |
| <div> | |
| <h3>Model Overview</h3> | |
| <p>This tool accompanies the research manuscript <em>Commitment over Compromise: The Unique Aesthetic Rewards of Stylistic Commitment</em>, which examines how clearly expressing a design style, rather than occupying a stylistic middle ground, shapes aesthetic appeal. Using minimalism and maximalism as two distinct design styles, the tool employs a trained computer-vision model (ResNet-18) to estimate where a design falls along the minimalism–maximalism stylistic continuum.</p> | |
| </div> | |
| <div> | |
| <h3>Interpreting the Style Score</h3> | |
| <p>The predicted Style Score indicates a design’s position along the minimalism–maximalism stylistic continuum. Increasingly negative scores indicate stronger stylistic commitment to minimalism, whereas increasingly positive scores indicate stronger stylistic commitment to maximalism. Scores closer to zero reflect a more moderate position along the continuum, corresponding to stylistically compromised designs that do not clearly express either style. Because the scores are mean-centered, zero represents the mean of the expert ratings used to train the model rather than an absolute boundary between stylistic categories.</p> | |
| </div> | |
| </div> | |
| </section> | |
| </main> | |
| </div> | |
| <script type="module"> | |
| // The original Space hosts the trained model. This page changes only the interface. | |
| import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js"; | |
| const MODEL_SPACE = "ThisIsAnAnonymousUser/Minimal-Maximal.7.0"; | |
| const API_ENDPOINT = "/predict"; | |
| // This is a visualization window, not a bound on the model's possible outputs. | |
| const DISPLAY_HALF_RANGE = 3.454; | |
| const dropzone = document.getElementById("dropzone"); | |
| const input = document.getElementById("image-file"); | |
| const preview = document.getElementById("image-preview"); | |
| const filename = document.getElementById("filename"); | |
| const button = document.getElementById("analyze-button"); | |
| const status = document.getElementById("status"); | |
| const empty = document.getElementById("empty-result"); | |
| const results = document.getElementById("results"); | |
| const styleScore = document.getElementById("style-score"); | |
| const expertRating = document.getElementById("expert-rating"); | |
| const marker = document.getElementById("marker"); | |
| const outside = document.getElementById("outside"); | |
| let selectedFile = null; | |
| let previewUrl = null; | |
| let clientPromise = null; | |
| let isAnalyzing = false; | |
| function clearPrediction() { | |
| results.style.display = "none"; | |
| empty.style.display = "flex"; | |
| styleScore.textContent = "—"; | |
| expertRating.textContent = "—"; | |
| } | |
| function selectImage(file) { | |
| if (!file || isAnalyzing) return; | |
| const allowedTypes = new Set(["image/png", "image/jpeg", "image/webp", "image/bmp", "image/gif"]); | |
| if (!allowedTypes.has(file.type)) { | |
| status.textContent = "Please select a PNG, JPG, WebP, BMP, or GIF image."; | |
| status.classList.add("error"); | |
| return; | |
| } | |
| selectedFile = file; | |
| if (previewUrl) URL.revokeObjectURL(previewUrl); | |
| previewUrl = URL.createObjectURL(file); | |
| preview.src = previewUrl; | |
| filename.textContent = file.name; | |
| dropzone.classList.add("has-image"); | |
| status.textContent = ""; | |
| status.classList.remove("error"); | |
| clearPrediction(); | |
| button.disabled = isAnalyzing; | |
| } | |
| dropzone.addEventListener("click", (event) => { | |
| // Ignore the click generated by input.click() so it does not bubble back. | |
| if (event.target !== input && !isAnalyzing) input.click(); | |
| }); | |
| dropzone.addEventListener("keydown", (event) => { | |
| if (event.key === "Enter" || event.key === " ") { | |
| event.preventDefault(); | |
| if (!isAnalyzing) input.click(); | |
| } | |
| }); | |
| input.addEventListener("change", () => selectImage(input.files?.[0])); | |
| dropzone.addEventListener("dragover", (event) => { event.preventDefault(); dropzone.classList.add("dragging"); }); | |
| dropzone.addEventListener("dragleave", () => dropzone.classList.remove("dragging")); | |
| dropzone.addEventListener("drop", (event) => { | |
| event.preventDefault(); | |
| dropzone.classList.remove("dragging"); | |
| selectImage(event.dataTransfer.files?.[0]); | |
| }); | |
| button.addEventListener("click", async () => { | |
| if (!selectedFile || isAnalyzing) return; | |
| const submittedFile = selectedFile; | |
| isAnalyzing = true; | |
| button.disabled = true; | |
| button.textContent = "Analyzing…"; | |
| status.textContent = "Connecting to the model and analyzing your image…"; | |
| status.classList.remove("error"); | |
| try { | |
| if (!clientPromise) clientPromise = Client.connect(MODEL_SPACE); | |
| const client = await clientPromise; | |
| // The API expects an image blob in the parameter named "img". | |
| const response = await client.predict(API_ENDPOINT, { img: submittedFile }); | |
| // From the documented /predict API: | |
| // [0] estimated expert rating (1–7), [1] mean-centered style score, | |
| // [2] original HTML visualization (unused by the redesigned UI). | |
| const values = response.data; | |
| if (!Array.isArray(values) || values.length < 2 || values[0] == null || values[1] == null) { | |
| throw new Error("The model returned an unexpected response."); | |
| } | |
| const expert = Number(values[0]); | |
| const centered = Number(values[1]); | |
| if (!Number.isFinite(expert) || !Number.isFinite(centered)) { | |
| throw new Error("The model returned a non-numeric prediction."); | |
| } | |
| // Do not change or recompute model outputs; round only the visible labels. | |
| styleScore.textContent = centered.toFixed(2); | |
| expertRating.textContent = expert.toFixed(2); | |
| const pct = Math.max(0, Math.min(100, ((centered + DISPLAY_HALF_RANGE) / (2 * DISPLAY_HALF_RANGE)) * 100)); | |
| marker.style.left = `${pct}%`; | |
| outside.style.display = Math.abs(centered) > DISPLAY_HALF_RANGE ? "block" : "none"; | |
| empty.style.display = "none"; | |
| results.style.display = "flex"; | |
| status.textContent = "Analysis complete."; | |
| } catch (error) { | |
| clientPromise = null; // allow a fresh connection on retry | |
| status.textContent = "Prediction unavailable. Please retry; the model may be restarting or temporarily offline."; | |
| status.classList.add("error"); | |
| console.error("Model prediction failed:", error); | |
| } finally { | |
| isAnalyzing = false; | |
| button.disabled = !selectedFile; | |
| button.textContent = "Analyze Design"; | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> | |