4 Commits

Author SHA1 Message Date
Mondo Diaz
dc44502840 Remove GitHub link from footer 2025-12-11 14:56:11 -06:00
Mondo Diaz
3488dea11d Switch accent color to green and improve text contrast
- Change accent colors from purple to emerald green (#10b981)
- Improve navbar text contrast (use text-primary instead of text-secondary)
- Improve footer link contrast
- Update all glow/shadow effects to match green theme
- Bump muted/tertiary text colors for better readability
2025-12-11 14:52:12 -06:00
Mondo Diaz
d9d8e14e52 Update logo to orchard/tree design
- Three trees representing an orchard
- Center tree larger for visual hierarchy
- Side trees with subtle opacity
- Ground line for grounding effect
2025-12-11 14:50:05 -06:00
Mondo Diaz
fb576743bb Modern dark mode UI overhaul
- Complete dark color palette with CSS custom properties
- Glassmorphism header with blur backdrop
- Gradient accents and glowing effects
- Modern card styling with hover animations
- Updated SVG icons and improved navigation
- Responsive design refinements
- Code blocks with syntax-aware styling
2025-12-11 14:48:34 -06:00
8 changed files with 598 additions and 416 deletions

View File

@@ -3,9 +3,6 @@ from functools import lru_cache
class Settings(BaseSettings): class Settings(BaseSettings):
# Environment
env: str = "development" # "development" or "production"
# Server # Server
server_host: str = "0.0.0.0" server_host: str = "0.0.0.0"
server_port: int = 8080 server_port: int = 8080
@@ -31,14 +28,6 @@ class Settings(BaseSettings):
sslmode = f"?sslmode={self.database_sslmode}" if self.database_sslmode else "" sslmode = f"?sslmode={self.database_sslmode}" if self.database_sslmode else ""
return f"postgresql://{self.database_user}:{self.database_password}@{self.database_host}:{self.database_port}/{self.database_dbname}{sslmode}" return f"postgresql://{self.database_user}:{self.database_password}@{self.database_host}:{self.database_port}/{self.database_dbname}{sslmode}"
@property
def is_development(self) -> bool:
return self.env.lower() == "development"
@property
def is_production(self) -> bool:
return self.env.lower() == "production"
class Config: class Config:
env_prefix = "ORCHARD_" env_prefix = "ORCHARD_"
case_sensitive = False case_sensitive = False

View File

@@ -2,35 +2,19 @@ from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse from fastapi.responses import FileResponse
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
import logging
import os import os
from .config import get_settings from .config import get_settings
from .database import init_db, SessionLocal from .database import init_db
from .routes import router from .routes import router
from .seed import seed_database
settings = get_settings() settings = get_settings()
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@asynccontextmanager @asynccontextmanager
async def lifespan(app: FastAPI): async def lifespan(app: FastAPI):
# Startup: initialize database # Startup: initialize database
init_db() init_db()
# Seed test data in development mode
if settings.is_development:
logger.info(f"Running in {settings.env} mode - checking for seed data")
db = SessionLocal()
try:
seed_database(db)
finally:
db.close()
else:
logger.info(f"Running in {settings.env} mode - skipping seed data")
yield yield
# Shutdown: cleanup if needed # Shutdown: cleanup if needed

View File

@@ -1,222 +0,0 @@
"""
Test data seeding for development environment.
"""
import hashlib
import logging
from sqlalchemy.orm import Session
from .models import Project, Package, Artifact, Tag, Upload
from .storage import get_storage
logger = logging.getLogger(__name__)
# Test data definitions
TEST_PROJECTS = [
{
"name": "frontend-libs",
"description": "Shared frontend libraries and components",
"is_public": True,
"packages": [
{
"name": "ui-components",
"description": "Reusable UI component library",
},
{
"name": "design-tokens",
"description": "Design system tokens and variables",
},
],
},
{
"name": "backend-services",
"description": "Backend microservices and shared utilities",
"is_public": True,
"packages": [
{
"name": "auth-lib",
"description": "Authentication and authorization library",
},
{
"name": "common-utils",
"description": "Common utility functions",
},
{
"name": "api-client",
"description": "Generated API client library",
},
],
},
{
"name": "mobile-apps",
"description": "Mobile application builds and assets",
"is_public": True,
"packages": [
{
"name": "ios-release",
"description": "iOS release builds",
},
{
"name": "android-release",
"description": "Android release builds",
},
],
},
{
"name": "internal-tools",
"description": "Internal development tools (private)",
"is_public": False,
"packages": [
{
"name": "dev-scripts",
"description": "Development automation scripts",
},
],
},
]
# Sample artifacts to create (content, tags)
TEST_ARTIFACTS = [
{
"project": "frontend-libs",
"package": "ui-components",
"content": b"/* UI Components v1.0.0 */\nexport const Button = () => {};\nexport const Input = () => {};\n",
"filename": "ui-components-1.0.0.js",
"content_type": "application/javascript",
"tags": ["v1.0.0", "latest"],
},
{
"project": "frontend-libs",
"package": "ui-components",
"content": b"/* UI Components v1.1.0 */\nexport const Button = () => {};\nexport const Input = () => {};\nexport const Modal = () => {};\n",
"filename": "ui-components-1.1.0.js",
"content_type": "application/javascript",
"tags": ["v1.1.0"],
},
{
"project": "frontend-libs",
"package": "design-tokens",
"content": b'{"colors": {"primary": "#007bff", "secondary": "#6c757d"}, "spacing": {"sm": "8px", "md": "16px"}}',
"filename": "tokens.json",
"content_type": "application/json",
"tags": ["v1.0.0", "latest"],
},
{
"project": "backend-services",
"package": "common-utils",
"content": b"# Common Utils\n\ndef format_date(dt):\n return dt.isoformat()\n\ndef slugify(text):\n return text.lower().replace(' ', '-')\n",
"filename": "utils-2.0.0.py",
"content_type": "text/x-python",
"tags": ["v2.0.0", "stable", "latest"],
},
{
"project": "backend-services",
"package": "auth-lib",
"content": b"package auth\n\nfunc ValidateToken(token string) bool {\n return len(token) > 0\n}\n",
"filename": "auth-lib-1.0.0.go",
"content_type": "text/x-go",
"tags": ["v1.0.0", "latest"],
},
]
def is_database_empty(db: Session) -> bool:
"""Check if the database has any projects."""
return db.query(Project).first() is None
def seed_database(db: Session) -> None:
"""Seed the database with test data."""
if not is_database_empty(db):
logger.info("Database already has data, skipping seed")
return
logger.info("Seeding database with test data...")
storage = get_storage()
# Create projects and packages
project_map = {}
package_map = {}
for project_data in TEST_PROJECTS:
project = Project(
name=project_data["name"],
description=project_data["description"],
is_public=project_data["is_public"],
created_by="seed-user",
)
db.add(project)
db.flush() # Get the ID
project_map[project_data["name"]] = project
for package_data in project_data["packages"]:
package = Package(
project_id=project.id,
name=package_data["name"],
description=package_data["description"],
)
db.add(package)
db.flush()
package_map[(project_data["name"], package_data["name"])] = package
logger.info(f"Created {len(project_map)} projects and {len(package_map)} packages")
# Create artifacts and tags
artifact_count = 0
tag_count = 0
for artifact_data in TEST_ARTIFACTS:
project = project_map[artifact_data["project"]]
package = package_map[(artifact_data["project"], artifact_data["package"])]
content = artifact_data["content"]
sha256_hash = hashlib.sha256(content).hexdigest()
size = len(content)
s3_key = f"fruits/{sha256_hash[:2]}/{sha256_hash[2:4]}/{sha256_hash}"
# Store in S3
try:
storage.client.put_object(
Bucket=storage.bucket,
Key=s3_key,
Body=content,
)
except Exception as e:
logger.warning(f"Failed to store artifact in S3: {e}")
continue
# Create artifact record
artifact = Artifact(
id=sha256_hash,
size=size,
content_type=artifact_data["content_type"],
original_name=artifact_data["filename"],
created_by="seed-user",
s3_key=s3_key,
ref_count=len(artifact_data["tags"]),
)
db.add(artifact)
# Create upload record
upload = Upload(
artifact_id=sha256_hash,
package_id=package.id,
original_name=artifact_data["filename"],
uploaded_by="seed-user",
)
db.add(upload)
artifact_count += 1
# Create tags
for tag_name in artifact_data["tags"]:
tag = Tag(
package_id=package.id,
name=tag_name,
artifact_id=sha256_hash,
created_by="seed-user",
)
db.add(tag)
tag_count += 1
db.commit()
logger.info(f"Created {artifact_count} artifacts and {tag_count} tags")
logger.info("Database seeding complete")

View File

@@ -2,64 +2,174 @@
min-height: 100vh; min-height: 100vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background: var(--bg-primary);
} }
/* Header */
.header { .header {
background-color: var(--primary); background: var(--bg-secondary);
color: white; border-bottom: 1px solid var(--border-primary);
padding: 1rem 0; padding: 0;
box-shadow: 0 2px 4px rgba(0,0,0,0.1); position: sticky;
top: 0;
z-index: 100;
backdrop-filter: blur(12px);
background: rgba(17, 17, 19, 0.85);
} }
.header-content { .header-content {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
height: 64px;
} }
/* Logo */
.logo { .logo {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.5rem; gap: 12px;
color: white; color: var(--text-primary);
font-size: 1.5rem; font-size: 1.25rem;
font-weight: bold; font-weight: 600;
text-decoration: none; text-decoration: none;
transition: opacity var(--transition-fast);
} }
.logo:hover { .logo:hover {
text-decoration: none; opacity: 0.9;
color: var(--text-primary);
} }
.logo-icon { .logo-icon {
font-size: 2rem; display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
background: var(--accent-gradient);
border-radius: var(--radius-md);
color: white;
box-shadow: var(--shadow-glow);
} }
.logo-text {
letter-spacing: -0.02em;
}
/* Navigation */
.nav { .nav {
display: flex; display: flex;
gap: 1.5rem; gap: 8px;
} }
.nav a { .nav a {
color: white; display: flex;
opacity: 0.9; align-items: center;
gap: 8px;
padding: 8px 16px;
color: var(--text-primary);
font-size: 0.875rem;
font-weight: 500;
border-radius: var(--radius-md);
transition: all var(--transition-fast);
} }
.nav a:hover { .nav a:hover {
opacity: 1; color: var(--text-primary);
text-decoration: none; background: var(--bg-hover);
} }
.nav a.active {
color: var(--accent-primary);
background: rgba(16, 185, 129, 0.1);
}
.nav a svg {
opacity: 0.8;
}
.nav a:hover svg,
.nav a.active svg {
opacity: 1;
}
.nav-link-muted {
opacity: 0.7;
}
/* Main content */
.main { .main {
flex: 1; flex: 1;
padding: 2rem 0; padding: 32px 0 64px;
} }
/* Footer */
.footer { .footer {
background-color: var(--primary-dark); background: var(--bg-secondary);
color: white; border-top: 1px solid var(--border-primary);
padding: 1rem 0; padding: 24px 0;
text-align: center; }
opacity: 0.9;
.footer-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.footer-brand {
display: flex;
align-items: center;
gap: 12px;
}
.footer-logo {
font-weight: 600;
color: var(--text-primary);
}
.footer-tagline {
color: var(--text-secondary);
font-size: 0.875rem; font-size: 0.875rem;
} }
.footer-links {
display: flex;
gap: 24px;
}
.footer-links a {
color: var(--text-secondary);
font-size: 0.875rem;
transition: color var(--transition-fast);
}
.footer-links a:hover {
color: var(--text-primary);
}
/* Responsive */
@media (max-width: 640px) {
.header-content {
height: 56px;
}
.logo-text {
display: none;
}
.nav a span {
display: none;
}
.footer-content {
flex-direction: column;
gap: 16px;
text-align: center;
}
.footer-brand {
flex-direction: column;
gap: 4px;
}
}

