Abmacode12 commited on
Commit
d225191
·
verified ·
1 Parent(s): fa7f49a

<!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;

Files changed (4) hide show
  1. index.html +132 -165
  2. rosalinda.html +153 -152
  3. script.js +54 -39
  4. style.css +37 -47
index.html CHANGED
@@ -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>Code Whisperer Haven</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="bg-gray-900 text-gray-100 h-screen overflow-hidden">
13
- <div class="app-container h-full grid grid-cols-12 gap-2 p-2">
14
- <!-- Left Column - Menu -->
15
- <div class="col-span-3 bg-gray-800 rounded-xl shadow-xl flex flex-col border border-gray-700">
16
- <div class="p-4 border-b border-gray-700 flex items-center gap-3">
17
- <div class="w-3 h-3 rounded-full bg-gradient-to-br from-blue-400 to-blue-600 shadow-blue-500/30 shadow-md"></div>
18
- <h1 class="font-bold text-lg">Espace Codage</h1>
19
- </div>
20
-
21
- <div class="p-3 border-b border-gray-700 flex items-center gap-2">
22
- <button class="p-2 rounded-lg bg-gray-700 hover:bg-gray-600 transition">
23
- <i data-feather="plus"></i>
24
- </button>
25
- <div class="flex-1 relative">
26
- <i data-feather="search" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></i>
27
- <input type="text" placeholder="Rechercher..."
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
- <!-- Middle Column - Workspace -->
81
- <div class="col-span-6 bg-gray-800 rounded-xl shadow-xl flex flex-col border border-gray-700">
82
- <div class="p-3 border-b border-gray-700 flex gap-2">
83
- <span class="text-xs px-3 py-1 bg-gray-700 rounded-full">Aperçu</span>
84
- <span class="text-xs px-3 py-1 bg-gray-700 rounded-full">Projet</span>
85
- <span class="text-xs px-3 py-1 bg-gray-700 rounded-full">/</span>
 
 
 
 
86
  </div>
87
-
88
- <div class="flex-1 flex items-center justify-center">
89
- <div class="text-center p-6 max-w-md">
90
- <div class="mx-auto w-32 h-32 bg-gradient-to-br from-gray-700 to-gray-800 rounded-xl mb-4 border border-dashed border-gray-600"></div>
91
- <h3 class="font-bold text-lg mb-1">Zone centrale vide</h3>
92
- <p class="text-gray-400">La barre de saisie reste en bas comme demandé</p>
93
- </div>
94
  </div>
95
-
96
- <div class="p-3 border-t border-gray-700 bg-gray-900/50">
97
- <div class="flex items-center gap-2 p-2 bg-gray-700 rounded-xl">
98
- <button class="p-2 rounded-lg hover:bg-gray-600">
99
- <i data-feather="plus"></i>
100
- </button>
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
- <!-- Right Column - Preview -->
119
- <div class="col-span-3 bg-gray-800 rounded-xl shadow-xl flex flex-col border border-gray-700">
120
- <div class="p-3 border-b border-gray-700 flex justify-between items-center">
121
- <h2 class="font-bold">Aperçu</h2>
122
- <div class="flex gap-1">
123
- <button class="p-2 rounded-lg hover:bg-gray-700">
124
- <i data-feather="arrow-left"></i>
125
- </button>
126
- <button class="p-2 rounded-lg hover:bg-gray-700">
127
- <i data-feather="arrow-right"></i>
128
- </button>
129
- <button class="p-2 rounded-lg hover:bg-gray-700">
130
- <i data-feather="refresh-cw"></i>
131
- </button>
132
- <button class="p-2 rounded-lg hover:bg-gray-700">
133
- <i data-feather="maximize"></i>
134
- </button>
135
- </div>
136
- </div>
137
-
138
- <div class="flex-1 flex items-center justify-center bg-gray-900/30">
139
- <div class="text-center p-6 max-w-sm">
140
- <div class="mx-auto w-40 h-32 bg-gradient-to-br from-gray-700 to-gray-800 rounded-xl mb-4 border border-dashed border-gray-600"></div>
141
- <h3 class="font-bold text-lg mb-1">Échec du chargement de l'aperçu</h3>
142
- <p class="text-gray-400">Vérifiez l'URL / le serveur local, puis rafraîchissez</p>
143
- </div>
144
- </div>
145
-
146
- <div class="p-3 border-t border-gray-700 flex justify-between text-xs text-gray-400">
147
- <span>Statut: <span class="text-gray-300">Hors ligne</span></span>
148
- <span>Mode: <span class="text-gray-300">Preview</span></span>
149
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
  </div>
151
- </div>
152
-
153
- <script>
154
- feather.replace();
155
-
156
- // Simple interaction for the message input
157
- const messageInput = document.querySelector('.app-container input[placeholder="Rechercher / écrire..."]');
158
- const sendButton = document.querySelector('.app-container button.bg-blue-600');
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>
rosalinda.html CHANGED
@@ -1,163 +1,164 @@
 
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>Rosalinda — Espace Codage</title>
7
- <link rel="stylesheet" href="style.css">
8
- <script src="https://cdn.tailwindcss.com"></script>
9
- <script src="https://unpkg.com/feather-icons"></script>
10
  </head>
