Spaces:
Running
<!doctype html>
Browse files<html lang="fr">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Espace Codage — Rosalinda</title>
<style>
:root{
--bg:#0b1220;
--panel:#0f1a2b;
--panel2:#101f35;
--border:rgba(255,255,255,.08);
--text:#e8eefc;
--muted:rgba(232,238,252,.65);
--muted2:rgba(232,238,252,.45);
--accent:#4ea1ff;
--accent2:#7dd3fc;
--danger:#ff6b6b;
--ok:#2ee59d;
--shadow: 0 10px 30px rgba(0,0,0,.35);
--radius:16px;
--radius2:22px;
--mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono","Courier New", monospace;
--sans: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji","Segoe UI Emoji";
}
*{box-sizing:border-box}
html,body{height:100%}
body{
margin:0;
font-family:var(--sans);
background: radial-gradient(1200px 600px at 40% 20%, rgba(78,161,255,.12), transparent 60%),
radial-gradient(900px 500px at 80% 10%, rgba(125,211,252,.08), transparent 55%),
var(--bg);
color:var(--text);
overflow:hidden;
}
/* Layout */
.app{
height:100vh;
padding:14px;
display:grid;
grid-template-columns: 320px 1fr 420px;
gap:14px;
}
.card{
background: linear-gradient(180deg, rgba(255,255,255,.03), rgba(255,255,255,.015));
border:1px solid var(--border);
border-radius: var(--radius2);
box-shadow: var(--shadow);
overflow:hidden;
display:flex;
flex-direction:column;
min-height:0;
}
.cardHeader{
padding:14px 14px 10px 14px;
display:flex;
align-items:center;
justify-content:space-between;
gap:10px;
border-bottom:1px solid var(--border);
background: rgba(255,255,255,.02);
}
.title{
display:flex;
align-items:center;
gap:10px;
font-weight:800;
letter-spacing:.2px;
}
.dot{
width:10px;height:10px;border-radius:999px;
background: rgba(78,161,255,.9);
box-shadow: 0 0 0 4px rgba(78,161,255,.15);
flex:0 0 auto;
}
.pill{
font-size:12px;
padding:6px 10px;
border-radius:999px;
border:1px solid var(--border);
background: rgba(255,255,255,.02);
color:var(--muted);
}
.pill.ok{ color: rgba(46,229,157,.95); border-color: rgba(46,229,157,.25); background: rgba(46,229,157,.06); }
.pill.bad{ color: rgba(255,107,107,.95); border-color: rgba(255,107,107,.25); background: rgba(255,107,107,.06); }
/* Sidebar */
.sidebarBody{ padding:14px; display:flex; flex-direction:column; gap:14px; min-height:0; }
.searchRow{ display:flex; gap:10px; align-items:center; }
.iconBtn{
width:40px;height:40px;border-radius:12px;
border:1px solid var(--border);
background: rgba(255,255,255,.02);
color:var(--text);
cursor:pointer;
display:grid;place-items:center;
transition:.15s transform, .15s background;
user-select:none;
}
.iconBtn:hover{ transform: translateY(-1px); background: rgba(255,255,255,.04); }
.iconBtn:active{ transform: translateY(0px) scale(.98); }
.input{
width:100%;
height:40px;
border-radius:12px;
border:1px solid var(--border);
background: rgba(0,0,0,.22);
color:var(--text);
padding:0 12px 0 38px;
outline:none;
font-size:14px;
}
.input::placeholder{ color: rgba(232,238,252,.45); }
.inputWrap{ position:relative; flex:1; }
.mag{
position:absolute; left:12px; top:50%; transform:translateY(-50%);
color: rgba(232,238,252,.45);
font-size:14px;
}
.sectionLabel{
font-size:12px;
color: var(--muted2);
letter-spacing:.12em;
text-transform:uppercase;
margin-top:2px;
}
.list{ display:flex; flex-direction:column; gap:10px; min-height:0; overflow:auto; padding-right:6px; }
.item{
padding:12px;
border:1px solid var(--border);
border-radius:14px;
background: rgba(255,255,255,.02);
display:flex;
gap:12px;
align-items:center;
cursor:pointer;
transition:.15s background, .15s transform;
user-select:none;
}
.item:hover{ background: rgba(255,255,255,.04); transform: translateY(-1px); }
.item.active{ border-color: rgba(78,161,255,.35); background: rgba(78,161,255,.08); }
.badge{
width:34px;height:34px;border-radius:12px;
display:grid;place-items:center;
border:1px solid var(--border);
background: rgba(0,0,0,.22);
color: rgba(232,238,252,.85);
font-family: var(--mono);
font-size:12px;
}
.itemText{ display:flex; flex-direction:column; gap:2px; }
.itemText .name{ font-weight:700; }
.itemText .sub{ font-size:12px; color: var(--muted); }
.footerRow{
margin-top:auto;
display:flex; align-items:center; justify-content:space-between; gap:10px;
padding-top:10px;
border-top:1px solid var(--border);
color: var(--muted);
font-size:12px;
}
/* Center (Chat) */
.centerBody{ padding:14px; display:flex; flex-direction:column; gap:12px; min-height:0; }
.crumbs{
display:flex; align-items:center; gap:8px;
color: var(--muted);
font-size:12px;
}
.crumb{ padding:6px 10px; border-radius:999px; border:1px solid var(--border); background: rgba(255,255,255,.02); }
.chatArea{
flex:1;
min-height:0;
border:1px dashed rgba(255,255,255,.12);
border-radius: var(--radius2);
background: rgba(0,0,0,.18);
display:flex;
align-items:center;
justify-content:center;
padding:18px;
position:relative;
overflow:hidden;
}
.chatInner{
width:100%;
height:100%;
display:flex;
flex-direction:column;
gap:12px;
overflow:auto;
padding-right:8px;
}
.emptyState{
text-align:center;
color: var(--muted);
max-width:520px;
padding:22px;
margin:auto;
}
.emptyIcon{
width:84px;height:84px;border-radius:18px;
margin:0 auto 14px auto;
border:1px solid var(--border);
background: radial-gradient(circle at 35% 30%, rgba(255,255,255,.12), rgba(255,255,255,.02) 60%),
rgba(0,0,0,.22);
}
.emptyTitle{ font-weight:800; color: var(--text); font-size:20px; margin-bottom:6px; }
.emptySub{ font-size:13px; color: var(--muted); }
.msg{
display:flex;
gap:10px;
align-items:flex-start;
}
.avatar{
width:34px;height:34px;border-radius:12px;
border:1px solid var(--border);
background: rgba(0,0,0,.22);
display:grid;place-items:center;
font-weight:800;
color: rgba(232,238,252,.9);
flex:0 0 auto;
}
.bubble{
max-width: 880px;
border:1px solid var(--border);
border-radius:16px;
padding:10px 12px;
background: rgba(255,255,255,.02);
}
.bubble.user{ border-color: rgba(78,161,255,.25); background: rgba(78,161,255,.08); }
.meta{
font-size:12px;
color: var(--muted2);
margin-bottom:6px;
display:flex; gap:10px; align-items:center;
}
.text{ white-space:pre-wrap; line-height:1.4; font-size:14px; }
.codeBlock{
margin-top:8px;
padding:10px;
border-radius:14px;
border:1px solid rgba(255,255,255,.10);
background: rgba(0,0,0,.28);
font-family: var(--mono);
font-size:12.5px;
overflow:auto;
}
/* Composer */
.composer{
display:flex;
gap:10px;
align-items:center;
padding:10px;
border:1px solid var(--border);
border-radius: 18px;
background: rgba(255,255,255,.02);
}
.composeInput{
flex:1;
height:42px;
border-radius:14px;
border:1px solid transparent;
background: rgba(0,0,0,.20);
color: var(--text);
padding:0 12px;
outline:none;
font-size:14px;
}
.composeInput:focus{ border-color: rgba(78,161,255,.35); }
.sendBtn{
width:46px;height:46px;border-radius:16px;
border:1px solid rgba(78,161,255,.35);
background: rgba(78,161,255,.22);
cursor:pointer;
display:grid;place-items:center;
transition:.15s transform, .15s background;
}
.sendBtn:hover{ transform: translateY(-1px); background: rgba(78,161,255,.28); }
.sendBtn:active{ transform: translateY(0) scale(.98); }
.micOn{ border-color: rgba(46,229,157,.35) !important; background: rgba(46,229,157,.14) !important; }
/* Right panel */
.rightBody{ padding:12px; display:flex; flex-direction:column; gap:10px; min-height:0; }
.topTools{
display:flex; align-items:center; justify-content:space-between; gap:10px;
}
.tabs{
display:flex; gap:8px; align-items:center;
}
.tab{
padding:8px 10px;
border-radius:999px;
border:1px solid var(--border);
background: rgba(255,255,255,.02);
color: var(--muted);
cursor:pointer;
user-select:none;
font-size:12px;
}
.tab.active{
color: rgba(232,238,252,.95);
border-color: rgba(78,161,255,.35);
background: rgba(78,161,255,.10);
}
.toolRow{ display:flex; gap:8px; align-items:center; }
.miniBtn{
padding:8px 10px;
border-radius:12px;
border:1px solid var(--border);
background: rgba(255,255,255,.02);
cursor:pointer;
color: var(--text);
font-size:12px;
user-select:none;
transition:.15s transform, .15s background;
}
.miniBtn:hover{ transform: translateY(-1px); background: rgba(255,255,255,.04); }
.miniBtn:active{ transform: translateY(0) scale(.98); }
.miniBtn.primary{ border-color: rgba(78,161,255,.35); background: rgba(78,161,255,.18); }
.miniBtn.danger{ border-color: rgba(255,107,107,.35); background: rgba(255,107,107,.12); }
.editorWrap{
flex:1;
min-height:0;
border:1px solid var(--border);
border-radius: var(--radius2);
overflow:hidden;
- index.html +132 -165
- rosalinda.html +153 -152
- script.js +54 -39
- style.css +37 -47
|
@@ -1,182 +1,149 @@
|
|
|
|
|
| 1 |
<!DOCTYPE html>
|
| 2 |
<html lang="fr">
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>
|
| 7 |
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
<script src="https://unpkg.com/feather-icons"></script>
|
| 9 |
-
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
|
| 10 |
<link rel="stylesheet" href="style.css">
|
| 11 |
</head>
|
| 12 |
-
<body class="
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
class="w-full pl-10 pr-4 py-2 bg-gray-700 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
|
| 29 |
-
</div>
|
| 30 |
-
</div>
|
| 31 |
-
|
| 32 |
-
<nav class="flex-1 overflow-y-auto p-2">
|
| 33 |
-
<h2 class="text-xs uppercase text-gray-500 px-2 py-1">Projets</h2>
|
| 34 |
-
<a href="#" class="flex items-center gap-3 p-2 rounded-lg bg-gray-700 mb-1">
|
| 35 |
-
<div class="p-2 bg-gray-600 rounded-lg">
|
| 36 |
-
<i data-feather="code" class="w-4 h-4"></i>
|
| 37 |
-
</div>
|
| 38 |
-
<span>Projet 1</span>
|
| 39 |
-
</a>
|
| 40 |
-
<a href="#" class="flex items-center gap-3 p-2 rounded-lg hover:bg-gray-700 mb-1">
|
| 41 |
-
<div class="p-2 bg-gray-600 rounded-lg">
|
| 42 |
-
<i data-feather="code" class="w-4 h-4"></i>
|
| 43 |
-
</div>
|
| 44 |
-
<span>Projet 2</span>
|
| 45 |
-
</a>
|
| 46 |
-
|
| 47 |
-
<h2 class="text-xs uppercase text-gray-500 px-2 py-1 mt-4">Raccourcis</h2>
|
| 48 |
-
<a href="#" class="flex items-center gap-3 p-2 rounded-lg hover:bg-gray-700 mb-1">
|
| 49 |
-
<div class="p-2 bg-gray-600 rounded-lg">
|
| 50 |
-
<i data-feather="book" class="w-4 h-4"></i>
|
| 51 |
-
</div>
|
| 52 |
-
<span>Bibliothèque</span>
|
| 53 |
-
</a>
|
| 54 |
-
<a href="rosalinda.html" class="flex items-center gap-3 p-2 rounded-lg hover:bg-gray-700 mb-1">
|
| 55 |
-
<div class="p-2 bg-gray-600 rounded-lg">
|
| 56 |
-
<i data-feather="message-circle" class="w-4 h-4"></i>
|
| 57 |
-
</div>
|
| 58 |
-
<span>Rosalinda</span>
|
| 59 |
-
</a>
|
| 60 |
-
<a href="#" class="flex items-center gap-3 p-2 rounded-lg hover:bg-gray-700 mb-1">
|
| 61 |
-
<div class="p-2 bg-gray-600 rounded-lg">
|
| 62 |
-
<i data-feather="settings" class="w-4 h-4"></i>
|
| 63 |
-
</div>
|
| 64 |
-
<span>Paramètres</span>
|
| 65 |
-
</a>
|
| 66 |
-
</nav>
|
| 67 |
-
|
| 68 |
-
<div class="p-3 border-t border-gray-700 flex justify-between">
|
| 69 |
-
<button class="text-sm flex items-center gap-1 px-3 py-1 bg-gray-700 rounded-lg hover:bg-gray-600">
|
| 70 |
-
<i data-feather="share-2" class="w-4 h-4"></i>
|
| 71 |
-
Partager
|
| 72 |
-
</button>
|
| 73 |
-
<button class="text-sm flex items-center gap-1 px-3 py-1 bg-gray-700 rounded-lg hover:bg-gray-600">
|
| 74 |
-
<i data-feather="user" class="w-4 h-4"></i>
|
| 75 |
-
Profil
|
| 76 |
-
</button>
|
| 77 |
-
</div>
|
| 78 |
</div>
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
<div class="
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
</div>
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
</div>
|
| 94 |
</div>
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
<button class="p-2 rounded-lg hover:bg-gray-600">
|
| 102 |
-
<i data-feather="paperclip"></i>
|
| 103 |
-
</button>
|
| 104 |
-
|
| 105 |
-
<input type="text" placeholder="Rechercher / écrire..."
|
| 106 |
-
class="flex-1 bg-transparent px-3 py-2 focus:outline-none">
|
| 107 |
-
|
| 108 |
-
<button class="p-2 rounded-lg hover:bg-gray-600">
|
| 109 |
-
<i data-feather="mic"></i>
|
| 110 |
-
</button>
|
| 111 |
-
<button class="p-2 rounded-lg bg-blue-600 hover:bg-blue-500 text-white">
|
| 112 |
-
<i data-feather="send"></i>
|
| 113 |
-
</button>
|
| 114 |
-
</div>
|
| 115 |
</div>
|
|
|
|
| 116 |
</div>
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
</div>
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
messageInput.addEventListener('input', () => {
|
| 161 |
-
if(messageInput.value.trim().length > 0) {
|
| 162 |
-
sendButton.classList.remove('bg-blue-600');
|
| 163 |
-
sendButton.classList.add('bg-blue-500');
|
| 164 |
-
} else {
|
| 165 |
-
sendButton.classList.add('bg-blue-600');
|
| 166 |
-
sendButton.classList.remove('bg-blue-500');
|
| 167 |
-
}
|
| 168 |
-
});
|
| 169 |
-
|
| 170 |
-
// Navigation items active state
|
| 171 |
-
const navItems = document.querySelectorAll('nav a');
|
| 172 |
-
navItems.forEach(item => {
|
| 173 |
-
item.addEventListener('click', (e) => {
|
| 174 |
-
e.preventDefault();
|
| 175 |
-
navItems.forEach(i => i.classList.remove('bg-gray-700'));
|
| 176 |
-
item.classList.add('bg-gray-700');
|
| 177 |
-
});
|
| 178 |
-
});
|
| 179 |
-
</script>
|
| 180 |
-
<script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
|
| 181 |
</body>
|
| 182 |
</html>
|
|
|
|
| 1 |
+
|
| 2 |
<!DOCTYPE html>
|
| 3 |
<html lang="fr">
|
| 4 |
<head>
|
| 5 |
<meta charset="UTF-8">
|
| 6 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 7 |
+
<title>Espace Codage</title>
|
| 8 |
<script src="https://cdn.tailwindcss.com"></script>
|
| 9 |
<script src="https://unpkg.com/feather-icons"></script>
|
|
|
|
| 10 |
<link rel="stylesheet" href="style.css">
|
| 11 |
</head>
|
| 12 |
+
<body class="text-gray-100 h-screen overflow-hidden">
|
| 13 |
+
<div class="app">
|
| 14 |
+
<!-- LEFT -->
|
| 15 |
+
<section class="card" aria-label="Sidebar">
|
| 16 |
+
<div class="cardHeader">
|
| 17 |
+
<div class="title"><span class="dot"></span><span>Espace Codage</span></div>
|
| 18 |
+
<span class="pill" id="pillProject">Projet 1</span>
|
| 19 |
+
</div>
|
| 20 |
+
|
| 21 |
+
<div class="sidebarBody">
|
| 22 |
+
<div class="searchRow">
|
| 23 |
+
<button class="iconBtn" id="btnNewProject" title="Nouveau projet">+</button>
|
| 24 |
+
<div class="inputWrap">
|
| 25 |
+
<span class="mag">🔎</span>
|
| 26 |
+
<input class="input" id="projectSearch" placeholder="Rechercher..." autocomplete="off" />
|
| 27 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
</div>
|
| 29 |
+
|
| 30 |
+
<div class="sectionLabel">Projets</div>
|
| 31 |
+
<div class="list" id="projectList"></div>
|
| 32 |
+
|
| 33 |
+
<div class="sectionLabel">Raccourcis</div>
|
| 34 |
+
<div class="list" style="gap:10px">
|
| 35 |
+
<div class="item" data-shortcut="library">
|
| 36 |
+
<div class="badge">📚</div>
|
| 37 |
+
<div class="itemText">
|
| 38 |
+
<div class="name">Bibliothèque</div>
|
| 39 |
+
<div class="sub">Templates & snippets</div>
|
| 40 |
</div>
|
| 41 |
+
</div>
|
| 42 |
+
<div class="item" data-shortcut="rosalinda">
|
| 43 |
+
<div class="badge">🤖</div>
|
| 44 |
+
<div class="itemText">
|
| 45 |
+
<div class="name">Rosalinda</div>
|
| 46 |
+
<div class="sub">Assistant local</div>
|
|
|
|
| 47 |
</div>
|
| 48 |
+
</div>
|
| 49 |
+
<div class="item" data-shortcut="settings">
|
| 50 |
+
<div class="badge">⚙️</div>
|
| 51 |
+
<div class="itemText">
|
| 52 |
+
<div class="name">Paramètres</div>
|
| 53 |
+
<div class="sub">Préférences</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
</div>
|
| 55 |
+
</div>
|
| 56 |
</div>
|
| 57 |
+
|
| 58 |
+
<div class="footerRow">
|
| 59 |
+
<span>Made with Espace Codage</span>
|
| 60 |
+
<span class="pill" style="padding:6px 10px;">Profil</span>
|
| 61 |
+
</div>
|
| 62 |
+
</div>
|
| 63 |
+
</section>
|
| 64 |
+
|
| 65 |
+
<!-- CENTER -->
|
| 66 |
+
<section class="card" aria-label="Centre">
|
| 67 |
+
<div class="cardHeader">
|
| 68 |
+
<div class="crumbs">
|
| 69 |
+
<span class="crumb">Aperçu</span>
|
| 70 |
+
<span class="crumb">Projet</span>
|
| 71 |
+
<span class="crumb" id="crumbName">Projet 1</span>
|
| 72 |
+
</div>
|
| 73 |
+
<span class="pill" id="pillMode">Mode: Preview</span>
|
| 74 |
+
</div>
|
| 75 |
+
|
| 76 |
+
<div class="centerBody">
|
| 77 |
+
<div class="chatArea">
|
| 78 |
+
<div class="chatInner" id="chatInner">
|
| 79 |
+
<!-- Empty state injected -->
|
| 80 |
+
</div>
|
| 81 |
+
</div>
|
| 82 |
+
|
| 83 |
+
<div class="composer">
|
| 84 |
+
<button class="iconBtn" id="btnPlus" title="Actions rapides">+</button>
|
| 85 |
+
<button class="iconBtn" id="btnAttach" title="Joindre (local)">📎</button>
|
| 86 |
+
<input class="composeInput" id="chatInput" placeholder="Écris ici… (ex: “crée une page”, ou colle du code HTML/CSS/JS)" />
|
| 87 |
+
<button class="iconBtn" id="btnMic" title="Micro (dictée)">🎤</button>
|
| 88 |
+
<button class="sendBtn" id="btnSend" title="Envoyer">➤</button>
|
| 89 |
+
</div>
|
| 90 |
+
</div>
|
| 91 |
+
</section>
|
| 92 |
+
|
| 93 |
+
<!-- RIGHT -->
|
| 94 |
+
<section class="card" aria-label="Droite">
|
| 95 |
+
<div class="cardHeader">
|
| 96 |
+
<div class="title" style="gap:8px;"><span style="font-weight:900">Aperçu</span></div>
|
| 97 |
+
<div class="toolRow">
|
| 98 |
+
<button class="iconBtn" id="btnBack" title="Retour">←</button>
|
| 99 |
+
<button class="iconBtn" id="btnForward" title="Avancer">→</button>
|
| 100 |
+
<button class="iconBtn" id="btnRefresh" title="Rafraîchir">⟳</button>
|
| 101 |
+
<button class="iconBtn" id="btnFull" title="Plein écran aperçu">⤢</button>
|
| 102 |
+
</div>
|
| 103 |
+
</div>
|
| 104 |
+
|
| 105 |
+
<div class="rightBody">
|
| 106 |
+
<div class="topTools">
|
| 107 |
+
<div class="tabs">
|
| 108 |
+
<div class="tab active" id="tabCode">Code</div>
|
| 109 |
+
<div class="tab" id="tabPreview">Aperçu</div>
|
| 110 |
+
</div>
|
| 111 |
+
<div class="toolRow">
|
| 112 |
+
<button class="miniBtn primary" id="btnRun">Run</button>
|
| 113 |
+
<button class="miniBtn" id="btnSave">Save</button>
|
| 114 |
+
<button class="miniBtn" id="btnDownload">Download</button>
|
| 115 |
+
<button class="miniBtn danger" id="btnReset">Reset</button>
|
| 116 |
+
</div>
|
| 117 |
+
</div>
|
| 118 |
+
|
| 119 |
+
<div class="editorWrap" id="panelCode">
|
| 120 |
+
<div class="editorHeader">
|
| 121 |
+
<span><b>Editor</b> — HTML/CSS/JS (local)</span>
|
| 122 |
+
<span class="pill" id="pillSaved">Auto-save</span>
|
| 123 |
+
</div>
|
| 124 |
+
<textarea class="editor" id="codeEditor" spellcheck="false"></textarea>
|
| 125 |
+
</div>
|
| 126 |
+
|
| 127 |
+
<div class="previewWrap" id="panelPreview" style="display:none">
|
| 128 |
+
<div class="editorHeader">
|
| 129 |
+
<span><b>Preview</b> — iframe sandbox</span>
|
| 130 |
+
<span class="pill bad" id="pillPreviewState">Hors ligne</span>
|
| 131 |
+
</div>
|
| 132 |
+
<div class="previewBox" id="previewBox">
|
| 133 |
+
<!-- Empty preview injected -->
|
| 134 |
+
</div>
|
| 135 |
+
<div class="statusBar">
|
| 136 |
+
<span id="statusLeft">Statut: Hors ligne</span>
|
| 137 |
+
<span id="statusRight">Mode: Preview</span>
|
| 138 |
+
</div>
|
| 139 |
</div>
|
| 140 |
+
</div>
|
| 141 |
+
</section>
|
| 142 |
+
</div>
|
| 143 |
+
|
| 144 |
+
<script src="script.js"></script>
|
| 145 |
+
<script>
|
| 146 |
+
feather.replace();
|
| 147 |
+
</script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
</body>
|
| 149 |
</html>
|
|
@@ -1,163 +1,164 @@
|
|
|
|
|
| 1 |
<!DOCTYPE html>
|
| 2 |
<html lang="fr">
|
| 3 |
<head>
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
</head>
|
| 11 |
<body class="bg-gray-900 text-gray-100 min-h-screen">
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
</div>
|
| 21 |
-
|
| 22 |
-
<div class="bg-gray-800 rounded-xl border border-gray-700 overflow-hidden">
|
| 23 |
-
<div class="h-[60vh] overflow-y-auto p-4 flex flex-col gap-3" id="msgs"></div>
|
| 24 |
-
|
| 25 |
-
<div class="p-3 border-t border-gray-700 bg-gray-900/50 flex items-center gap-2">
|
| 26 |
-
<button id="micBtn" class="p-2 rounded-lg hover:bg-gray-700">
|
| 27 |
-
<i data-feather="mic"></i>
|
| 28 |
-
</button>
|
| 29 |
-
<input id="inp" type="text" placeholder="Écris à Rosalinda…"
|
| 30 |
-
class="flex-1 bg-gray-700 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
|
| 31 |
-
<button id="sendBtn" class="p-2 rounded-lg bg-blue-600 hover:bg-blue-500 text-white">
|
| 32 |
-
<i data-feather="send"></i>
|
| 33 |
-
</button>
|
| 34 |
-
<button id="speakBtn" title="Lire la dernière réponse" class="p-2 rounded-lg hover:bg-gray-700">
|
| 35 |
-
<i data-feather="volume-2"></i>
|
| 36 |
-
</button>
|
| 37 |
-
</div>
|
| 38 |
-
</div>
|
| 39 |
-
|
| 40 |
-
<div class="text-sm text-gray-400 mt-4">
|
| 41 |
-
✅ Micro & voix = API du navigateur (Chrome/Edge).<br>
|
| 42 |
-
⚠️ Cette version répond avec une "IA locale simple". Prochaine étape : brancher un vrai modèle IA.
|
| 43 |
-
</div>
|
| 44 |
</div>
|
| 45 |
|
| 46 |
-
<
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
msgs.scrollTop = msgs.scrollHeight;
|
| 64 |
-
}
|
| 65 |
-
|
| 66 |
-
function rosalindaBrain(userText) {
|
| 67 |
-
const t = userText.toLowerCase();
|
| 68 |
-
if (t.includes("bonjour") || t.includes("salut")) return "Bonjour 😄 Je suis Rosalinda. Dis-moi ce que tu veux créer : site, thème, image, vidéo, plugin…";
|
| 69 |
-
if (t.includes("theme") || t.includes("thème")) return "Ok ✅ Dis-moi : (1) style (moderne, luxe, minimal, flashy), (2) couleurs, (3) 3 colonnes ou non, (4) Shopify/WooCommerce/autre.";
|
| 70 |
-
if (t.includes("image")) return "Je peux préparer une demande d'image. Dis-moi : sujet + style + format (1:1, 16:9, 9:16) + texte à afficher.";
|
| 71 |
-
if (t.includes("vidéo") || t.includes("video")) return "Je peux préparer une demande vidéo. Dis-moi : durée, style (réaliste/3D), texte à l'écran, musique oui/non.";
|
| 72 |
-
if (t.includes("micro")) return "Pour le micro : clique 🎤, autorise le micro dans ton navigateur, puis parle. Je transcris et je réponds.";
|
| 73 |
-
return "Compris ✅ Donne-moi plus de détails (objectif + plateforme + style), et je te génère une réponse claire.";
|
| 74 |
-
}
|
| 75 |
-
|
| 76 |
-
function speak(text) {
|
| 77 |
-
if (!("speechSynthesis" in window)) {
|
| 78 |
-
alert("TTS non supporté sur ce navigateur.");
|
| 79 |
-
return;
|
| 80 |
-
}
|
| 81 |
-
const utterance = new SpeechSynthesisUtterance(text);
|
| 82 |
-
utterance.lang = "fr-FR";
|
| 83 |
-
window.speechSynthesis.cancel();
|
| 84 |
-
window.speechSynthesis.speak(utterance);
|
| 85 |
-
}
|
| 86 |
-
|
| 87 |
-
function handleSend(text) {
|
| 88 |
-
const inputText = (text ?? inp.value).trim();
|
| 89 |
-
if (!inputText) return;
|
| 90 |
-
addMsg(inputText, "me");
|
| 91 |
-
inp.value = "";
|
| 92 |
-
const reply = rosalindaBrain(inputText);
|
| 93 |
-
lastAIText = reply;
|
| 94 |
-
addMsg(reply, "ai");
|
| 95 |
-
speak(reply);
|
| 96 |
-
}
|
| 97 |
-
|
| 98 |
-
sendBtn.addEventListener("click", () => handleSend());
|
| 99 |
-
inp.addEventListener("keydown", (e) => {
|
| 100 |
-
if (e.key === "Enter") handleSend();
|
| 101 |
-
});
|
| 102 |
-
|
| 103 |
-
speakBtn.addEventListener("click", () => lastAIText && speak(lastAIText));
|
| 104 |
-
|
| 105 |
-
let recognition = null;
|
| 106 |
-
let isListening = false;
|
| 107 |
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
}
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
statusEl.textContent = "Micro: erreur";
|
| 143 |
-
};
|
| 144 |
-
|
| 145 |
-
recognition.onend = () => {
|
| 146 |
-
isListening = false;
|
| 147 |
-
statusEl.textContent = "Micro: prêt";
|
| 148 |
-
if (inp.value.trim()) handleSend(inp.value);
|
| 149 |
-
};
|
| 150 |
-
|
| 151 |
-
try {
|
| 152 |
-
recognition.start();
|
| 153 |
-
} catch (e) {
|
| 154 |
-
isListening = false;
|
| 155 |
-
statusEl.textContent = "Micro: prêt";
|
| 156 |
-
}
|
| 157 |
-
});
|
| 158 |
-
|
| 159 |
-
// Initial message
|
| 160 |
-
addMsg("Bonjour 👋 Je suis Rosalinda. Clique sur le micro pour parler ou écris-moi.", "ai");
|
| 161 |
-
</script>
|
| 162 |
</body>
|
| 163 |
</html>
|
|
|
|
| 1 |
+
|
| 2 |
<!DOCTYPE html>
|
| 3 |
<html lang="fr">
|
| 4 |
<head>
|
| 5 |
+
<meta charset="UTF-8">
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 7 |
+
<title>Rosalinda — Espace Codage</title>
|
| 8 |
+
<link rel="stylesheet" href="style.css">
|
| 9 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 10 |
+
<script src="https://unpkg.com/feather-icons"></script>
|
| 11 |
</head>
|
| 12 |
<body class="bg-gray-900 text-gray-100 min-h-screen">
|
| 13 |
+
<div class="container mx-auto p-4 max-w-4xl">
|
| 14 |
+
<div class="flex justify-between items-center mb-4">
|
| 15 |
+
<div class="px-4 py-2 bg-gray-800 rounded-full border border-gray-700">
|
| 16 |
+
<span class="font-bold">Rosalinda</span> — Espace Codage
|
| 17 |
+
</div>
|
| 18 |
+
<div class="px-4 py-2 bg-gray-800 rounded-full border border-gray-700" id="status">
|
| 19 |
+
Micro: prêt
|
| 20 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
</div>
|
| 22 |
|
| 23 |
+
<div class="bg-gray-800 rounded-xl border border-gray-700 overflow-hidden">
|
| 24 |
+
<div class="h-[60vh] overflow-y-auto p-4 flex flex-col gap-3" id="msgs"></div>
|
| 25 |
+
|
| 26 |
+
<div class="p-3 border-t border-gray-700 bg-gray-900/50 flex items-center gap-2">
|
| 27 |
+
<button id="micBtn" class="p-2 rounded-lg hover:bg-gray-700">
|
| 28 |
+
<i data-feather="mic"></i>
|
| 29 |
+
</button>
|
| 30 |
+
<input id="inp" type="text" placeholder="Écris à Rosalinda…"
|
| 31 |
+
class="flex-1 bg-gray-700 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
|
| 32 |
+
<button id="sendBtn" class="p-2 rounded-lg bg-blue-600 hover:bg-blue-500 text-white">
|
| 33 |
+
<i data-feather="send"></i>
|
| 34 |
+
</button>
|
| 35 |
+
<button id="speakBtn" title="Lire la dernière réponse" class="p-2 rounded-lg hover:bg-gray-700">
|
| 36 |
+
<i data-feather="volume-2"></i>
|
| 37 |
+
</button>
|
| 38 |
+
</div>
|
| 39 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
|
| 41 |
+
<div class="text-sm text-gray-400 mt-4">
|
| 42 |
+
✅ Micro & voix = API du navigateur (Chrome/Edge).<br>
|
| 43 |
+
⚠️ Cette version répond avec une "IA locale simple". Prochaine étape : brancher un vrai modèle IA.
|
| 44 |
+
</div>
|
| 45 |
+
</div>
|
| 46 |
+
|
| 47 |
+
<script>
|
| 48 |
+
feather.replace();
|
| 49 |
+
|
| 50 |
+
const msgs = document.getElementById("msgs");
|
| 51 |
+
const inp = document.getElementById("inp");
|
| 52 |
+
const sendBtn = document.getElementById("sendBtn");
|
| 53 |
+
const micBtn = document.getElementById("micBtn");
|
| 54 |
+
const speakBtn = document.getElementById("speakBtn");
|
| 55 |
+
const statusEl = document.getElementById("status");
|
| 56 |
+
|
| 57 |
+
let lastAIText = "";
|
| 58 |
+
|
| 59 |
+
function addMsg(text, who) {
|
| 60 |
+
const msgDiv = document.createElement("div");
|
| 61 |
+
msgDiv.className = `p-3 rounded-lg max-w-[80%] ${who === "me" ? "ml-auto bg-blue-900/30" : "mr-auto bg-gray-700"}`;
|
| 62 |
+
msgDiv.textContent = text;
|
| 63 |
+
msgs.appendChild(msgDiv);
|
| 64 |
+
msgs.scrollTop = msgs.scrollHeight;
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
function rosalindaBrain(userText) {
|
| 68 |
+
const t = userText.toLowerCase();
|
| 69 |
+
if (t.includes("bonjour") || t.includes("salut")) return "Bonjour 😄 Je suis Rosalinda. Dis-moi ce que tu veux créer : site, thème, image, vidéo, plugin…";
|
| 70 |
+
if (t.includes("theme") || t.includes("thème")) return "Ok ✅ Dis-moi : (1) style (moderne, luxe, minimal, flashy), (2) couleurs, (3) 3 colonnes ou non, (4) Shopify/WooCommerce/autre.";
|
| 71 |
+
if (t.includes("image")) return "Je peux préparer une demande d'image. Dis-moi : sujet + style + format (1:1, 16:9, 9:16) + texte à afficher.";
|
| 72 |
+
if (t.includes("vidéo") || t.includes("video")) return "Je peux préparer une demande vidéo. Dis-moi : durée, style (réaliste/3D), texte à l'écran, musique oui/non.";
|
| 73 |
+
if (t.includes("micro")) return "Pour le micro : clique 🎤, autorise le micro dans ton navigateur, puis parle. Je transcris et je réponds.";
|
| 74 |
+
return "Compris ✅ Donne-moi plus de détails (objectif + plateforme + style), et je te génère une réponse claire.";
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
function speak(text) {
|
| 78 |
+
if (!("speechSynthesis" in window)) {
|
| 79 |
+
alert("TTS non supporté sur ce navigateur.");
|
| 80 |
+
return;
|
| 81 |
+
}
|
| 82 |
+
const utterance = new SpeechSynthesisUtterance(text);
|
| 83 |
+
utterance.lang = "fr-FR";
|
| 84 |
+
window.speechSynthesis.cancel();
|
| 85 |
+
window.speechSynthesis.speak(utterance);
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
function handleSend(text) {
|
| 89 |
+
const inputText = (text ?? inp.value).trim();
|
| 90 |
+
if (!inputText) return;
|
| 91 |
+
addMsg(inputText, "me");
|
| 92 |
+
inp.value = "";
|
| 93 |
+
const reply = rosalindaBrain(inputText);
|
| 94 |
+
lastAIText = reply;
|
| 95 |
+
addMsg(reply, "ai");
|
| 96 |
+
speak(reply);
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
sendBtn.addEventListener("click", () => handleSend());
|
| 100 |
+
inp.addEventListener("keydown", (e) => {
|
| 101 |
+
if (e.key === "Enter") handleSend();
|
| 102 |
+
});
|
| 103 |
+
|
| 104 |
+
speakBtn.addEventListener("click", () => lastAIText && speak(lastAIText));
|
| 105 |
+
|
| 106 |
+
let recognition = null;
|
| 107 |
+
let isListening = false;
|
| 108 |
+
|
| 109 |
+
function setupSpeechRecognition() {
|
| 110 |
+
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
| 111 |
+
if (!SpeechRecognition) return null;
|
| 112 |
+
const recognizer = new SpeechRecognition();
|
| 113 |
+
recognizer.lang = "fr-FR";
|
| 114 |
+
recognizer.interimResults = true;
|
| 115 |
+
recognizer.continuous = false;
|
| 116 |
+
return recognizer;
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
recognition = setupSpeechRecognition();
|
| 120 |
+
|
| 121 |
+
micBtn.addEventListener("click", async () => {
|
| 122 |
+
if (!recognition) {
|
| 123 |
+
alert("Reconnaissance vocale non disponible ici. Essaie Chrome/Edge.");
|
| 124 |
+
return;
|
| 125 |
+
}
|
| 126 |
+
if (isListening) return;
|
| 127 |
+
|
| 128 |
+
isListening = true;
|
| 129 |
+
statusEl.textContent = "Micro: écoute…";
|
| 130 |
+
let finalText = "";
|
| 131 |
+
|
| 132 |
+
recognition.onresult = (e) => {
|
| 133 |
+
let transcript = "";
|
| 134 |
+
for (let i = e.resultIndex; i < e.results.length; i++) {
|
| 135 |
+
transcript += e.results[i][0].transcript;
|
| 136 |
+
if (e.results[i].isFinal) finalText += e.results[i][0].transcript + " ";
|
| 137 |
}
|
| 138 |
+
inp.value = (finalText || transcript).trim();
|
| 139 |
+
};
|
| 140 |
+
|
| 141 |
+
recognition.onerror = () => {
|
| 142 |
+
isListening = false;
|
| 143 |
+
statusEl.textContent = "Micro: erreur";
|
| 144 |
+
};
|
| 145 |
+
|
| 146 |
+
recognition.onend = () => {
|
| 147 |
+
isListening = false;
|
| 148 |
+
statusEl.textContent = "Micro: prêt";
|
| 149 |
+
if (inp.value.trim()) handleSend(inp.value);
|
| 150 |
+
};
|
| 151 |
+
|
| 152 |
+
try {
|
| 153 |
+
recognition.start();
|
| 154 |
+
} catch (e) {
|
| 155 |
+
isListening = false;
|
| 156 |
+
statusEl.textContent = "Micro: prêt";
|
| 157 |
+
}
|
| 158 |
+
});
|
| 159 |
+
|
| 160 |
+
// Initial message
|
| 161 |
+
addMsg("Bonjour 👋 Je suis Rosalinda. Clique sur le micro pour parler ou écris-moi.", "ai");
|
| 162 |
+
</script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
</body>
|
| 164 |
</html>
|
|
@@ -1,44 +1,59 @@
|
|
|
|
|
| 1 |
document.addEventListener('DOMContentLoaded', () => {
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
|
|
|
|
|
|
| 29 |
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
});
|
| 35 |
|
| 36 |
-
//
|
| 37 |
-
export
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
document.addEventListener('DOMContentLoaded', () => {
|
| 3 |
+
// Initialize tooltips for icons
|
| 4 |
+
const initTooltips = () => {
|
| 5 |
+
const buttons = document.querySelectorAll('button');
|
| 6 |
+
buttons.forEach(button => {
|
| 7 |
+
const icon = button.querySelector('[data-feather]');
|
| 8 |
+
if (icon) {
|
| 9 |
+
const iconName = icon.getAttribute('data-feather');
|
| 10 |
+
button.setAttribute('title', iconName.charAt(0).toUpperCase() + iconName.slice(1));
|
| 11 |
+
}
|
| 12 |
+
});
|
| 13 |
+
};
|
| 14 |
|
| 15 |
+
// Handle microphone functionality
|
| 16 |
+
const setupMicrophone = () => {
|
| 17 |
+
const micButton = document.querySelector('#btnMic');
|
| 18 |
+
if (!micButton) return;
|
| 19 |
+
|
| 20 |
+
micButton.addEventListener('click', async () => {
|
| 21 |
+
try {
|
| 22 |
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
| 23 |
+
// Here you would typically connect to a speech recognition API
|
| 24 |
+
alert('Microphone access granted. Speech recognition would be implemented here.');
|
| 25 |
+
stream.getTracks().forEach(track => track.stop());
|
| 26 |
+
} catch (error) {
|
| 27 |
+
console.error('Error accessing microphone:', error);
|
| 28 |
+
alert('Could not access microphone. Please check permissions.');
|
| 29 |
+
}
|
| 30 |
+
});
|
| 31 |
+
};
|
| 32 |
|
| 33 |
+
// Initialize everything
|
| 34 |
+
feather.replace();
|
| 35 |
+
initTooltips();
|
| 36 |
+
setupMicrophone();
|
| 37 |
});
|
| 38 |
|
| 39 |
+
// Toast notification utility
|
| 40 |
+
export function toast(msg) {
|
| 41 |
+
const t = document.createElement("div");
|
| 42 |
+
t.textContent = msg;
|
| 43 |
+
t.style.position="fixed";
|
| 44 |
+
t.style.left="50%";
|
| 45 |
+
t.style.bottom="18px";
|
| 46 |
+
t.style.transform="translateX(-50%)";
|
| 47 |
+
t.style.padding="10px 12px";
|
| 48 |
+
t.style.borderRadius="14px";
|
| 49 |
+
t.style.border="1px solid rgba(255,255,255,.12)";
|
| 50 |
+
t.style.background="rgba(0,0,0,.55)";
|
| 51 |
+
t.style.backdropFilter="blur(8px)";
|
| 52 |
+
t.style.color="rgba(232,238,252,.95)";
|
| 53 |
+
t.style.fontSize="13px";
|
| 54 |
+
t.style.boxShadow="0 10px 24px rgba(0,0,0,.35)";
|
| 55 |
+
t.style.zIndex=9999;
|
| 56 |
+
document.body.appendChild(t);
|
| 57 |
+
setTimeout(()=>{ t.style.opacity="0"; t.style.transition="opacity .2s"; }, 1100);
|
| 58 |
+
setTimeout(()=> t.remove(), 1400);
|
| 59 |
+
}
|
|
@@ -1,60 +1,50 @@
|
|
| 1 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
::-webkit-scrollbar {
|
| 3 |
-
|
| 4 |
-
|
| 5 |
}
|
| 6 |
|
| 7 |
::-webkit-scrollbar-track {
|
| 8 |
-
|
| 9 |
-
border-radius: 4px;
|
| 10 |
}
|
| 11 |
|
| 12 |
::-webkit-scrollbar-thumb {
|
| 13 |
-
|
| 14 |
-
|
|
|
|
|
|
|
| 15 |
}
|
| 16 |
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
scrollbar-width: thin;
|
| 23 |
-
scrollbar-color: rgba(255,255,255,0.1) rgba(255,255,255,0.05);
|
| 24 |
-
}
|
| 25 |
-
|
| 26 |
-
/* Animation for empty state icons */
|
| 27 |
-
@keyframes pulse {
|
| 28 |
-
0%, 100% {
|
| 29 |
-
opacity: 0.7;
|
| 30 |
-
}
|
| 31 |
-
50% {
|
| 32 |
-
opacity: 0.3;
|
| 33 |
-
}
|
| 34 |
-
}
|
| 35 |
-
|
| 36 |
-
.empty-state-icon {
|
| 37 |
-
animation: pulse 3s ease-in-out infinite;
|
| 38 |
}
|
| 39 |
|
| 40 |
/* Responsive adjustments */
|
| 41 |
-
@media (max-width:
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
.app-container > div {
|
| 47 |
-
min-height: 300px;
|
| 48 |
-
}
|
| 49 |
-
}
|
| 50 |
-
|
| 51 |
-
/* Custom focus styles */
|
| 52 |
-
input:focus, button:focus {
|
| 53 |
-
outline: none;
|
| 54 |
-
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5);
|
| 55 |
}
|
| 56 |
-
|
| 57 |
-
/* Transition effects */
|
| 58 |
-
button, a, input {
|
| 59 |
-
transition: all 0.2s ease;
|
| 60 |
-
}
|
|
|
|
| 1 |
+
|
| 2 |
+
:root{
|
| 3 |
+
--bg:#0b1220;
|
| 4 |
+
--panel:#0f1a2b;
|
| 5 |
+
--panel2:#101f35;
|
| 6 |
+
--border:rgba(255,255,255,.08);
|
| 7 |
+
--text:#e8eefc;
|
| 8 |
+
--muted:rgba(232,238,252,.65);
|
| 9 |
+
--muted2:rgba(232,238,252,.45);
|
| 10 |
+
--accent:#4ea1ff;
|
| 11 |
+
--accent2:#7dd3fc;
|
| 12 |
+
--danger:#ff6b6b;
|
| 13 |
+
--ok:#2ee59d;
|
| 14 |
+
--shadow: 0 10px 30px rgba(0,0,0,.35);
|
| 15 |
+
--radius:16px;
|
| 16 |
+
--radius2:22px;
|
| 17 |
+
--mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono","Courier New", monospace;
|
| 18 |
+
--sans: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji","Segoe UI Emoji";
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
/* Scrollbars */
|
| 22 |
::-webkit-scrollbar {
|
| 23 |
+
width: 10px;
|
| 24 |
+
height: 10px;
|
| 25 |
}
|
| 26 |
|
| 27 |
::-webkit-scrollbar-track {
|
| 28 |
+
background: rgba(255, 255, 255, 0.05);
|
|
|
|
| 29 |
}
|
| 30 |
|
| 31 |
::-webkit-scrollbar-thumb {
|
| 32 |
+
background: rgba(255, 255, 255, 0.1);
|
| 33 |
+
border: 3px solid transparent;
|
| 34 |
+
background-clip: padding-box;
|
| 35 |
+
border-radius: 999px;
|
| 36 |
}
|
| 37 |
|
| 38 |
+
/* Body background */
|
| 39 |
+
body {
|
| 40 |
+
background: radial-gradient(1200px 600px at 40% 20%, rgba(78,161,255,.12), transparent 60%),
|
| 41 |
+
radial-gradient(900px 500px at 80% 10%, rgba(125,211,252,.08), transparent 55%),
|
| 42 |
+
var(--bg);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
}
|
| 44 |
|
| 45 |
/* Responsive adjustments */
|
| 46 |
+
@media (max-width: 1100px){
|
| 47 |
+
body{ overflow:auto; }
|
| 48 |
+
.app{ grid-template-columns: 1fr; height:auto; overflow:auto; }
|
| 49 |
+
.card{ min-height: 320px; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|