View File

@@ -1,5 +1,5 @@
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { Link } from 'react-router-dom'; import { Link, useLocation } from 'react-router-dom';
import './Layout.css'; import './Layout.css';
interface LayoutProps { interface LayoutProps {
@@ -7,16 +7,48 @@ interface LayoutProps {
} }
function Layout({ children }: LayoutProps) { function Layout({ children }: LayoutProps) {
const location = useLocation();
return ( return (
<div className="layout"> <div className="layout">
<header className="header"> <header className="header">
<div className="container header-content"> <div className="container header-content">
<Link to="/" className="logo"> <Link to="/" className="logo">
<span className="logo-icon">🌳</span> <div className="logo-icon">
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
{/* Three trees representing an orchard */}
{/* Left tree */}
<ellipse cx="6" cy="9" rx="4" ry="5" fill="currentColor" opacity="0.7"/>
<rect x="5" y="13" width="2" height="5" fill="currentColor"/>
{/* Center tree (larger) */}
<ellipse cx="12" cy="7" rx="5" ry="6" fill="currentColor"/>
<rect x="11" y="12" width="2" height="6" fill="currentColor"/>
{/* Right tree */}
<ellipse cx="18" cy="9" rx="4" ry="5" fill="currentColor" opacity="0.7"/>
<rect x="17" y="13" width="2" height="5" fill="currentColor"/>
{/* Ground line */}
<line x1="2" y1="18" x2="22" y2="18" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" opacity="0.5"/>
</svg>
</div>
<span className="logo-text">Orchard</span> <span className="logo-text">Orchard</span>
</Link> </Link>
<nav className="nav"> <nav className="nav">
<Link to="/">Groves</Link> <Link to="/" className={location.pathname === '/' ? 'active' : ''}>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
<polyline points="9,22 9,12 15,12 15,22"/>
</svg>
Projects
</Link>
<a href="/docs" className="nav-link-muted">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
<polyline points="14,2 14,8 20,8"/>
<line x1="16" y1="13" x2="8" y2="13"/>
<line x1="16" y1="17" x2="8" y2="17"/>
</svg>
Docs
</a>
</nav> </nav>
</div> </div>
</header> </header>
@@ -26,8 +58,15 @@ function Layout({ children }: LayoutProps) {
</div> </div>
</main> </main>
<footer className="footer"> <footer className="footer">
<div className="container"> <div className="container footer-content">
<p>Orchard - Content-Addressable Storage System</p> <div className="footer-brand">
<span className="footer-logo">Orchard</span>
<span className="footer-tagline">Content-Addressable Storage</span>
</div>
<div className="footer-links">
<a href="/docs">Documentation</a>
<a href="/api/v1">API</a>
</div>
</div> </div>
</footer> </footer>
</div> </div>

View File

@@ -5,34 +5,97 @@
} }
:root { :root {
--primary: #2d5a27; /* Dark mode color palette */
--primary-light: #4a8c3f; --bg-primary: #0a0a0b;
--primary-dark: #1e3d1a; --bg-secondary: #111113;
--secondary: #8b4513; --bg-tertiary: #1a1a1d;
--background: #f5f5f0; --bg-elevated: #222225;
--surface: #ffffff; --bg-hover: #2a2a2e;
--text: #333333;
--text-light: #666666; /* Accent colors - Green/Emerald theme */
--border: #e0e0e0; --accent-primary: #10b981;
--success: #28a745; --accent-primary-hover: #34d399;
--error: #dc3545; --accent-secondary: #059669;
--warning: #ffc107; --accent-gradient: linear-gradient(135deg, #10b981 0%, #059669 100%);
/* Text colors - improved contrast */
--text-primary: #f9fafb;
--text-secondary: #d1d5db;
--text-tertiary: #9ca3af;
--text-muted: #6b7280;
/* Border colors */
--border-primary: #27272a;
--border-secondary: #3f3f46;
--border-accent: #10b981;
/* Status colors */
--success: #22c55e;
--success-bg: rgba(34, 197, 94, 0.1);
--error: #ef4444;
--error-bg: rgba(239, 68, 68, 0.1);
--warning: #f59e0b;
--warning-bg: rgba(245, 158, 11, 0.1);
/* Shadows */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -2px rgba(0, 0, 0, 0.3);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.5), 0 4px 6px -4px rgba(0, 0, 0, 0.4);
--shadow-glow: 0 0 20px rgba(16, 185, 129, 0.3);
/* Transitions */
--transition-fast: 150ms ease;
--transition-normal: 250ms ease;
--transition-slow: 350ms ease;
/* Border radius */
--radius-sm: 6px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-xl: 16px;
}
html {
color-scheme: dark;
} }
body { body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
background-color: var(--background); background-color: var(--bg-primary);
color: var(--text); color: var(--text-primary);
line-height: 1.6; line-height: 1.6;
font-size: 14px;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* Scrollbar styling */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: var(--bg-secondary);
}
::-webkit-scrollbar-thumb {
background: var(--border-secondary);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--text-tertiary);
} }
a { a {
color: var(--primary); color: var(--accent-primary);
text-decoration: none; text-decoration: none;
transition: color var(--transition-fast);
} }
a:hover { a:hover {
text-decoration: underline; color: var(--accent-primary-hover);
} }
button { button {
@@ -41,7 +104,13 @@ button {
} }
.container { .container {
max-width: 1200px; max-width: 1280px;
margin: 0 auto; margin: 0 auto;
padding: 0 20px; padding: 0 24px;
}
/* Selection */
::selection {
background: var(--accent-primary);
color: white;
} }

View File

@@ -1,3 +1,4 @@
/* Page Layout */
.home { .home {
max-width: 1000px; max-width: 1000px;
margin: 0 auto; margin: 0 auto;
@@ -7,71 +8,92 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 2rem; margin-bottom: 32px;
} }
.page-header h1 { .page-header h1 {
font-size: 2rem; font-size: 2rem;
color: var(--primary-dark); font-weight: 700;
color: var(--text-primary);
letter-spacing: -0.02em;
} }
/* Buttons */
.btn { .btn {
padding: 0.625rem 1.25rem; display: inline-flex;
align-items: center;
gap: 8px;
padding: 10px 20px;
border: none; border: none;
border-radius: 6px; border-radius: var(--radius-md);
font-size: 0.875rem; font-size: 0.875rem;
font-weight: 500; font-weight: 500;
transition: all 0.2s; transition: all var(--transition-fast);
} }
.btn-primary { .btn-primary {
background-color: var(--primary); background: var(--accent-gradient);
color: white; color: white;
box-shadow: var(--shadow-sm), 0 0 20px rgba(16, 185, 129, 0.2);
} }
.btn-primary:hover { .btn-primary:hover {
background-color: var(--primary-dark); transform: translateY(-1px);
box-shadow: var(--shadow-md), 0 0 30px rgba(16, 185, 129, 0.3);
}
.btn-primary:active {
transform: translateY(0);
} }
.btn-primary:disabled { .btn-primary:disabled {
opacity: 0.6; opacity: 0.5;
cursor: not-allowed; cursor: not-allowed;
transform: none;
} }
.btn-secondary { .btn-secondary {
background-color: var(--border); background: var(--bg-tertiary);
color: var(--text); color: var(--text-secondary);
border: 1px solid var(--border-primary);
} }
.btn-secondary:hover { .btn-secondary:hover {
background-color: #d0d0d0; background: var(--bg-hover);
color: var(--text-primary);
border-color: var(--border-secondary);
} }
/* Cards */
.card { .card {
background-color: var(--surface); background: var(--bg-secondary);
border: 1px solid var(--border); border: 1px solid var(--border-primary);
border-radius: 8px; border-radius: var(--radius-lg);
padding: 1.5rem; padding: 24px;
transition: all var(--transition-normal);
} }
/* Forms */
.form { .form {
margin-bottom: 2rem; margin-bottom: 32px;
} }
.form h3 { .form h3 {
margin-bottom: 1rem; margin-bottom: 20px;
color: var(--primary-dark); color: var(--text-primary);
font-size: 1.125rem;
font-weight: 600;
} }
.form-group { .form-group {
margin-bottom: 1rem; margin-bottom: 16px;
} }
.form-group label { .form-group label {
display: block; display: block;
margin-bottom: 0.375rem; margin-bottom: 8px;
font-weight: 500; font-weight: 500;
color: var(--text-light); color: var(--text-secondary);
font-size: 0.875rem; font-size: 0.875rem;
} }
@@ -80,82 +102,138 @@
.form-group select, .form-group select,
.form-group textarea { .form-group textarea {
width: 100%; width: 100%;
padding: 0.625rem; padding: 12px 16px;
border: 1px solid var(--border); background: var(--bg-tertiary);
border-radius: 6px; border: 1px solid var(--border-primary);
border-radius: var(--radius-md);
font-size: 0.875rem; font-size: 0.875rem;
color: var(--text-primary);
transition: all var(--transition-fast);
}
.form-group input::placeholder {
color: var(--text-muted);
} }
.form-group input:focus, .form-group input:focus,
.form-group select:focus, .form-group select:focus,
.form-group textarea:focus { .form-group textarea:focus {
outline: none; outline: none;
border-color: var(--primary); border-color: var(--accent-primary);
box-shadow: 0 0 0 3px rgba(45, 90, 39, 0.1); box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.15);
background: var(--bg-elevated);
} }
.form-group.checkbox label { .form-group.checkbox label {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.5rem; gap: 10px;
cursor: pointer;
color: var(--text-primary);
}
.form-group.checkbox input[type="checkbox"] {
width: 18px;
height: 18px;
accent-color: var(--accent-primary);
cursor: pointer; cursor: pointer;
} }
.form-group.checkbox input { /* Messages */
width: auto;
}
.error-message { .error-message {
background-color: #fef2f2; background: var(--error-bg);
border: 1px solid #fecaca; border: 1px solid rgba(239, 68, 68, 0.2);
color: var(--error); color: var(--error);
padding: 0.75rem 1rem; padding: 12px 16px;
border-radius: 6px; border-radius: var(--radius-md);
margin-bottom: 1rem; margin-bottom: 16px;
font-size: 0.875rem;
} }
.success-message {
background: var(--success-bg);
border: 1px solid rgba(34, 197, 94, 0.2);
color: var(--success);
padding: 12px 16px;
border-radius: var(--radius-md);
margin-bottom: 16px;
font-size: 0.875rem;
}
/* Loading */
.loading { .loading {
text-align: center; display: flex;
padding: 3rem; align-items: center;
color: var(--text-light); justify-content: center;
padding: 64px;
color: var(--text-tertiary);
font-size: 0.875rem;
} }
/* Empty State */
.empty-state { .empty-state {
text-align: center; text-align: center;
padding: 3rem; padding: 64px 32px;
color: var(--text-light); color: var(--text-tertiary);
background-color: var(--surface); background: var(--bg-secondary);
border: 1px dashed var(--border); border: 1px dashed var(--border-secondary);
border-radius: 8px; border-radius: var(--radius-lg);
} }
.empty-state p {
font-size: 0.9375rem;
}
/* Project Grid */
.project-grid { .project-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1rem; gap: 16px;
} }
.project-card { .project-card {
display: block; display: block;
color: inherit; color: inherit;
transition: transform 0.2s, box-shadow 0.2s; position: relative;
overflow: hidden;
}
.project-card::before {
content: '';
position: absolute;
inset: 0;
background: var(--accent-gradient);
opacity: 0;
transition: opacity var(--transition-normal);
border-radius: var(--radius-lg);
} }
.project-card:hover { .project-card:hover {
border-color: var(--border-secondary);
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); box-shadow: var(--shadow-lg);
text-decoration: none; color: inherit;
}
.project-card:hover::before {
opacity: 0.03;
} }
.project-card h3 { .project-card h3 {
color: var(--primary); color: var(--text-primary);
margin-bottom: 0.5rem; font-size: 1.125rem;
font-weight: 600;
margin-bottom: 8px;
display: flex;
align-items: center;
gap: 8px;
} }
.project-card p { .project-card p {
color: var(--text-light); color: var(--text-secondary);
font-size: 0.875rem; font-size: 0.875rem;
margin-bottom: 1rem; margin-bottom: 16px;
line-height: 1.5;
} }
.project-meta { .project-meta {
@@ -163,24 +241,63 @@
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
font-size: 0.75rem; font-size: 0.75rem;
padding-top: 16px;
border-top: 1px solid var(--border-primary);
margin-top: auto;
} }
/* Badges */
.badge { .badge {
padding: 0.25rem 0.5rem; padding: 4px 10px;
border-radius: 4px; border-radius: 100px;
font-weight: 500; font-weight: 500;
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.02em;
} }
.badge-public { .badge-public {
background-color: #dcfce7; background: var(--success-bg);
color: #166534; color: var(--success);
border: 1px solid rgba(34, 197, 94, 0.2);
} }
.badge-private { .badge-private {
background-color: #fef3c7; background: var(--warning-bg);
color: #92400e; color: var(--warning);
border: 1px solid rgba(245, 158, 11, 0.2);
} }
.date { .date {
color: var(--text-light); color: var(--text-muted);
}
/* Breadcrumb */
.breadcrumb {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 24px;
font-size: 0.875rem;
color: var(--text-tertiary);
}
.breadcrumb a {
color: var(--text-secondary);
transition: color var(--transition-fast);
}
.breadcrumb a:hover {
color: var(--accent-primary);
}
.breadcrumb span {
color: var(--text-primary);
font-weight: 500;
}
.description {
color: var(--text-secondary);
margin-top: 4px;
font-size: 0.9375rem;
} }

