Drawer

website using html, css and javascript:

<!doctype html>

<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<title>Mini Web App</title>
<link rel="preconnect" href="https://fonts.gstatic.com"/>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap" rel="stylesheet"/>
<style>
:root{
  --bg:#0f1724;
  --card:#0b1220;
  --muted:#9aa7bf;
  --accent:#7c5cff;
  --glass:rgba(255,255,255,0.04);
  --glass-2:rgba(255,255,255,0.02);
  --success:#34d399;
}
*{box-sizing:border-box}
html,body{height:100%}
body{
  margin:0;
  font-family:Inter,system-ui,-apple-system,Segoe UI,Roboto,'Helvetica Neue',Arial;
  background:linear-gradient(180deg,#071029 0%,#071621 50%),var(--bg);
  color:#e6eef8;
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
  display:flex;
  align-items:center;
  justify-content:center;
  padding:32px;
}
.container{
  width:100%;
  max-width:1000px;
  background:linear-gradient(180deg,rgba(255,255,255,0.02),rgba(255,255,255,0.01));
  border-radius:14px;
  box-shadow:0 10px 30px rgba(2,6,23,0.6), inset 0 1px 0 rgba(255,255,255,0.02);
  overflow:hidden;
  display:grid;
  grid-template-columns:420px 1fr;
  gap:0;
  min-height:560px;
}
.aside{
  padding:28px;
  background:linear-gradient(180deg,var(--card),#071122);
  border-right:1px solid rgba(255,255,255,0.03);
  display:flex;
  flex-direction:column;
  gap:18px;
}
.brand{
  display:flex;
  gap:12px;
  align-items:center;
}
.logo{
  width:52px;
  height:52px;
  border-radius:12px;
  background:linear-gradient(135deg,var(--accent),#5ad0ff);
  display:flex;
  align-items:center;
  justify-content:center;
  color:#071021;
  font-weight:700;
  font-size:20px;
  box-shadow:0 6px 18px rgba(124,92,255,0.18);
}
.title{
  font-size:18px;
  font-weight:600;
  letter-spacing:-0.2px;
}
.subtitle{font-size:12px;color:var(--muted)}
.metrics{display:flex;gap:12px;margin-top:6px}
.metric{background:var(--glass);padding:10px;border-radius:10px;flex:1;text-align:center}
.metric strong{display:block;font-size:18px}
.controls{display:flex;gap:8px;margin-top:auto}
.btn{
  background:linear-gradient(180deg,rgba(255,255,255,0.02),rgba(255,255,255,0.01));
  border:1px solid rgba(255,255,255,0.03);
  color:inherit;
  padding:10px 12px;border-radius:10px;
  cursor:pointer;font-weight:600;min-width:120px;
  transition:transform .16s ease,box-shadow .16s ease;
}
.btn:active{transform:translateY(1px)}
.small{padding:8px 10px;min-width:40px}
.preview{
  margin-top:14px;
  padding:12px;
  border-radius:12px;
  background:linear-gradient(180deg,var(--glass),var(--glass-2));
  display:flex;
  gap:10px;
  align-items:center;
  justify-content:space-between;
}
.card{
  background:linear-gradient(135deg,rgba(255,255,255,0.015),rgba(255,255,255,0.01));
  border-radius:10px;padding:12px;width:100%;display:flex;gap:12px;align-items:center;
}
.icon{
  width:48px;height:48px;border-radius:8px;background:rgba(255,255,255,0.03);display:flex;align-items:center;justify-content:center;font-weight:700;color:var(--accent)
}
.info{flex:1}
.info h4{margin:0;font-size:14px}
.info p{margin:4px 0 0;color:var(--muted);font-size:12px;line-height:1.2}
.right{text-align:right}
.badge{display:inline-block;padding:6px 8px;border-radius:999px;background:rgba(255,255,255,0.02);font-weight:600;font-size:12px;color:var(--muted)}

.main{
padding:28px;
display:flex;
flex-direction:column;
gap:18px;
}
.topbar{display:flex;align-items:center;justify-content:space-between;gap:12px}
.controls-row{display:flex;gap:12px;align-items:center}
.search{
background:var(--glass);
border-radius:12px;padding:8px 12px;display:flex;gap:8px;align-items:center;
width:420px;border:1px solid rgba(255,255,255,0.02)
}
.search input{background:transparent;border:0;outline:0;color:inherit;width:100%;font-size:14px}
.grid{display:grid;grid-template-columns:repeat(2,1fr);gap:14px;margin-top:6px}
.panel{
background:linear-gradient(180deg,rgba(255,255,255,0.015),rgba(255,255,255,0.01));
padding:16px;border-radius:12px;border:1px solid rgba(255,255,255,0.02);
}
.hero{
display:flex;gap:18px;align-items:center;justify-content:space-between;
}
.hero-left{max-width:60%}
.hero h1{margin:0;font-size:22px;letter-spacing:-0.3px}
.hero p{margin:8px 0 0;color:var(--muted);font-size:14px}
.play{
background:linear-gradient(90deg,var(--accent),#5ad0ff);
border:none;padding:10px 14px;border-radius:12px;color:#071021;font-weight:700;cursor:pointer;
box-shadow:0 8px 30px rgba(92,72,255,0.18);
}
.list{display:flex;flex-direction:column;gap:10px;margin-top:10px}
.item{display:flex;align-items:center;gap:12px;padding:10px;border-radius:10px;background:linear-gradient(180deg,rgba(255,255,255,0.01),transparent);border:1px solid rgba(255,255,255,0.02)}
.item h4{margin:0;font-size:14px}
.item p{margin:2px 0 0;color:var(--muted);font-size:12px}
.progress{height:8px;background:rgba(255,255,255,0.03);border-radius:999px;overflow:hidden;margin-top:8px}
.progress > b{display:block;height:100%;background:linear-gradient(90deg,var(--accent),#5ad0ff);width:40%}
.form-row{display:flex;gap:8px;align-items:center}
input[type="text"],input[type="email"],textarea,select{
background:transparent;border:1px solid rgba(255,255,255,0.04);padding:10px;border-radius:10px;color:inherit;outline:none;width:100%
}
textarea{min-height:120px;resize:vertical}
.footer{display:flex;gap:10px;justify-content:space-between;align-items:center;color:var(--muted);font-size:13px;margin-top:10px}
.toast{position:fixed;left:50%;transform:translateX(-50%);bottom:36px;background:#071728;padding:12px 16px;border-radius:10px;border:1px solid rgba(255,255,255,0.03);box-shadow:0 8px 24px rgba(2,6,23,0.6);display:flex;gap:10px;align-items:center}
@media (max-width:980px){
.container{grid-template-columns:1fr;min-height:720px}
.aside{order:2}
.main{order:1}
.search{width:100%}
} </style>

</head>
<body>
<div class="container" id="app">
  <aside class="aside">
    <div class="brand">
      <div class="logo">WV</div>
      <div>
        <div class="title">Value Web Demo</div>
        <div class="subtitle">Mini project • interactive</div>
      </div>
    </div>
    <div class="metrics">
      <div class="metric">
        <strong id="stat-cards">7</strong>
        Cards
      </div>
      <div class="metric">
        <strong id="stat-users">1</strong>
        Sessions
      </div>
    </div>
    <div class="preview">
      <div class="card">
        <div class="icon">JS</div>
        <div class="info">
          <h4 id="card-title">Value Converter</h4>
          <p id="card-desc">Convert units, focus on precision and UX.</p>
        </div>
        <div class="right">
          <div class="badge" id="card-status">Ready</div>
        </div>
      </div>
    </div>
    <div class="controls">
      <button class="btn" id="resetBtn">Reset</button>
      <button class="btn" id="exportBtn">Export</button>
    </div>
    <div style="font-size:12px;color:var(--muted);margin-top:6px">Local demo • data stored in your browser</div>
  </aside>
  <main class="main">
    <div class="topbar">
      <div class="controls-row">
        <div class="search">
          <svg width="18" height="18" viewBox="0 0 24 24" fill="none" aria-hidden><path d="M21 21l-4.35-4.35" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><circle cx="11" cy="11" r="6" stroke="currentColor" stroke-width="1.5"></circle></svg>
          <input id="q" placeholder="Search cards, examples..." />
        </div>
        <div style="display:flex;gap:8px">
          <select id="preset">
            <option value="length">Length</option>
            <option value="weight">Weight</option>
            <option value="temperature">Temperature</option>
            <option value="currency">Currency (demo)</option>
          </select>
          <button class="btn small" id="addCard">Add</button>
        </div>
      </div>
      <div class="controls-row">
        <div class="badge" id="modeLabel">Demo Mode</div>
        <button class="btn" id="themeToggle">Toggle</button>
      </div>
    </div>

```
<div class="grid">
  <div class="panel">
    <div class="hero">
      <div class="hero-left">
        <h1 id="page-title">Interactive Value Conversion</h1>
        <p>Quickly convert between units with precision and preview results live. Try adding cards and running comparisons.</p>
        <div style="margin-top:12px;display:flex;gap:8px">
          <button class="play" id="runBtn">Open Converter</button>
          <button class="btn" id="demoExample">Example</button>
        </div>
      </div>
      <div style="width:36%;text-align:right">
        <div class="badge" id="liveRate">Rates: local</div>
        <div style="margin-top:12px;color:var(--muted);font-size:13px">Last saved: <span id="lastSaved">never</span></div>
      </div>
    </div>
    <div class="list" id="cardList">
    </div>
  </div>

  <div class="panel">
    <div style="display:flex;justify-content:space-between;align-items:center">
      <div>
        <h3 style="margin:0">Converter</h3>
        <div style="color:var(--muted);font-size:13px;margin-top:6px">Select a card and convert values instantly</div>
      </div>
      <div class="badge" id="selLabel">No card</div>
    </div>

    <div style="margin-top:12px">
      <div class="form-row" style="margin-bottom:8px">
        <input type="text" id="valueInput" placeholder="Enter value" />
        <select id="fromUnit"></select>
        <select id="toUnit"></select>
      </div>
      <div class="panel" id="resultPanel" style="margin-top:12px;padding:12px;background:linear-gradient(180deg,rgba(255,255,255,0.02),transparent);display:flex;justify-content:space-between;align-items:center">
        <div>
          <div style="font-size:13px;color:var(--muted)">Result</div>
          <div id="result" style="font-size:18px;font-weight:700">—</div>
        </div>
        <div>
          <button class="btn small" id="copyBtn">Copy</button>
          <button class="btn small" id="swapBtn">Swap</button>
        </div>
      </div>
    </div>

    <div class="footer">
      <div id="helpText">Tip: Use presets to add common unit groups</div>
      <div>
        <button class="btn small" id="saveBtn">Save</button>
        <button class="btn small" id="clearBtn">Clear</button>
      </div>
    </div>
  </div>
</div>

<div style="display:flex;justify-content:space-between;gap:12px">
  <div style="flex:1" class="panel" id="notesPanel">
    <h4 style="margin:0 0 8px 0">Notes</h4>
    <textarea id="notes" placeholder="Write quick notes..."></textarea>
  </div>
  <div style="width:300px" class="panel">
    <h4 style="margin:0 0 8px 0">Activity</h4>
    <div id="activity" style="display:flex;flex-direction:column;gap:8px"></div>
  </div>
</div>
```

  </main>
</div>

<div id="toast" class="toast" style="display:none">
  <div id="toastText">Saved</div>
</div>

<script>
const presets = {
  length: { units:[{k:'m',n:'Meters'},{k:'km',n:'Kilometers'},{k:'cm',n:'Centimeters'},{k:'inch',n:'Inches'},{k:'ft',n:'Feet'}], convert:(v,f,t)=>{const factors={m:1,km:1000,cm:0.01,inch:0.0254,ft:0.3048};return v* factors[f]/factors[t]}},
  weight: { units:[{k:'kg',n:'Kilograms'},{k:'g',n:'Grams'},{k:'lb',n:'Pounds'},{k:'oz',n:'Ounces'}], convert:(v,f,t)=>{const factors={kg:1,g:0.001,lb:0.45359237,oz:0.028349523125};return v* factors[f]/factors[t]}},
  temperature: { units:[{k:'c',n:'Celsius'},{k:'f',n:'Fahrenheit'},{k:'k',n:'Kelvin'}], convert:(v,f,t)=>{if(f===t) return v; if(f==='c'&&t==='f') return v*9/5+32; if(f==='f'&&t==='c') return (v-32)*5/9; if(f==='c'&&t==='k') return v+273.15; if(f==='k'&&t==='c') return v-273.15; if(f==='f'&&t==='k') return (v-32)*5/9+273.15; if(f==='k'&&t==='f') return (v-273.15)*9/5+32}},
  currency: { units:[{k:'USD',n:'USD'},{k:'EUR',n:'EUR'},{k:'JPY',n:'JPY'},{k:'INR',n:'INR'}], convert:(v,f,t,rate)=>{const r=rate||{USD:1,EUR:0.93,JPY:160,INR:83};return v*(r[f]/r[t])}}
};

let cards = JSON.parse(localStorage.getItem('demo.cards')||'[]');
let activity = JSON.parse(localStorage.getItem('demo.act')||'[]');
let selected = null;
let rates = {USD:1,EUR:0.93,JPY:160,INR:83};

const el = id=>document.getElementById(id);
const cardList = el('cardList');
const selLabel = el('selLabel');
const fromUnit = el('fromUnit');
const toUnit = el('toUnit');
const result = el('result');
const valueInput = el('valueInput');
const q = el('q');
const preset = el('preset');

function renderCards(filter=''){
  cardList.innerHTML='';
  const items = cards.filter(c=>c.title.toLowerCase().includes(filter.toLowerCase()));
  el('stat-cards').textContent=cards.length;
  for(let i=0;i<items.length;i++){
    const c=items[i];
    const node=document.createElement('div');
    node.className='item';
    node.innerHTML=`<div style="width:48px;height:48px;border-radius:8px;background:linear-gradient(135deg,rgba(124,92,255,0.12),rgba(90,208,255,0.06));display:flex;align-items:center;justify-content:center;font-weight:700;color:var(--accent)">${c.icon||'U'}</div>
    <div style="flex:1"><h4>${c.title}</h4><p>${c.desc}</p><div class="progress"><b style="width:${Math.min(100,c.progress)}%"></b></div></div>
    <div style="display:flex;flex-direction:column;gap:8px">
      <button class="btn small" data-id="${c.id}" data-action="select">Open</button>
      <button class="btn small" data-id="${c.id}" data-action="delete">Del</button>
    </div>`;
    cardList.appendChild(node);
  }
  attachCardButtons();
}

function attachCardButtons(){
  cardList.querySelectorAll('button').forEach(b=>{
    b.onclick=()=>{
      const id=b.dataset.id;
      const action=b.dataset.action;
      if(action==='select') openCard(id);
      if(action==='delete') { cards=cards.filter(x=>x.id!==id); save(); renderCards(q.value); log('Deleted card'); }
    };
  });
}

function openCard(id){
  selected = cards.find(c=>c.id===id);
  if(!selected) return;
  selLabel.textContent=selected.title;
  populateUnits(selected.units);
  el('card-title').textContent=selected.title;
  el('card-desc').textContent=selected.desc;
  el('card-status').textContent='Loaded';
  el('lastSaved').textContent=new Date(selected.savedAt).toLocaleString();
  populateNotes(selected.notes||'');
}

function populateUnits(list){
  fromUnit.innerHTML='';toUnit.innerHTML='';
  list.forEach(u=>{
    const o1=document.createElement('option');o1.value=u.k;o1.textContent=u.n;
    const o2=o1.cloneNode(true);
    fromUnit.appendChild(o1);toUnit.appendChild(o2);
  });
}

function populateNotes(t){el('notes').value=t}

function addCardFromPreset(key){
  const p=presets[key];
  if(!p) return;
  const id='c_'+Date.now().toString(36);
  const card={id,title: key.charAt(0).toUpperCase()+key.slice(1)+' Units',desc:`Common ${key} units`,units:p.units,icon:key[0].toUpperCase(),progress:Math.floor(Math.random()*60)+20,savedAt:Date.now(),notes:''};
  cards.unshift(card);
  save();
  renderCards(q.value);
  log('Added preset: '+key);
  openCard(card.id);
}

function convertValue(){
  if(!selected) return;
  const v=parseFloat(valueInput.value);
  if(Number.isNaN(v)) { result.textContent='—'; return; }
  const from=fromUnit.value; const to=toUnit.value;
  const presetKey = getPresetFor(selected);
  let out='—';
  if(presetKey && presetKey!=='currency'){
    out = presets[presetKey].convert(v,from,to);
  } else if(presetKey==='currency'){
    out = presets.currency.convert(v,from,to,rates);
  } else {
    const idxFrom = selected.units.find(u=>u.k===from);
    const idxTo = selected.units.find(u=>u.k===to);
    if(idxFrom && idxTo) out=(v*1);
  }
  if(typeof out==='number') out = Number(out.toFixed(6)).toString();
  result.textContent=out;
}

function getPresetFor(card){
  const title = card.title.toLowerCase();
  if(title.includes('length')) return 'length';
  if(title.includes('weight')) return 'weight';
  if(title.includes('temperature')) return 'temperature';
  if(title.includes('currency')) return 'currency';
  return null;
}

function save(){
  localStorage.setItem('demo.cards',JSON.stringify(cards));
  localStorage.setItem('demo.act',JSON.stringify(activity));
  el('lastSaved').textContent=new Date().toLocaleString();
}

function log(text){
  activity.unshift({t:Date.now(),text});
  if(activity.length>10) activity.pop();
  renderActivity();
  save();
  showToast(text);
}

function renderActivity(){
  const container = el('activity');
  container.innerHTML='';
  activity.forEach(a=>{
    const d=document.createElement('div');
    d.style.fontSize='13px'; d.style.color='var(--muted)';
    d.textContent = `${new Date(a.t).toLocaleTimeString()} • ${a.text}`;
    container.appendChild(d);
  });
}

function showToast(text,ms=1400){
  el('toastText').textContent=text;
  const t=el('toast'); t.style.display='flex'; clearTimeout(t._hide);
  t._hide=setTimeout(()=>t.style.display='none',ms);
}

function exportData(){
  const data = {cards,activity};
  const blob = new Blob([JSON.stringify(data,0,2)],{type:'application/json'});
  const url = URL.createObjectURL(blob);
  const a=document.createElement('a'); a.href=url; a.download='demo-data.json'; a.click();
  URL.revokeObjectURL(url);
  log('Exported data');
}

function clearAll(){
  cards=[]; activity=[]; selected=null;
  save(); renderCards(); renderActivity();
  el('card-title').textContent='Value Converter';
  el('card-desc').textContent='Convert units, focus on precision and UX.';
  selLabel.textContent='No card';
  populateUnits([{k:'',n:'--'},{k:'',n:'--'}]);
  result.textContent='—';
  showToast('Cleared');
}

function restoreExample(){
  cards = [
    {id:'c_len',title:'Length Units',desc:'Meters, Kilometers, Inches',units:presets.length.units,icon:'L',progress:45,savedAt:Date.now(),notes:'Used for distance conversion'},
    {id:'c_temp',title:'Temperature Units',desc:'Celsius, Fahrenheit, Kelvin',units:presets.temperature.units,icon:'T',progress:65,savedAt:Date.now(),notes:'Careful with offsets'}
  ];
  save(); renderCards(); log('Loaded example cards');
  openCard(cards[0].id);
}

function init(){
  renderCards();
  renderActivity();
  if(cards.length===0) restoreExample();
  q.oninput=()=>renderCards(q.value);
  el('addCard').onclick=()=>addCardFromPreset(preset.value);
  el('runBtn').onclick=()=>{ if(selected) showToast('Converter opened'); else showToast('Select a card'); };
  el('demoExample').onclick=restoreExample;
  el('resetBtn').onclick=()=>{ clearAll(); };
  el('exportBtn').onclick=exportData;
  el('saveBtn').onclick=()=>{
    if(!selected){ showToast('Select a card first'); return; }
    selected.notes = el('notes').value;
    selected.savedAt = Date.now();
    save();
    renderCards(q.value);
    log('Saved card');
  };
  el('themeToggle').onclick=()=>{ document.body.classList.toggle('alt'); showToast('Toggled theme'); };
  el('clearBtn').onclick=()=>{ el('notes').value=''; showToast('Notes cleared'); };
  el('copyBtn').onclick=()=>{ navigator.clipboard.writeText(result.textContent).then(()=>showToast('Copied')); };
  el('swapBtn').onclick=()=>{ const a=fromUnit.value; fromUnit.value=toUnit.value; toUnit.value=a; convertValue(); };
  valueInput.oninput=convertValue;
  fromUnit.onchange=convertValue;
  toUnit.onchange=convertValue;
  el('preset').onchange=()=>{};
  el('q').value='';
  el('stat-users').textContent=1;
  el('liveRate').textContent='Rates: local';
  el('lastSaved').textContent= cards[0]? new Date(cards[0].savedAt).toLocaleString() : 'never';
  el('notes').oninput=()=>{};
  el('notes').value='';
  el('cardList').addEventListener('click',()=>{});
  document.addEventListener('keydown',(e)=>{ if(e.key==='k'&& (e.ctrlKey||e.metaKey)) { e.preventDefault(); q.focus(); }});
}

init();
</script>

</body>
</html>
::contentReference[oaicite:0]{index=0}