web: start: move css to dedicated file

Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
This commit is contained in:
GnomeZworc 2026-06-01 19:44:44 +02:00
commit 6e976ac6e3
Signed by: nicolas.boufideline
GPG key ID: 4406BBBF8845D632
10 changed files with 288 additions and 309 deletions

View file

@ -0,0 +1,38 @@
:host {
display: block;
background: #1e1e2e;
border: 1px solid #313244;
border-radius: 8px;
padding: 16px 20px;
font-family: monospace;
color: #cdd6f4;
min-width: 260px;
}
.label {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.08em;
color: #6c7086;
margin-bottom: 6px;
}
.time {
font-size: 28px;
font-weight: 600;
color: #89b4fa;
}
.date {
font-size: 13px;
color: #a6adc8;
margin-top: 4px;
}
.agent {
margin-top: 12px;
font-size: 11px;
color: #585b70;
border-top: 1px solid #313244;
padding-top: 10px;
}

View file

@ -1,53 +1,19 @@
import { config } from '../../core/config.js' import { config } from '../../core/config.js'
const CSS = new URL('./date-display.css', import.meta.url).href
class DateDisplay extends HTMLElement { class DateDisplay extends HTMLElement {
#interval = null #interval = null
connectedCallback() { connectedCallback() {
this.attachShadow({ mode: 'open' }) this.attachShadow({ mode: 'open' })
this.shadowRoot.innerHTML = ` this.shadowRoot.innerHTML = `
<style> <link rel="stylesheet" href="${CSS}">
:host {
display: block;
background: #1e1e2e;
border: 1px solid #313244;
border-radius: 8px;
padding: 16px 20px;
font-family: monospace;
color: #cdd6f4;
min-width: 260px;
}
.label {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.08em;
color: #6c7086;
margin-bottom: 6px;
}
.time {
font-size: 28px;
font-weight: 600;
color: #89b4fa;
}
.date {
font-size: 13px;
color: #a6adc8;
margin-top: 4px;
}
.agent {
margin-top: 12px;
font-size: 11px;
color: #585b70;
border-top: 1px solid #313244;
padding-top: 10px;
}
</style>
<div class="label">Current time</div> <div class="label">Current time</div>
<div class="time" id="time"></div> <div class="time" id="time"></div>
<div class="date" id="date"></div> <div class="date" id="date"></div>
<div class="agent">agent <span id="agent-url"></span></div> <div class="agent">agent <span id="agent-url"></span></div>
` `
this.shadowRoot.getElementById('agent-url').textContent = config.agent_url this.shadowRoot.getElementById('agent-url').textContent = config.agent_url
this.#tick() this.#tick()
this.#interval = setInterval(() => this.#tick(), 1000) this.#interval = setInterval(() => this.#tick(), 1000)

View file

@ -0,0 +1,81 @@
* { box-sizing: border-box; margin: 0; padding: 0; }
.overlay {
position: fixed;
inset: 0;
background: #181825;
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
.card {
background: #1e1e2e;
border: 1px solid #313244;
border-radius: 12px;
padding: 36px 40px;
width: 100%;
max-width: 420px;
display: flex;
flex-direction: column;
gap: 24px;
}
@media (max-width: 480px) {
.card { padding: 24px 20px; margin: 0 12px; }
}
.header { display: flex; flex-direction: column; gap: 6px; }
.header h1 {
font-size: 20px;
font-weight: 600;
color: #89b4fa;
letter-spacing: 0.02em;
}
.header p { font-size: 13px; color: #6c7086; }
.fields { display: flex; flex-direction: column; gap: 14px; }
.field { display: flex; flex-direction: column; gap: 6px; }
label {
font-size: 12px;
font-weight: 500;
color: #a6adc8;
text-transform: uppercase;
letter-spacing: 0.06em;
}
input {
background: #181825;
border: 1px solid #313244;
border-radius: 6px;
padding: 9px 12px;
color: #cdd6f4;
font-size: 14px;
font-family: monospace;
outline: none;
transition: border-color 0.15s;
width: 100%;
}
input:focus { border-color: #89b4fa; }
input::placeholder { color: #45475a; }
button {
background: #89b4fa;
color: #1e1e2e;
border: none;
border-radius: 6px;
padding: 10px 16px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: background 0.15s;
width: 100%;
}
button:hover { background: #b4d0fa; }

View file

@ -18,6 +18,7 @@
// Phase 2: swap for <oidc-gate> that resolves ready with a JWT — api-client unchanged. // Phase 2: swap for <oidc-gate> that resolves ready with a JWT — api-client unchanged.
const SESSION_KEY = 'two:credentials' const SESSION_KEY = 'two:credentials'
const CSS = new URL('./login-gate.css', import.meta.url).href
class LoginGate extends HTMLElement { class LoginGate extends HTMLElement {
#resolve = null #resolve = null
@ -57,107 +58,7 @@ class LoginGate extends HTMLElement {
const overlay = document.createElement('div') const overlay = document.createElement('div')
overlay.attachShadow({ mode: 'open' }) overlay.attachShadow({ mode: 'open' })
overlay.shadowRoot.innerHTML = ` overlay.shadowRoot.innerHTML = `
<style> <link rel="stylesheet" href="${CSS}">
* { box-sizing: border-box; margin: 0; padding: 0; }
.overlay {
position: fixed;
inset: 0;
background: #181825;
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
.card {
background: #1e1e2e;
border: 1px solid #313244;
border-radius: 12px;
padding: 36px 40px;
width: 100%;
max-width: 420px;
display: flex;
flex-direction: column;
gap: 24px;
}
.header {
display: flex;
flex-direction: column;
gap: 6px;
}
.header h1 {
font-size: 20px;
font-weight: 600;
color: #89b4fa;
letter-spacing: 0.02em;
}
.header p {
font-size: 13px;
color: #6c7086;
}
.fields {
display: flex;
flex-direction: column;
gap: 14px;
}
.field {
display: flex;
flex-direction: column;
gap: 6px;
}
label {
font-size: 12px;
font-weight: 500;
color: #a6adc8;
text-transform: uppercase;
letter-spacing: 0.06em;
}
input {
background: #181825;
border: 1px solid #313244;
border-radius: 6px;
padding: 9px 12px;
color: #cdd6f4;
font-size: 14px;
font-family: monospace;
outline: none;
transition: border-color 0.15s;
width: 100%;
}
input:focus {
border-color: #89b4fa;
}
input::placeholder {
color: #45475a;
}
button {
background: #89b4fa;
color: #1e1e2e;
border: none;
border-radius: 6px;
padding: 10px 16px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: background 0.15s, opacity 0.15s;
width: 100%;
}
button:hover { background: #b4d0fa; }
</style>
<div class="overlay"> <div class="overlay">
<div class="card"> <div class="card">
<div class="header"> <div class="header">

View file

@ -0,0 +1,30 @@
:host {
display: inline-flex;
}
button {
display: inline-flex;
align-items: center;
gap: 7px;
background: transparent;
border: 1px solid #313244;
border-radius: 6px;
padding: 7px 12px;
color: #a6adc8;
font-size: 13px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
cursor: pointer;
transition: background 0.12s, color 0.12s, border-color 0.12s;
}
button:hover {
background: #313244;
color: #f38ba8;
border-color: #f38ba8;
}
.icon { display: flex; align-items: center; }
/* compact attribute — icon only */
:host([compact]) button { padding: 7px; }
:host([compact]) .label { display: none; }

View file

@ -1,63 +1,21 @@
// Logout button — drop anywhere in the DOM. const CSS = new URL('./logout-button.css', import.meta.url).href
//
// <logout-button></logout-button> full button with label
// <logout-button compact></logout-button> icon only (for tight spaces)
//
// Delegates to <login-gate>.logout(). Works regardless of where it sits
// (header, members panel, dropdown…) since it resolves the gate from the document.
//
// Phase 2: if login-gate is swapped for oidc-gate, this still works as long as
// the auth component exposes a logout() method (see #gate()).
const ICON = `<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>` const ICON = `<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>`
class LogoutButton extends HTMLElement { class LogoutButton extends HTMLElement {
connectedCallback() { connectedCallback() {
this.attachShadow({ mode: 'open' }) this.attachShadow({ mode: 'open' })
const compact = this.hasAttribute('compact')
this.shadowRoot.innerHTML = ` this.shadowRoot.innerHTML = `
<style> <link rel="stylesheet" href="${CSS}">
:host { display: inline-flex; }
button {
display: inline-flex;
align-items: center;
gap: 7px;
background: transparent;
border: 1px solid #313244;
border-radius: 6px;
padding: ${compact ? '7px' : '7px 12px'};
color: #a6adc8;
font-size: 13px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
cursor: pointer;
transition: background 0.12s, color 0.12s, border-color 0.12s;
}
button:hover {
background: #313244;
color: #f38ba8;
border-color: #f38ba8;
}
.icon { display: flex; align-items: center; }
.label { ${compact ? 'display: none;' : ''} }
</style>
<button type="button" title="Log out"> <button type="button" title="Log out">
<span class="icon">${ICON}</span> <span class="icon">${ICON}</span>
<span class="label">Log out</span> <span class="label">Log out</span>
</button> </button>
` `
this.shadowRoot.querySelector('button') this.shadowRoot.querySelector('button')
.addEventListener('click', () => this.#logout()) .addEventListener('click', () => this.#logout())
} }
// Resolves the auth component. Today: login-gate. Tomorrow: any element
// exposing logout() (oidc-gate, oauth-gate…).
#gate() { #gate() {
return document.querySelector('login-gate, [data-auth-gate]') return document.querySelector('login-gate, [data-auth-gate]')
} }

View file

@ -0,0 +1,40 @@
:host {
display: flex;
flex-direction: column;
width: 220px;
min-width: 220px;
background: #1e1e2e;
border-right: 1px solid #313244;
padding: 16px 12px;
gap: 4px;
height: 100%;
overflow: hidden;
box-sizing: border-box;
}
.item {
display: flex;
align-items: center;
gap: 10px;
padding: 11px 12px;
border-radius: 6px;
text-decoration: none;
color: #a6adc8;
font-size: 14px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
min-height: 44px;
transition: background 0.1s, color 0.1s;
}
.item:hover { background: #313244; color: #cdd6f4; }
.item.active { background: #313244; color: #89b4fa; }
.item.active .icon { color: #89b4fa; }
.icon {
display: flex;
align-items: center;
color: #6c7086;
flex-shrink: 0;
}
.item:hover .icon { color: #cdd6f4; }

View file

@ -18,6 +18,7 @@ const ICONS = {
vm: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/></svg>`, vm: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/></svg>`,
} }
const CSS = new URL('./side-nav.css', import.meta.url).href
const MQ = window.matchMedia('(max-width: 768px)') const MQ = window.matchMedia('(max-width: 768px)')
const NAV_W = 220 // px const NAV_W = 220 // px
@ -67,44 +68,7 @@ class SideNav extends HTMLElement {
}).join('') }).join('')
this.shadowRoot.innerHTML = ` this.shadowRoot.innerHTML = `
<style> <link rel="stylesheet" href="${CSS}">
:host {
/* Géométrie desktop définie ici ; mobile gérée par this.style en JS */
display: flex;
flex-direction: column;
width: ${NAV_W}px;
min-width: ${NAV_W}px;
background: #1e1e2e;
border-right: 1px solid #313244;
padding: 16px 12px;
gap: 4px;
height: 100%;
overflow: hidden; /* empêche tout débordement */
box-sizing: border-box;
}
.item {
display: flex;
align-items: center;
gap: 10px;
padding: 11px 12px;
border-radius: 6px;
text-decoration: none;
color: #a6adc8;
font-size: 14px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
min-height: 44px;
transition: background 0.1s, color 0.1s;
}
.item:hover { background: #313244; color: #cdd6f4; }
.item.active { background: #313244; color: #89b4fa; }
.item.active .icon { color: #89b4fa; }
.icon {
display: flex; align-items: center;
color: #6c7086; flex-shrink: 0;
}
.item:hover .icon { color: #cdd6f4; }
</style>
${links} ${links}
` `

View file

@ -0,0 +1,83 @@
:host {
--width: 480px; /* overridden via this.style.setProperty in JS */
--offset: 0px;
--dur: 220ms;
--actual-width: min(var(--width), 100vw);
position: fixed;
top: 0;
right: calc(-1 * var(--actual-width));
width: var(--actual-width);
height: 100vh;
background: #1e1e2e;
border-left: 1px solid #313244;
box-shadow: -8px 0 32px rgba(0, 0, 0, 0.4);
display: flex;
flex-direction: column;
z-index: calc(1000 + var(--stack, 0));
transition: right var(--dur) cubic-bezier(0.4, 0, 0.2, 1);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
color: #cdd6f4;
}
:host([data-open]) {
right: var(--offset);
}
@media (max-width: 600px) {
:host {
--actual-width: 100vw;
}
:host([data-open]) {
right: 0;
}
}
.header {
display: flex;
align-items: center;
gap: 12px;
padding: 16px 20px;
border-bottom: 1px solid #313244;
flex-shrink: 0;
}
.title {
flex: 1;
font-size: 15px;
font-weight: 600;
color: #cdd6f4;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.close {
display: flex;
align-items: center;
justify-content: center;
background: transparent;
border: 1px solid transparent;
border-radius: 6px;
padding: 5px;
color: #6c7086;
cursor: pointer;
transition: background 0.12s, color 0.12s, border-color 0.12s;
flex-shrink: 0;
}
.close:hover {
background: #313244;
border-color: #45475a;
color: #f38ba8;
}
.body {
flex: 1;
overflow-y: auto;
padding: 20px;
}
.body::-webkit-scrollbar { width: 6px; }
.body::-webkit-scrollbar-track { background: transparent; }
.body::-webkit-scrollbar-thumb { background: #45475a; border-radius: 3px; }

View file

@ -25,6 +25,8 @@
// Clicking the backdrop closes only the topmost panel. // Clicking the backdrop closes only the topmost panel.
// ESC closes the topmost panel. // ESC closes the topmost panel.
const CSS = new URL('./side-panel.css', import.meta.url).href
const CLOSE_ICON = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" const CLOSE_ICON = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/> <line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
@ -103,95 +105,11 @@ export class SidePanel extends HTMLElement {
const title = this.getAttribute('title') ?? '' const title = this.getAttribute('title') ?? ''
const width = this.getAttribute('width') ?? '480px' const width = this.getAttribute('width') ?? '480px'
// Passe la largeur comme custom property — référencée dans le CSS via var(--width)
this.style.setProperty('--width', width)
this.shadowRoot.innerHTML = ` this.shadowRoot.innerHTML = `
<style> <link rel="stylesheet" href="${CSS}">
:host {
--offset: 0px;
--width: ${width};
--dur: ${ANIM_DURATION}ms;
/* Cap width to viewport — never overflows on mobile */
--actual-width: min(var(--width), 100vw);
position: fixed;
top: 0;
right: calc(-1 * var(--actual-width));
width: var(--actual-width);
height: 100vh;
background: #1e1e2e;
border-left: 1px solid #313244;
box-shadow: -8px 0 32px rgba(0,0,0,0.4);
display: flex;
flex-direction: column;
z-index: calc(1000 + var(--stack, 0));
transition: right var(--dur) cubic-bezier(0.4, 0, 0.2, 1);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
color: #cdd6f4;
}
:host([data-open]) {
right: var(--offset);
}
/* On mobile: always full-width, no stack offset */
@media (max-width: 600px) {
:host {
--actual-width: 100vw;
}
:host([data-open]) {
right: 0;
}
}
.header {
display: flex;
align-items: center;
gap: 12px;
padding: 16px 20px;
border-bottom: 1px solid #313244;
flex-shrink: 0;
}
.title {
flex: 1;
font-size: 15px;
font-weight: 600;
color: #cdd6f4;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.close {
display: flex;
align-items: center;
justify-content: center;
background: transparent;
border: 1px solid transparent;
border-radius: 6px;
padding: 5px;
color: #6c7086;
cursor: pointer;
transition: background 0.12s, color 0.12s, border-color 0.12s;
flex-shrink: 0;
}
.close:hover {
background: #313244;
border-color: #45475a;
color: #f38ba8;
}
.body {
flex: 1;
overflow-y: auto;
padding: 20px;
}
.body::-webkit-scrollbar { width: 6px; }
.body::-webkit-scrollbar-track { background: transparent; }
.body::-webkit-scrollbar-thumb { background: #45475a; border-radius: 3px; }
</style>
<div class="header"> <div class="header">
<span class="title">${title}</span> <span class="title">${title}</span>
<button class="close" aria-label="Close">${CLOSE_ICON}</button> <button class="close" aria-label="Close">${CLOSE_ICON}</button>