View File

@@ -1,44 +1,32 @@
.breadcrumb { /* Upload Section */
margin-bottom: 1rem;
font-size: 0.875rem;
color: var(--text-light);
}
.breadcrumb a {
color: var(--primary);
}
.breadcrumb span {
color: var(--text);
font-weight: 500;
}
.description {
color: var(--text-light);
margin-top: 0.25rem;
}
.success-message {
background-color: #f0fdf4;
border: 1px solid #bbf7d0;
color: var(--success);
padding: 0.75rem 1rem;
border-radius: 6px;
margin-bottom: 1rem;
}
.upload-section { .upload-section {
margin-bottom: 2rem; margin-bottom: 32px;
background: linear-gradient(135deg, rgba(16, 185, 129, 0.05) 0%, rgba(5, 150, 105, 0.05) 100%);
border: 1px solid rgba(16, 185, 129, 0.2);
} }
.upload-section h3 { .upload-section h3 {
margin-bottom: 1rem; margin-bottom: 20px;
color: var(--primary-dark); color: var(--text-primary);
font-size: 1rem;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
}
.upload-section h3::before {
content: '';
display: block;
width: 4px;
height: 20px;
background: var(--accent-gradient);
border-radius: 2px;
} }
.upload-form { .upload-form {
display: flex; display: flex;
gap: 1rem; gap: 16px;
align-items: flex-end; align-items: flex-end;
flex-wrap: wrap; flex-wrap: wrap;
} }
@@ -49,17 +37,42 @@
min-width: 200px; min-width: 200px;
} }
h2 { .upload-form .form-group input[type="file"] {
margin-bottom: 1rem; padding: 10px 16px;
color: var(--primary-dark); background: var(--bg-tertiary);
cursor: pointer;
} }
.upload-form .form-group input[type="file"]::file-selector-button {
background: var(--accent-gradient);
color: white;
border: none;
padding: 6px 12px;
border-radius: var(--radius-sm);
margin-right: 12px;
cursor: pointer;
font-weight: 500;
font-size: 0.8125rem;
}
/* Section Headers */
h2 {
margin-bottom: 16px;
color: var(--text-primary);
font-size: 1.25rem;
font-weight: 600;
display: flex;
align-items: center;
gap: 10px;
}
/* Tags Table */
.tags-table { .tags-table {
background-color: var(--surface); background: var(--bg-secondary);
border: 1px solid var(--border); border: 1px solid var(--border-primary);
border-radius: 8px; border-radius: var(--radius-lg);
overflow: hidden; overflow: hidden;
margin-bottom: 2rem; margin-bottom: 32px;
} }
.tags-table table { .tags-table table {
@@ -69,63 +82,146 @@ h2 {
.tags-table th, .tags-table th,
.tags-table td { .tags-table td {
padding: 0.875rem 1rem; padding: 14px 20px;
text-align: left; text-align: left;
border-bottom: 1px solid var(--border); border-bottom: 1px solid var(--border-primary);
} }
.tags-table th { .tags-table th {
background-color: #f9f9f9; background: var(--bg-tertiary);
font-weight: 600; font-weight: 600;
font-size: 0.75rem; font-size: 0.75rem;
text-transform: uppercase; text-transform: uppercase;
color: var(--text-light); letter-spacing: 0.05em;
color: var(--text-tertiary);
} }
.tags-table tr:last-child td { .tags-table tr:last-child td {
border-bottom: none; border-bottom: none;
} }
.tags-table tr:hover { .tags-table tbody tr {
background-color: #f9f9f9; transition: background var(--transition-fast);
}
.tags-table tbody tr:hover {
background: var(--bg-tertiary);
}
.tags-table td strong {
color: var(--accent-primary);
font-weight: 600;
} }
.artifact-id { .artifact-id {
font-family: monospace; font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
font-size: 0.875rem; font-size: 0.8125rem;
color: var(--text-light); color: var(--text-tertiary);
background: var(--bg-tertiary);
padding: 4px 8px;
border-radius: var(--radius-sm);
} }
.btn-small { .btn-small {
padding: 0.375rem 0.75rem; padding: 6px 12px;
font-size: 0.75rem; font-size: 0.75rem;
} }
/* Usage Section */
.usage-section { .usage-section {
margin-top: 2rem; margin-top: 32px;
background: var(--bg-secondary);
} }
.usage-section h3 { .usage-section h3 {
margin-bottom: 0.5rem; margin-bottom: 12px;
color: var(--primary-dark); color: var(--text-primary);
font-size: 1rem;
font-weight: 600;
} }
.usage-section p { .usage-section p {
color: var(--text-light); color: var(--text-secondary);
margin-bottom: 0.5rem; margin-bottom: 12px;
font-size: 0.875rem; font-size: 0.875rem;
} }
.usage-section pre { .usage-section pre {
background-color: #1e1e1e; background: #0d0d0f;
color: #d4d4d4; border: 1px solid var(--border-primary);
padding: 1rem; padding: 16px 20px;
border-radius: 6px; border-radius: var(--radius-md);
overflow-x: auto; overflow-x: auto;
margin-bottom: 1rem; margin-bottom: 16px;
} }
.usage-section code { .usage-section code {
font-family: 'Fira Code', 'Consolas', monospace; font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
font-size: 0.875rem; font-size: 0.8125rem;
color: #e2e8f0;
}
/* Syntax highlighting for code blocks */
.usage-section pre {
position: relative;
}
.usage-section pre::before {
content: 'bash';
position: absolute;
top: 8px;
right: 12px;
font-size: 0.6875rem;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.05em;
}
/* Copy button for code blocks (optional enhancement) */
.code-block {
position: relative;
}
.code-block .copy-btn {
position: absolute;
top: 8px;
right: 8px;
background: var(--bg-tertiary);
border: 1px solid var(--border-primary);
color: var(--text-secondary);
padding: 4px 8px;
border-radius: var(--radius-sm);
font-size: 0.6875rem;
cursor: pointer;
opacity: 0;
transition: opacity var(--transition-fast);
}
.code-block:hover .copy-btn {
opacity: 1;
}
.code-block .copy-btn:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
/* Responsive adjustments */
@media (max-width: 768px) {
.upload-form {
flex-direction: column;
align-items: stretch;
}
.upload-form .form-group {
min-width: 100%;
}
.tags-table {
overflow-x: auto;
}
.tags-table table {
min-width: 500px;
}
} }