mirror of
https://github.com/System-End/spaces.git
synced 2026-04-19 16:38:24 +00:00
feat: add styling to pages :D
This commit is contained in:
parent
5829ccc6f0
commit
33fa885336
8 changed files with 1177 additions and 648 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -5,3 +5,4 @@ build
|
|||
.github
|
||||
.vscode
|
||||
.DS_Store
|
||||
.claude
|
||||
6
client/package-lock.json
generated
6
client/package-lock.json
generated
|
|
@ -637,7 +637,6 @@
|
|||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.53.1.tgz",
|
||||
"integrity": "sha512-Q4/hHkktZogGhN5iqxqSi9sjEVoe/NbIxX4hXEHoasTxj+TxEQVAq66LnDMdAZxjmsodkoI5F3slqsS68U7FNw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
|
|
@ -659,7 +658,6 @@
|
|||
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.3.tgz",
|
||||
"integrity": "sha512-h8jl1TZ76eGs3o2dIBSsvXDLb1m/Ec1iej8ZMdz+PsaFUsftZeWe2CZOI3qogEsMNaywc17gu0q6cQDzh/weCQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.15.9",
|
||||
"postcss": "^8.4.18",
|
||||
|
|
@ -1041,8 +1039,7 @@
|
|||
"version": "3.53.1",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.53.1.tgz",
|
||||
"integrity": "sha512-Q4/hHkktZogGhN5iqxqSi9sjEVoe/NbIxX4hXEHoasTxj+TxEQVAq66LnDMdAZxjmsodkoI5F3slqsS68U7FNw==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
"dev": true
|
||||
},
|
||||
"svelte-hmr": {
|
||||
"version": "0.15.0",
|
||||
|
|
@ -1056,7 +1053,6 @@
|
|||
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.3.tgz",
|
||||
"integrity": "sha512-h8jl1TZ76eGs3o2dIBSsvXDLb1m/Ec1iej8ZMdz+PsaFUsftZeWe2CZOI3qogEsMNaywc17gu0q6cQDzh/weCQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"esbuild": "^0.15.9",
|
||||
"fsevents": "~2.3.2",
|
||||
|
|
|
|||
BIN
client/public/group-photo.jpg
Normal file
BIN
client/public/group-photo.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.6 MiB |
|
|
@ -1,12 +1,56 @@
|
|||
@font-face {
|
||||
font-family: 'Phantom Sans';
|
||||
src: url('https://assets.hackclub.com/fonts/Phantom_Sans_0.7/Regular.woff2') format('woff2'),
|
||||
url('https://assets.hackclub.com/fonts/Phantom_Sans_0.7/Regular.woff') format('woff');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Phantom Sans';
|
||||
src: url('https://assets.hackclub.com/fonts/Phantom_Sans_0.7/Italic.woff2') format('woff2'),
|
||||
url('https://assets.hackclub.com/fonts/Phantom_Sans_0.7/Italic.woff') format('woff');
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Phantom Sans';
|
||||
src: url('https://assets.hackclub.com/fonts/Phantom_Sans_0.7/Bold.woff2') format('woff2'),
|
||||
url('https://assets.hackclub.com/fonts/Phantom_Sans_0.7/Bold.woff') format('woff');
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
:root {
|
||||
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
|
||||
--red: #ec3750;
|
||||
--orange: #ff8c37;
|
||||
--yellow: #f1c40f;
|
||||
--green: #33d6a6;
|
||||
--cyan: #5bc0de;
|
||||
--blue: #338eda;
|
||||
--purple: #a633d6;
|
||||
--muted: #8492a6;
|
||||
--darker: #121217;
|
||||
--dark: #17171d;
|
||||
--darkless: #252429;
|
||||
--black: #1f2d3d;
|
||||
--steel: #273444;
|
||||
--slate: #3c4858;
|
||||
--smoke: #e0e6ed;
|
||||
--snow: #f9fafc;
|
||||
--white: #ffffff;
|
||||
|
||||
font-family: 'Phantom Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
color: var(--black);
|
||||
background-color: var(--snow);
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
|
|
@ -15,67 +59,40 @@
|
|||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
#app {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--blue);
|
||||
text-decoration: none;
|
||||
transition: color 0.125s ease-in-out;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--red);
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-weight: bold;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #1a1a1a;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
font-family: 'Phantom Sans', sans-serif;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
input, textarea, select {
|
||||
font-family: 'Phantom Sans', sans-serif;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,13 +4,14 @@
|
|||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let mode = 'login'; // 'login', 'signup', or 'verify'
|
||||
let mode = 'login';
|
||||
let email = '';
|
||||
let username = '';
|
||||
let verificationCode = '';
|
||||
let error = '';
|
||||
let loading = false;
|
||||
let message = '';
|
||||
let displayMode = 'login';
|
||||
|
||||
async function sendVerificationCode() {
|
||||
error = '';
|
||||
|
|
@ -104,37 +105,41 @@
|
|||
}
|
||||
|
||||
function switchMode(newMode) {
|
||||
mode = newMode;
|
||||
error = '';
|
||||
message = '';
|
||||
verificationCode = '';
|
||||
setTimeout(() => {
|
||||
mode = newMode;
|
||||
}, 400);
|
||||
setTimeout(() => {
|
||||
displayMode = newMode;
|
||||
}, 800);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="auth-container">
|
||||
<div class="auth-card">
|
||||
<h2>Hack Club Spaces</h2>
|
||||
<svelte:head>
|
||||
<link rel="stylesheet" href="/src/styles/auth.css" />
|
||||
</svelte:head>
|
||||
|
||||
{#if mode === 'login' || mode === 'signup'}
|
||||
<div class="tabs">
|
||||
<button
|
||||
class:active={mode === 'login'}
|
||||
on:click={() => switchMode('login')}
|
||||
>
|
||||
Login
|
||||
</button>
|
||||
<button
|
||||
class:active={mode === 'signup'}
|
||||
on:click={() => switchMode('signup')}
|
||||
>
|
||||
Sign Up
|
||||
</button>
|
||||
<a href="https://hackclub.com/">
|
||||
<img class="flag-banner" src="https://assets.hackclub.com/flag-orpheus-top.svg" alt="Hack Club"/>
|
||||
</a>
|
||||
|
||||
<div class="auth-container" class:signup-mode={mode === 'signup'}>
|
||||
<div class="auth-panel auth-form-panel">
|
||||
<div class="auth-form-content">
|
||||
<div class="auth-header">
|
||||
<img class="auth-logo" src="https://icons.hackclub.com/api/icons/ec3750/clubs" alt="Hack Club" />
|
||||
<h2 class="auth-title">{displayMode === 'signup' ? 'Join Hack Club Spaces' : 'Welcome Back'}</h2>
|
||||
<p class="auth-subtitle">Lorem ipsum dolor sit amet</p>
|
||||
</div>
|
||||
|
||||
{#if mode === 'login' || mode === 'signup'}
|
||||
<form on:submit|preventDefault={sendVerificationCode}>
|
||||
<div class="form-group">
|
||||
<label for="email">Email</label>
|
||||
<label class="form-label" for="email">Email</label>
|
||||
<input
|
||||
class="form-input"
|
||||
id="email"
|
||||
type="email"
|
||||
bind:value={email}
|
||||
|
|
@ -143,39 +148,47 @@
|
|||
/>
|
||||
</div>
|
||||
|
||||
{#if mode === 'signup'}
|
||||
<div class="form-group">
|
||||
<label for="username">Username</label>
|
||||
<div class="form-group username-field" class:show={mode === 'signup'}>
|
||||
<label class="form-label" for="username">Username</label>
|
||||
<input
|
||||
class="form-input"
|
||||
id="username"
|
||||
type="text"
|
||||
bind:value={username}
|
||||
required
|
||||
required={mode === 'signup'}
|
||||
maxlength="100"
|
||||
placeholder="Choose a username"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if error}
|
||||
<div class="error">{error}</div>
|
||||
<div class="error-message">{error}</div>
|
||||
{/if}
|
||||
|
||||
{#if message}
|
||||
<div class="success">{message}</div>
|
||||
<div class="success-message">{message}</div>
|
||||
{/if}
|
||||
|
||||
<button type="submit" disabled={loading || !email}>
|
||||
<button class="primary-button" type="submit" disabled={loading || !email}>
|
||||
{loading ? 'Sending...' : 'Send Verification Code'}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="auth-mode-switch">
|
||||
{#if mode === 'login'}
|
||||
Don't have an account? <span class="auth-mode-link" on:click={() => switchMode('signup')} on:keypress={(e) => e.key === 'Enter' && switchMode('signup')} role="button" tabindex="0">Sign up</span>
|
||||
{:else}
|
||||
Already have an account? <span class="auth-mode-link" on:click={() => switchMode('login')} on:keypress={(e) => e.key === 'Enter' && switchMode('login')} role="button" tabindex="0">Log in</span>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if mode === 'verify'}
|
||||
<p class="info">Check your email for the verification code</p>
|
||||
<p class="info-message">Check your email for the verification code</p>
|
||||
|
||||
<form on:submit|preventDefault={mode === 'login' ? handleLogin : handleSignup}>
|
||||
<div class="form-group">
|
||||
<label for="code">Verification Code</label>
|
||||
<label class="form-label" for="code">Verification Code</label>
|
||||
<input
|
||||
class="form-input"
|
||||
id="code"
|
||||
type="text"
|
||||
bind:value={verificationCode}
|
||||
|
|
@ -185,149 +198,27 @@
|
|||
</div>
|
||||
|
||||
{#if error}
|
||||
<div class="error">{error}</div>
|
||||
<div class="error-message">{error}</div>
|
||||
{/if}
|
||||
|
||||
<button type="submit" disabled={loading || !verificationCode}>
|
||||
<button class="primary-button" type="submit" disabled={loading || !verificationCode}>
|
||||
{loading ? 'Verifying...' : mode === 'signup' ? 'Complete Sign Up' : 'Login'}
|
||||
</button>
|
||||
|
||||
<button type="button" class="secondary" on:click={() => switchMode(mode)}>
|
||||
<button class="secondary-button" type="button" on:click={() => switchMode(mode)}>
|
||||
Resend Code
|
||||
</button>
|
||||
</form>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="auth-panel auth-image-panel">
|
||||
<div class="auth-image-content">
|
||||
<img class="auth-image" src="/group-photo.jpg" alt="Hack Club Shipwrecked" />
|
||||
<div class="image-caption">
|
||||
Hackers at <a href="https://shipwrecked.hackclub.com" target="_blank" rel="noopener noreferrer">Shipwrecked</a>, 2025
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.auth-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.auth-card {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 12px;
|
||||
padding: 2rem;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
color: #ec3750;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.tabs button {
|
||||
flex: 1;
|
||||
padding: 0.75rem;
|
||||
background: transparent;
|
||||
border: 1px solid #646cff;
|
||||
color: #646cff;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.tabs button.active {
|
||||
background: #646cff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid #444;
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
color: inherit;
|
||||
font-size: 1rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
input:focus {
|
||||
outline: none;
|
||||
border-color: #646cff;
|
||||
}
|
||||
|
||||
button[type="submit"], .secondary {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
margin-top: 0.5rem;
|
||||
border-radius: 8px;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
button[type="submit"] {
|
||||
background: #646cff;
|
||||
border: none;
|
||||
color: white;
|
||||
}
|
||||
|
||||
button[type="submit"]:hover:not(:disabled) {
|
||||
background: #535bf2;
|
||||
}
|
||||
|
||||
button[type="submit"]:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.secondary {
|
||||
background: transparent;
|
||||
border: 1px solid #646cff;
|
||||
color: #646cff;
|
||||
}
|
||||
|
||||
.secondary:hover {
|
||||
background: rgba(100, 108, 255, 0.1);
|
||||
}
|
||||
|
||||
.error {
|
||||
padding: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
border: 1px solid rgba(239, 68, 68, 0.3);
|
||||
border-radius: 8px;
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.success {
|
||||
padding: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
background: rgba(34, 197, 94, 0.1);
|
||||
border: 1px solid rgba(34, 197, 94, 0.3);
|
||||
border-radius: 8px;
|
||||
color: #22c55e;
|
||||
}
|
||||
|
||||
.info {
|
||||
text-align: center;
|
||||
margin-bottom: 1.5rem;
|
||||
color: #888;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -15,6 +15,8 @@
|
|||
let loading = false;
|
||||
let actionLoading = {};
|
||||
let actionError = {};
|
||||
let dropdownOpen = false;
|
||||
let showPassword = false;
|
||||
|
||||
const spaceTypes = [
|
||||
{ value: 'code-server', label: 'VS Code Server', description: 'Web-based code editor' },
|
||||
|
|
@ -79,7 +81,6 @@
|
|||
actionLoading = actionLoading;
|
||||
actionError = actionError;
|
||||
|
||||
// Find the space to get its access_url
|
||||
const space = spaces.find(s => s.id === spaceId);
|
||||
|
||||
try {
|
||||
|
|
@ -176,67 +177,139 @@
|
|||
dispatch('signout');
|
||||
}
|
||||
|
||||
function getStatusColor(status) {
|
||||
function getStatusClass(status) {
|
||||
switch(status?.toLowerCase()) {
|
||||
case 'running': return '#22c55e';
|
||||
case 'stopped': return '#ef4444';
|
||||
case 'created': return '#eab308';
|
||||
default: return '#888';
|
||||
case 'running': return 'status-running';
|
||||
case 'stopped': return 'status-stopped';
|
||||
case 'created': return 'status-created';
|
||||
default: return 'status-unknown';
|
||||
}
|
||||
}
|
||||
|
||||
function toggleDropdown() {
|
||||
dropdownOpen = !dropdownOpen;
|
||||
}
|
||||
|
||||
function selectSpaceType(value) {
|
||||
newSpaceType = value;
|
||||
dropdownOpen = false;
|
||||
}
|
||||
|
||||
$: selectedType = spaceTypes.find(type => type.value === newSpaceType) || spaceTypes[0];
|
||||
|
||||
function togglePasswordVisibility() {
|
||||
showPassword = !showPassword;
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<link rel="stylesheet" href="/src/styles/dashboard.css" />
|
||||
</svelte:head>
|
||||
|
||||
<div class="dashboard">
|
||||
<header>
|
||||
<header class="dashboard-header">
|
||||
<div class="header-content">
|
||||
<img class="dashboard-logo" src="https://icons.hackclub.com/api/icons/ec3750/clubs" alt="Hack Club" />
|
||||
<div>
|
||||
<h1>Hack Club Spaces</h1>
|
||||
<p class="welcome">Welcome, {username}!</p>
|
||||
<h1 class="dashboard-title">Hack Club Spaces</h1>
|
||||
<p class="welcome-text">Welcome, {username}!</p>
|
||||
</div>
|
||||
<button class="signout" on:click={handleSignOut}>Sign Out</button>
|
||||
</div>
|
||||
<button class="signout-button" on:click={handleSignOut}>Sign Out</button>
|
||||
</header>
|
||||
|
||||
<div class="content">
|
||||
<div class="dashboard-content">
|
||||
<div class="actions-bar">
|
||||
<button class="primary" on:click={() => showCreateForm = !showCreateForm}>
|
||||
<button class="btn-primary" on:click={() => showCreateForm = !showCreateForm}>
|
||||
{showCreateForm ? 'Cancel' : '+ Create New Space'}
|
||||
</button>
|
||||
<button class="secondary" on:click={loadSpaces}>
|
||||
<button class="btn-secondary" on:click={loadSpaces}>
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if showCreateForm}
|
||||
<div class="create-form">
|
||||
<h3>Create New Space</h3>
|
||||
<h3 class="create-form-title">Create New Space</h3>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="type">Space Type</label>
|
||||
<select id="type" bind:value={newSpaceType}>
|
||||
<label class="form-label" for="type">Space Type</label>
|
||||
<div class="custom-select">
|
||||
<button
|
||||
type="button"
|
||||
class="select-trigger"
|
||||
class:open={dropdownOpen}
|
||||
on:click={toggleDropdown}
|
||||
>
|
||||
{selectedType.label}
|
||||
</button>
|
||||
<div class="select-arrow" class:open={dropdownOpen}></div>
|
||||
<div class="select-dropdown" class:open={dropdownOpen}>
|
||||
{#each spaceTypes as spaceType}
|
||||
<option value={spaceType.value}>
|
||||
{spaceType.label} - {spaceType.description}
|
||||
</option>
|
||||
<div
|
||||
class="select-option"
|
||||
class:selected={spaceType.value === newSpaceType}
|
||||
on:click={() => selectSpaceType(spaceType.value)}
|
||||
on:keypress={(e) => e.key === 'Enter' && selectSpaceType(spaceType.value)}
|
||||
role="option"
|
||||
tabindex="0"
|
||||
aria-selected={spaceType.value === newSpaceType}
|
||||
>
|
||||
<div class="option-label">{spaceType.label}</div>
|
||||
<div class="option-description">{spaceType.description}</div>
|
||||
</div>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<label class="form-label" for="password">Password</label>
|
||||
<div class="password-input-wrapper">
|
||||
{#if showPassword}
|
||||
<input
|
||||
class="form-input password-input"
|
||||
id="password"
|
||||
type="text"
|
||||
bind:value={newSpacePassword}
|
||||
required
|
||||
placeholder="Set a password for this space"
|
||||
/>
|
||||
{:else}
|
||||
<input
|
||||
class="form-input password-input"
|
||||
id="password"
|
||||
type="password"
|
||||
bind:value={newSpacePassword}
|
||||
required
|
||||
placeholder="Set a password for this space"
|
||||
/>
|
||||
{/if}
|
||||
<button
|
||||
type="button"
|
||||
class="password-toggle"
|
||||
on:click={togglePasswordVisibility}
|
||||
aria-label={showPassword ? 'Hide password' : 'Show password'}
|
||||
>
|
||||
{#if showPassword}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 256 256">
|
||||
<path d="M53.92,34.62A8,8,0,1,0,42.08,45.38L61.32,66.55C25,88.84,9.38,123.2,8.69,124.76a8,8,0,0,0,0,6.5c.35.79,8.82,19.57,27.65,38.4C61.43,194.74,93.12,208,128,208a127.11,127.11,0,0,0,52.07-10.83l22,24.21a8,8,0,1,0,11.84-10.76Zm47.33,75.84,41.67,45.85a32,32,0,0,1-41.67-45.85ZM128,192c-30.78,0-57.67-11.19-79.93-33.25A133.16,133.16,0,0,1,25,128c4.69-8.79,19.66-33.39,47.35-49.38l18,19.75a48,48,0,0,0,63.66,70l14.73,16.2A112,112,0,0,1,128,192Zm6-95.43a8,8,0,0,1,3-15.72,48.16,48.16,0,0,1,38.77,42.64,8,8,0,0,1-7.22,8.71,6.39,6.39,0,0,1-.75,0,8,8,0,0,1-8-7.26A32.09,32.09,0,0,0,134,96.57Zm113.28,34.69c-.42.94-10.55,23.37-33.36,43.8a8,8,0,1,1-10.67-11.92A132.77,132.77,0,0,0,231.05,128a133.15,133.15,0,0,0-23.12-30.77C185.67,75.19,158.78,64,128,64a118.37,118.37,0,0,0-19.36,1.57A8,8,0,1,1,106,49.79,134,134,0,0,1,128,48c34.88,0,66.57,13.26,91.66,38.35,18.83,18.83,27.3,37.62,27.65,38.41A8,8,0,0,1,247.31,131.26Z"></path>
|
||||
</svg>
|
||||
{:else}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 256 256">
|
||||
<path d="M247.31,124.76c-.35-.79-8.82-19.58-27.65-38.41C194.57,61.26,162.88,48,128,48S61.43,61.26,36.34,86.35C17.51,105.18,9,124,8.69,124.76a8,8,0,0,0,0,6.5c.35.79,8.82,19.57,27.65,38.4C61.43,194.74,93.12,208,128,208s66.57-13.26,91.66-38.34c18.83-18.83,27.3-37.61,27.65-38.4A8,8,0,0,0,247.31,124.76ZM128,192c-30.78,0-57.67-11.19-79.93-33.25A133.47,133.47,0,0,1,25,128,133.33,133.33,0,0,1,48.07,97.25C70.33,75.19,97.22,64,128,64s57.67,11.19,79.93,33.25A133.46,133.46,0,0,1,231.05,128C223.84,141.46,192.43,192,128,192Zm0-112a48,48,0,1,0,48,48A48.05,48.05,0,0,0,128,80Zm0,80a32,32,0,1,1,32-32A32,32,0,0,1,128,160Z"></path>
|
||||
</svg>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if error}
|
||||
<div class="error">{error}</div>
|
||||
<div class="error-message">{error}</div>
|
||||
{/if}
|
||||
|
||||
<button
|
||||
class="primary"
|
||||
class="btn-primary"
|
||||
on:click={createSpace}
|
||||
disabled={loading || !newSpacePassword}
|
||||
>
|
||||
|
|
@ -246,7 +319,7 @@
|
|||
{/if}
|
||||
|
||||
<div class="spaces-list">
|
||||
<h3>Your Spaces</h3>
|
||||
<h3 class="section-title">Your Spaces</h3>
|
||||
|
||||
{#if spaces.length === 0}
|
||||
<div class="empty-state">
|
||||
|
|
@ -257,11 +330,8 @@
|
|||
{#each spaces as space}
|
||||
<div class="space-card">
|
||||
<div class="space-header">
|
||||
<h4>{space.type}</h4>
|
||||
<span
|
||||
class="status-badge"
|
||||
style="background-color: {getStatusColor(space.status)}"
|
||||
>
|
||||
<h4 class="space-type">{space.type}</h4>
|
||||
<span class="status-badge {getStatusClass(space.status)}">
|
||||
{space.status || 'Unknown'}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -279,7 +349,7 @@
|
|||
</div>
|
||||
|
||||
{#if actionError[space.id]}
|
||||
<div class="error small">{actionError[space.id]}</div>
|
||||
<div class="error-message small">{actionError[space.id]}</div>
|
||||
{/if}
|
||||
|
||||
<div class="space-actions">
|
||||
|
|
@ -328,274 +398,3 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.dashboard {
|
||||
min-height: 100vh;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 2rem;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 2px solid #333;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
color: #ec3750;
|
||||
}
|
||||
|
||||
.welcome {
|
||||
margin: 0.5rem 0 0 0;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.signout {
|
||||
padding: 0.5rem 1rem;
|
||||
background: transparent;
|
||||
border: 1px solid #ef4444;
|
||||
color: #ef4444;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.signout:hover {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
}
|
||||
|
||||
.content {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.actions-bar {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.primary {
|
||||
background: #646cff;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.primary:hover:not(:disabled) {
|
||||
background: #535bf2;
|
||||
}
|
||||
|
||||
.primary:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.secondary {
|
||||
background: transparent;
|
||||
border: 1px solid #646cff;
|
||||
color: #646cff;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.secondary:hover {
|
||||
background: rgba(100, 108, 255, 0.1);
|
||||
}
|
||||
|
||||
.create-form {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
padding: 2rem;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.create-form h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
input, select {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid #444;
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
color: inherit;
|
||||
font-size: 1rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
input:focus, select:focus {
|
||||
outline: none;
|
||||
border-color: #646cff;
|
||||
}
|
||||
|
||||
.error {
|
||||
padding: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
border: 1px solid rgba(239, 68, 68, 0.3);
|
||||
border-radius: 8px;
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.error.small {
|
||||
padding: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.spaces-list h3 {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 3rem;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.spaces-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.space-card {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
padding: 1.5rem;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #333;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.space-card:hover {
|
||||
border-color: #646cff;
|
||||
box-shadow: 0 4px 12px rgba(100, 108, 255, 0.1);
|
||||
}
|
||||
|
||||
.space-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.space-header h4 {
|
||||
margin: 0;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 12px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.space-info {
|
||||
margin-bottom: 1rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.space-info p {
|
||||
margin: 0.5rem 0;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.space-info strong {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.space-info a {
|
||||
color: #646cff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.space-info a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.space-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
flex: 1;
|
||||
padding: 0.5rem;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s;
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.action-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.action-btn.start {
|
||||
background: #22c55e;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.action-btn.start:hover:not(:disabled) {
|
||||
background: #16a34a;
|
||||
}
|
||||
|
||||
.action-btn.stop {
|
||||
background: #ef4444;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.action-btn.stop:hover:not(:disabled) {
|
||||
background: #dc2626;
|
||||
}
|
||||
|
||||
.action-btn.open {
|
||||
background: #646cff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.action-btn.open:hover {
|
||||
background: #535bf2;
|
||||
}
|
||||
|
||||
.action-btn.refresh {
|
||||
background: transparent;
|
||||
border: 1px solid #646cff;
|
||||
color: #646cff;
|
||||
flex: 0 0 auto;
|
||||
min-width: 40px;
|
||||
}
|
||||
|
||||
.action-btn.refresh:hover:not(:disabled) {
|
||||
background: rgba(100, 108, 255, 0.1);
|
||||
}
|
||||
</style>
|
||||
322
client/src/styles/auth.css
Normal file
322
client/src/styles/auth.css
Normal file
|
|
@ -0,0 +1,322 @@
|
|||
.auth-container {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
background: #f9fafc;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.auth-panel {
|
||||
width: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
transition: left 1s cubic-bezier(0.65, 0, 0.35, 1), right 1s cubic-bezier(0.65, 0, 0.35, 1);
|
||||
}
|
||||
|
||||
.auth-form-panel {
|
||||
background: #ffffff;
|
||||
left: 0;
|
||||
padding: 64px;
|
||||
}
|
||||
|
||||
.auth-image-panel {
|
||||
background: #f9fafc;
|
||||
right: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.auth-container.signup-mode .auth-form-panel {
|
||||
left: 50%;
|
||||
}
|
||||
|
||||
.auth-container.signup-mode .auth-image-panel {
|
||||
right: 50%;
|
||||
}
|
||||
|
||||
.auth-form-content {
|
||||
width: 100%;
|
||||
max-width: 440px;
|
||||
}
|
||||
|
||||
.auth-image-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.auth-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.auth-image-content::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 120px;
|
||||
background: linear-gradient(to bottom, rgba(249, 250, 252, 0.3) 0%, transparent 100%);
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.auth-image-content::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 60px;
|
||||
background: linear-gradient(to right, rgba(249, 250, 252, 0.2) 0%, transparent 100%);
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.image-caption {
|
||||
position: absolute;
|
||||
bottom: 32px;
|
||||
left: 32px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #ffffff;
|
||||
z-index: 2;
|
||||
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.image-caption a {
|
||||
color: #ffffff;
|
||||
text-decoration: underline;
|
||||
transition: opacity 0.125s ease-in-out;
|
||||
}
|
||||
|
||||
.image-caption a:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.flag-banner {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 10px;
|
||||
width: 256px;
|
||||
z-index: 999;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.auth-header {
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
|
||||
.auth-logo {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.auth-title {
|
||||
font-size: 36px;
|
||||
font-weight: bold;
|
||||
color: #1f2d3d;
|
||||
margin: 0 0 8px 0;
|
||||
line-height: 1.2;
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.auth-subtitle {
|
||||
font-size: 18px;
|
||||
color: #8492a6;
|
||||
margin: 0;
|
||||
line-height: 1.5;
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.auth-mode-switch {
|
||||
text-align: center;
|
||||
margin-top: 24px;
|
||||
font-size: 16px;
|
||||
color: #8492a6;
|
||||
}
|
||||
|
||||
.auth-mode-link {
|
||||
color: #ec3750;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: color 0.125s ease-in-out;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.auth-mode-link:hover {
|
||||
color: #ff8c37;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 24px;
|
||||
transition: opacity 0.6s ease-in-out, max-height 0.8s ease-in-out, margin 0.8s ease-in-out;
|
||||
}
|
||||
|
||||
.username-field {
|
||||
max-height: 0;
|
||||
opacity: 0;
|
||||
margin-bottom: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.username-field.show {
|
||||
max-height: 200px;
|
||||
opacity: 1;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #1f2d3d;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
width: 100%;
|
||||
padding: 14px 16px;
|
||||
border: 2px solid #e0e6ed;
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
color: #1f2d3d;
|
||||
font-size: 16px;
|
||||
font-family: Phantom Sans, sans-serif;
|
||||
box-sizing: border-box;
|
||||
transition: border-color 0.125s ease-in-out, box-shadow 0.125s ease-in-out;
|
||||
}
|
||||
|
||||
.form-input:focus {
|
||||
outline: none;
|
||||
border-color: #338eda;
|
||||
box-shadow: 0 0 0 3px rgba(51, 142, 218, 0.1);
|
||||
}
|
||||
|
||||
.form-input::placeholder {
|
||||
color: #8492a6;
|
||||
}
|
||||
|
||||
.primary-button {
|
||||
width: 100%;
|
||||
padding: 16px 32px;
|
||||
margin-top: 8px;
|
||||
border: none;
|
||||
border-radius: 99999px;
|
||||
background: #ec3750;
|
||||
color: #ffffff;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
font-family: Phantom Sans, sans-serif;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.125);
|
||||
transition: transform 0.125s ease-in-out, box-shadow 0.125s ease-in-out;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.primary-button:hover:not(:disabled) {
|
||||
transform: scale(1.0625);
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.primary-button:active:not(:disabled) {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.primary-button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.secondary-button {
|
||||
width: 100%;
|
||||
padding: 16px 32px;
|
||||
margin-top: 16px;
|
||||
background: transparent;
|
||||
border: 2px solid #ec3750;
|
||||
border-radius: 99999px;
|
||||
color: #ec3750;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
font-family: Phantom Sans, sans-serif;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: transform 0.125s ease-in-out, box-shadow 0.125s ease-in-out;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.secondary-button:hover {
|
||||
transform: scale(1.0625);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.125);
|
||||
}
|
||||
|
||||
.secondary-button:active {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.error-message {
|
||||
padding: 12px 16px;
|
||||
margin-bottom: 16px;
|
||||
background: rgba(236, 55, 80, 0.1);
|
||||
border: 2px solid #ec3750;
|
||||
border-radius: 8px;
|
||||
color: #ec3750;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.success-message {
|
||||
padding: 12px 16px;
|
||||
margin-bottom: 16px;
|
||||
background: rgba(51, 214, 166, 0.1);
|
||||
border: 2px solid #33d6a6;
|
||||
border-radius: 8px;
|
||||
color: #33d6a6;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.info-message {
|
||||
text-align: center;
|
||||
margin-bottom: 24px;
|
||||
color: #8492a6;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.auth-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.auth-panel {
|
||||
padding: 32px;
|
||||
}
|
||||
|
||||
.auth-form-panel {
|
||||
order: 1 !important;
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.auth-image-panel {
|
||||
order: 2 !important;
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.flag-banner {
|
||||
width: 128px;
|
||||
}
|
||||
|
||||
.auth-title {
|
||||
font-size: 28px;
|
||||
}
|
||||
}
|
||||
503
client/src/styles/dashboard.css
Normal file
503
client/src/styles/dashboard.css
Normal file
|
|
@ -0,0 +1,503 @@
|
|||
.dashboard {
|
||||
min-height: 100vh;
|
||||
padding: 32px;
|
||||
background: #f9fafc;
|
||||
}
|
||||
|
||||
.dashboard-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 64px;
|
||||
padding-bottom: 32px;
|
||||
border-bottom: 2px solid #e0e6ed;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.dashboard-logo {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.dashboard-title {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
color: #1f2d3d;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.welcome-text {
|
||||
margin: 8px 0 0 0;
|
||||
font-size: 16px;
|
||||
color: #8492a6;
|
||||
}
|
||||
|
||||
.signout-button {
|
||||
padding: 12px 24px;
|
||||
background: transparent;
|
||||
border: 2px solid #ec3750;
|
||||
border-radius: 99999px;
|
||||
color: #ec3750;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
font-family: Phantom Sans, sans-serif;
|
||||
cursor: pointer;
|
||||
transition: transform 0.125s ease-in-out, box-shadow 0.125s ease-in-out;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.signout-button:hover {
|
||||
transform: scale(1.0625);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.125);
|
||||
}
|
||||
|
||||
.signout-button:active {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.dashboard-content {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.actions-bar {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
padding: 14px 32px;
|
||||
background: #ec3750;
|
||||
color: #ffffff;
|
||||
border: none;
|
||||
border-radius: 99999px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
font-family: Phantom Sans, sans-serif;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.125);
|
||||
transition: transform 0.125s ease-in-out, box-shadow 0.125s ease-in-out;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
transform: scale(1.0625);
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.btn-primary:active:not(:disabled) {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.btn-primary:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
padding: 14px 32px;
|
||||
background: transparent;
|
||||
border: 2px solid #ec3750;
|
||||
border-radius: 99999px;
|
||||
color: #ec3750;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
font-family: Phantom Sans, sans-serif;
|
||||
cursor: pointer;
|
||||
transition: transform 0.125s ease-in-out, box-shadow 0.125s ease-in-out;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
transform: scale(1.0625);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.125);
|
||||
}
|
||||
|
||||
.btn-secondary:active {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.create-form {
|
||||
background: #ffffff;
|
||||
padding: 32px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 32px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.125);
|
||||
}
|
||||
|
||||
.create-form-title {
|
||||
margin: 0 0 24px 0;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #1f2d3d;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #1f2d3d;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
width: 100%;
|
||||
padding: 14px 16px;
|
||||
border: 2px solid #e0e6ed;
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
color: #1f2d3d;
|
||||
font-size: 16px;
|
||||
font-family: Phantom Sans, sans-serif;
|
||||
box-sizing: border-box;
|
||||
transition: border-color 0.125s ease-in-out, box-shadow 0.125s ease-in-out;
|
||||
}
|
||||
|
||||
.form-input:focus {
|
||||
outline: none;
|
||||
border-color: #338eda;
|
||||
box-shadow: 0 0 0 3px rgba(51, 142, 218, 0.1);
|
||||
}
|
||||
|
||||
.password-input-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.password-input {
|
||||
padding-right: 48px;
|
||||
}
|
||||
|
||||
.password-toggle {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: opacity 0.125s ease-in-out;
|
||||
}
|
||||
|
||||
.password-toggle:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.password-toggle img {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
filter: opacity(0.6);
|
||||
transition: filter 0.125s ease-in-out;
|
||||
}
|
||||
|
||||
.password-toggle:hover img {
|
||||
filter: opacity(1);
|
||||
}
|
||||
|
||||
.custom-select {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.select-trigger {
|
||||
width: 100%;
|
||||
padding: 14px 16px;
|
||||
padding-right: 48px;
|
||||
border: 2px solid #e0e6ed;
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
color: #1f2d3d;
|
||||
font-size: 16px;
|
||||
font-family: Phantom Sans, sans-serif;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
transition: border-color 0.125s ease-in-out, box-shadow 0.125s ease-in-out;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.select-trigger:hover {
|
||||
border-color: #338eda;
|
||||
}
|
||||
|
||||
.select-trigger.open {
|
||||
border-color: #338eda;
|
||||
box-shadow: 0 0 0 3px rgba(51, 142, 218, 0.1);
|
||||
}
|
||||
|
||||
.select-arrow {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
border-top: 6px solid #3c4858;
|
||||
transition: transform 0.125s ease-in-out;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.select-arrow.open {
|
||||
transform: translateY(-50%) rotate(180deg);
|
||||
}
|
||||
|
||||
.select-dropdown {
|
||||
position: absolute;
|
||||
top: calc(100% + 8px);
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #ffffff;
|
||||
border: 2px solid #338eda;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
|
||||
z-index: 100;
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
transition: max-height 0.25s ease-in-out, opacity 0.25s ease-in-out;
|
||||
}
|
||||
|
||||
.select-dropdown.open {
|
||||
max-height: 300px;
|
||||
opacity: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.select-option {
|
||||
padding: 14px 16px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.125s ease-in-out;
|
||||
border-bottom: 1px solid #e0e6ed;
|
||||
}
|
||||
|
||||
.select-option:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.select-option:hover {
|
||||
background: #f9fafc;
|
||||
}
|
||||
|
||||
.select-option.selected {
|
||||
background: rgba(51, 142, 218, 0.1);
|
||||
color: #338eda;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.option-label {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #1f2d3d;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.option-description {
|
||||
font-size: 14px;
|
||||
color: #8492a6;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
padding: 12px 16px;
|
||||
margin-bottom: 16px;
|
||||
background: rgba(236, 55, 80, 0.1);
|
||||
border: 2px solid #ec3750;
|
||||
border-radius: 8px;
|
||||
color: #ec3750;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.error-message.small {
|
||||
padding: 8px 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #1f2d3d;
|
||||
margin: 0 0 24px 0;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 64px 32px;
|
||||
color: #8492a6;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.spaces-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.space-card {
|
||||
background: #ffffff;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
border: 2px solid #e0e6ed;
|
||||
transition: border-color 0.125s ease-in-out, box-shadow 0.125s ease-in-out;
|
||||
}
|
||||
|
||||
.space-card:hover {
|
||||
border-color: #338eda;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.125);
|
||||
}
|
||||
|
||||
.space-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.space-type {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: #1f2d3d;
|
||||
margin: 0;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 4px 12px;
|
||||
border-radius: 99999px;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.status-running {
|
||||
background: #33d6a6;
|
||||
}
|
||||
|
||||
.status-stopped {
|
||||
background: #ec3750;
|
||||
}
|
||||
|
||||
.status-created {
|
||||
background: #f1c40f;
|
||||
}
|
||||
|
||||
.status-unknown {
|
||||
background: #8492a6;
|
||||
}
|
||||
|
||||
.space-info {
|
||||
margin-bottom: 16px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.space-info p {
|
||||
margin: 8px 0;
|
||||
color: #8492a6;
|
||||
}
|
||||
|
||||
.space-info strong {
|
||||
color: #3c4858;
|
||||
}
|
||||
|
||||
.space-info a {
|
||||
color: #338eda;
|
||||
text-decoration: none;
|
||||
transition: color 0.125s ease-in-out;
|
||||
}
|
||||
|
||||
.space-info a:hover {
|
||||
color: #ec3750;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.space-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
flex: 1;
|
||||
padding: 10px 16px;
|
||||
border-radius: 99999px;
|
||||
border: none;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
font-family: Phantom Sans, sans-serif;
|
||||
cursor: pointer;
|
||||
transition: transform 0.125s ease-in-out;
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.action-btn:hover:not(:disabled) {
|
||||
transform: scale(1.0625);
|
||||
}
|
||||
|
||||
.action-btn:active:not(:disabled) {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.action-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.action-btn.start {
|
||||
background: #33d6a6;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.action-btn.stop {
|
||||
background: #ec3750;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.action-btn.open {
|
||||
background: #338eda;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.action-btn.refresh {
|
||||
background: transparent;
|
||||
border: 2px solid #338eda;
|
||||
color: #338eda;
|
||||
flex: 0 0 auto;
|
||||
min-width: 44px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.dashboard {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.dashboard-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.actions-bar {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.spaces-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue