Spaces:
Running
Running
Download index.html from scento/perfume-api: direct link, hf CLI and curl.
- Browser
- Download file 8.67 kB
-
https://huggingface.co/spaces/scento/perfume-api/resolve/main/index.html
- Command line
-
hf download hf://spaces/scento/perfume-api/index.html
-
curl -L -o index.html https://huggingface.co/spaces/scento/perfume-api/resolve/main/index.html
8.67 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <title>Scento Perfume API</title> | |
| <style> | |
| :root{--bg:#faf7f2;--ink:#1d1a16;--mute:#6f675c;--line:#e4ddd0;--card:#fff;--acc:#8a6a2f} | |
| @media (prefers-color-scheme:dark){:root{--bg:#14120f;--ink:#f1ece3;--mute:#a39a8b;--line:#2e2a23;--card:#1b1814;--acc:#d4b06a}} | |
| *{box-sizing:border-box} | |
| body{margin:0;background:var(--bg);color:var(--ink);font:16px/1.55 ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif} | |
| main{max-width:720px;margin:0 auto;padding:40px 16px 56px} | |
| h1{font:500 clamp(28px,6vw,40px)/1.1 Georgia,"Times New Roman",serif;margin:0 0 10px;letter-spacing:-.01em} | |
| .lede{color:var(--mute);margin:0 0 28px} | |
| .lede a,.foot a,.tail a,.found a{color:var(--acc)} | |
| nav{display:flex;gap:4px;border-bottom:1px solid var(--line);margin-bottom:20px} | |
| nav button{background:none;border:0;border-bottom:2px solid transparent;padding:10px 14px;font:inherit;color:var(--mute);cursor:pointer;margin-bottom:-1px} | |
| nav button[aria-selected=true]{color:var(--ink);border-color:var(--acc)} | |
| nav button:focus-visible,input:focus-visible,.go:focus-visible{outline:2px solid var(--acc);outline-offset:2px} | |
| form{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:20px} | |
| input{flex:1 1 180px;min-width:0;padding:11px 12px;border:1px solid var(--line);border-radius:8px;background:var(--card);color:var(--ink);font:inherit} | |
| .go{padding:11px 18px;border:0;border-radius:8px;background:var(--ink);color:var(--bg);font:inherit;cursor:pointer} | |
| .out{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:20px 22px;min-height:120px} | |
| .out h2{font:500 24px/1.2 Georgia,serif;margin:0 0 6px} | |
| .out .l{color:var(--mute);font-style:italic;margin:0 0 14px} | |
| .out dl{margin:0;display:grid;grid-template-columns:auto 1fr;gap:6px 16px} | |
| .out dt{color:var(--mute);font-size:13px;text-transform:uppercase;letter-spacing:.06em;padding-top:2px} | |
| .out dd{margin:0} | |
| table{width:100%;border-collapse:collapse;font-size:14px;margin:6px 0 14px} | |
| th,td{text-align:left;padding:7px 8px;border-bottom:1px solid var(--line);vertical-align:top} | |
| th{color:var(--mute);font-weight:500} | |
| .tail{margin-top:16px;font-size:14px;color:var(--mute)} | |
| .tail a{margin-right:12px} | |
| .chips{display:flex;gap:6px;flex-wrap:wrap;margin:-8px 0 20px} | |
| .chips button{border:1px solid var(--line);background:none;color:var(--mute);border-radius:99px;padding:3px 11px;font:inherit;font-size:13px;cursor:pointer} | |
| .foot{margin-top:28px;font-size:14px;color:var(--mute)} | |
| code{background:var(--card);border:1px solid var(--line);border-radius:4px;padding:1px 5px;font-size:13px} | |
| </style> | |
| </head> | |
| <body> | |
| <main> | |
| <h1>Scento Perfume API</h1> | |
| <p class="lede">Scento Perfume API, built on Scento's verified perfume database, for people and AI assistants: 1,400+ designer and niche perfumes, 400+ raw materials and 140+ houses, every answer tied to a page on <a href="https://www.scento.com">scento.com</a>. No key, no account. This demo calls the public REST API from your browser.</p> | |
| <nav role="tablist"> | |
| <button role="tab" aria-selected="true" data-t="p">Perfume portrait</button> | |
| <button role="tab" aria-selected="false" data-t="c">Compare two</button> | |
| <button role="tab" aria-selected="false" data-t="m">Raw material</button> | |
| </nav> | |
| <section id="p"><form><input name="a" value="Oud Wood Tom Ford" aria-label="Perfume" autocomplete="off"><button class="go">Draw the portrait</button></form> | |
| <div class="chips" data-for="p"><button>Delina</button><button>Baccarat Rouge 540</button><button>Eau Sauvage Dior</button></div></section> | |
| <section id="c" hidden><form><input name="a" value="Eau Sauvage Dior" aria-label="First perfume" autocomplete="off"><input name="b" value="Oud Wood Tom Ford" aria-label="Second perfume" autocomplete="off"><button class="go">Compare</button></form></section> | |
| <section id="m" hidden><form><input name="a" value="vetiver" aria-label="Raw material" autocomplete="off"><button class="go">Look it up</button></form> | |
| <div class="chips" data-for="m"><button>oud</button><button>iris</button><button>ambroxan</button></div></section> | |
| <div class="out" id="out" aria-live="polite"></div> | |
| <p class="foot">Use it from code: <code>https://www.scento.com/api/perfume/v1</code> 路 MCP <code>https://www.scento.com/mcp</code> 路 <a href="https://www.scento.com/developers/perfume-api">REST docs</a> 路 <a href="https://www.scento.com/developers/perfume-mcp-server">MCP docs</a> 路 <a href="https://www.scento.com/api/perfume/v1/openapi.json">OpenAPI</a> 路 <a href="https://github.com/Scento-com/perfume-mcp-server">GitHub</a></p> | |
| </main> | |
| <script> | |
| const API="https://www.scento.com/api/perfume/v1"; | |
| const out=document.getElementById("out"); | |
| const esc=s=>String(s??"").replace(/[&<>"]/g,c=>({"&":"&","<":"<",">":">",'"':"""}[c])); | |
| const safeUrl=u=>/^https:\/\/www\.scento\.com\//.test(u)?u:"https://www.scento.com"; | |
| async function get(path){ | |
| try{ | |
| const r=await fetch(API+path); | |
| const j=await r.json(); | |
| if(!r.ok) return {err:(j.error&&j.error.message)||"No match in the Scento Perfume API."}; | |
| if(j.detail&&j.detail.found===false) return {err:j.lede}; | |
| return {j}; | |
| }catch(e){return {err:"The Scento Perfume API could not be reached just now. Please try again in a moment."}} | |
| } | |
| function tail(j){ | |
| const l=[]; | |
| if(j.scento_page) l.push(`<a href="${safeUrl(j.scento_page.url)}">${esc(j.scento_page.label)}</a>`); | |
| (j.further_reading||[]).slice(0,4).forEach(f=>l.push(`<a href="${safeUrl(f.url)}">${esc(f.label)}</a>`)); | |
| return `<div class="tail">${l.join("")}<br>${esc(j.cite||"Scento Perfume API, scento.com")}</div>`; | |
| } | |
| const row=(k,v)=>v&&(Array.isArray(v)?v.length:true)?`<dt>${k}</dt><dd>${esc(Array.isArray(v)?v.join(", "):v)}</dd>`:""; | |
| async function portrait(q){ | |
| let r=await get("/perfumes/"+encodeURIComponent(q)); | |
| if(r.err) return `<p>${esc(r.err)}</p>`; | |
| const j=r.j,d=j.detail,p=d.pyramid||{}; | |
| return `<h2>${esc(d.name)} 路 ${esc(d.house)}</h2><p class="l">${esc(j.lede)}</p><dl>`+ | |
| (d.pyramid?row("Top",p.top)+row("Heart",p.heart)+row("Base",p.base):row("Notes",d.notes))+ | |
| row("Character",d.character)+row("Family",d.family)+row("Concentration",d.concentration)+row("Year",d.year&&String(d.year))+row("Perfumer",d.perfumer)+`</dl>`+tail(j); | |
| } | |
| async function contrast(a,b){ | |
| const r=await get("/contrast?perfumes="+encodeURIComponent(a+","+b)); | |
| if(r.err) return `<p>${esc(r.err)}</p>`; | |
| const j=r.j,d=j.detail; | |
| if(!d.perfumes) return `<p>${esc(j.lede)}</p>`; | |
| const rows=d.axes.map(x=>`<tr><th>${esc(x.axis)}</th>${x.values.map(v=>`<td>${esc(v)}</td>`).join("")}</tr>`).join(""); | |
| return `<p class="l">${esc(j.lede)}</p><table><thead><tr><th></th>${d.perfumes.map(p=>`<th>${esc(p.name)}</th>`).join("")}</tr></thead><tbody>${rows}</tbody></table><p><b>Shared materials:</b> ${esc((d.shared_materials||[]).join(", ")||"none listed")}</p>`+tail(j); | |
| } | |
| async function material(q){ | |
| const r=await get("/materials/"+encodeURIComponent(q)); | |
| if(r.err) return `<p>${esc(r.err)}</p>`; | |
| const j=r.j,d=j.detail; | |
| const found=(d.showcased_in||[]).slice(0,6).map(s=>`<a href="${safeUrl(s.page)}">${esc(s.name)} (${esc(s.house)})</a>`).join(", "); | |
| return `<h2>${esc(d.name)}</h2><p class="l">${esc(j.lede)}</p><dl>${row("Smells like",d.smells_like)}${row("Origin",d.origin)}${row("Source",d.natural_or_synthetic)}${row("Scento family",d.scento_family)}${row("Pairs well with",d.pairs_well_with)}${found?`<dt>Found in</dt><dd class="found">${found}</dd>`:""}</dl>`+tail(j); | |
| } | |
| async function run(t,f){ | |
| out.innerHTML="<p class='l'>Reading the Scento Perfume API...</p>"; | |
| const v=n=>f.elements[n]&&f.elements[n].value.trim(); | |
| if(!v("a")||(t==="c"&&!v("b"))){out.innerHTML="<p>Type a name to begin.</p>";return} | |
| out.innerHTML=t==="p"?await portrait(v("a")):t==="c"?await contrast(v("a"),v("b")):await material(v("a")); | |
| } | |
| document.querySelectorAll("section").forEach(s=>s.querySelector("form").addEventListener("submit",e=>{e.preventDefault();run(s.id,e.target)})); | |
| document.querySelectorAll(".chips").forEach(c=>c.addEventListener("click",e=>{if(e.target.tagName!=="BUTTON")return;const s=document.getElementById(c.dataset.for);s.querySelector("input").value=e.target.textContent;run(s.id,s.querySelector("form"))})); | |
| document.querySelectorAll("nav button").forEach(b=>b.addEventListener("click",()=>{ | |
| document.querySelectorAll("nav button").forEach(x=>x.setAttribute("aria-selected",x===b)); | |
| document.querySelectorAll("section").forEach(s=>s.hidden=s.id!==b.dataset.t); | |
| run(b.dataset.t,document.querySelector("#"+b.dataset.t+" form")); | |
| })); | |
| run("p",document.querySelector("#p form")); | |
| </script> | |
| </body> | |
| </html> | |