11
  <body class="bg-gray-900 text-gray-100 min-h-screen">
12
- <div class="container mx-auto p-4 max-w-4xl">
13
- <div class="flex justify-between items-center mb-4">
14
- <div class="px-4 py-2 bg-gray-800 rounded-full border border-gray-700">
15
- <span class="font-bold">Rosalinda</span> — Espace Codage
16
- </div>
17
- <div class="px-4 py-2 bg-gray-800 rounded-full border border-gray-700" id="status">
18
- Micro: prêt
19
- </div>
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
- <script>
47
- feather.replace();
48
-
49
- const msgs = document.getElementById("msgs");
50
- const inp = document.getElementById("inp");
51
- const sendBtn = document.getElementById("sendBtn");
52
- const micBtn = document.getElementById("micBtn");
53
- const speakBtn = document.getElementById("speakBtn");
54
- const statusEl = document.getElementById("status");
55
-
56
- let lastAIText = "";
57
-
58
- function addMsg(text, who) {
59
- const msgDiv = document.createElement("div");
60
- msgDiv.className = `p-3 rounded-lg max-w-[80%] ${who === "me" ? "ml-auto bg-blue-900/30" : "mr-auto bg-gray-700"}`;
61
- msgDiv.textContent = text;
62
- msgs.appendChild(msgDiv);
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
- function setupSpeechRecognition() {
109
- const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
110
- if (!SpeechRecognition) return null;
111
- const recognizer = new SpeechRecognition();
112
- recognizer.lang = "fr-FR";
113
- recognizer.interimResults = true;
114
- recognizer.continuous = false;
115
- return recognizer;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  }
117
-
118
- recognition = setupSpeechRecognition();
119
-
120
- micBtn.addEventListener("click", async () => {
121
- if (!recognition) {
122
- alert("Reconnaissance vocale non disponible ici. Essaie Chrome/Edge.");
123
- return;
124
- }
125
- if (isListening) return;
126
-
127
- isListening = true;
128
- statusEl.textContent = "Micro: écoute…";
129
- let finalText = "";
130
-
131
- recognition.onresult = (e) => {
132
- let transcript = "";
133
- for (let i = e.resultIndex; i < e.results.length; i++) {
134
- transcript += e.results[i][0].transcript;
135
- if (e.results[i].isFinal) finalText += e.results[i][0].transcript + " ";
136
- }
137
- inp.value = (finalText || transcript).trim();
138
- };
139
-
140
- recognition.onerror = () => {
141
- isListening = false;
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>
script.js CHANGED
@@ -1,44 +1,59 @@
 
1
  document.addEventListener('DOMContentLoaded', () => {
2
- // Initialize tooltips for icons
3
- const initTooltips = () => {
4
- const buttons = document.querySelectorAll('button');
5
- buttons.forEach(button => {
6
- const icon = button.querySelector('[data-feather]');
7
- if (icon) {
8
- const iconName = icon.getAttribute('data-feather');
9
- button.setAttribute('title', iconName.charAt(0).toUpperCase() + iconName.slice(1));
10
- }
11
- });
12
- };
13
 
14
- // Handle microphone functionality
15
- const setupMicrophone = () => {
16
- const micButton = document.querySelector('button i[data-feather="mic"]').parentElement;
17
- micButton.addEventListener('click', async () => {
18
- try {
19
- const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
20
- // Here you would typically connect to a speech recognition API
21
- alert('Microphone access granted. Speech recognition would be implemented here.');
22
- stream.getTracks().forEach(track => track.stop());
23
- } catch (error) {
24
- console.error('Error accessing microphone:', error);
25
- alert('Could not access microphone. Please check permissions.');
26
- }
27
- });
28
- };
 
 
29
 
30
- // Initialize everything
31
- feather.replace();
32
- initTooltips();
33
- setupMicrophone();
34
  });
35
 
36
- // Export functions if needed for modules
37
- export const utils = {
38
- initTooltips: () => {
39
- // Tooltip initialization logic
40
- },
41
- setupMicrophone: () => {
42
- // Microphone setup logic
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
+ }
style.css CHANGED
@@ -1,60 +1,50 @@
1
- /* Custom scrollbar */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  ::-webkit-scrollbar {
3
- width: 8px;
4
- height: 8px;
5
  }
6
 
7
  ::-webkit-scrollbar-track {
8
- background: rgba(255, 255, 255, 0.05);
9
- border-radius: 4px;
10
  }
11
 
12
  ::-webkit-scrollbar-thumb {
13
- background: rgba(255, 255, 255, 0.1);
14
- border-radius: 4px;
 
 
15
  }
16
 
17
- ::-webkit-scrollbar-thumb:hover {
18
- background: rgba(255, 255, 255, 0.2);
19
- }
20
- /* Chat message styles */
21
- #msgs {
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: 1024px) {
42
- .app-container {
43
- grid-template-columns: 1fr;
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
  }