mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-08-31 23:36:57 +00:00
dark mode! fixes #1
This commit is contained in:
@@ -302,11 +302,12 @@ func GetRepositoryMetadata(db *sql.DB, did string, repository string) (map[strin
|
||||
// GetUserByDID retrieves a user by DID
|
||||
func GetUserByDID(db *sql.DB, did string) (*User, error) {
|
||||
var user User
|
||||
var avatar sql.NullString
|
||||
err := db.QueryRow(`
|
||||
SELECT did, handle, pds_endpoint, avatar, last_seen
|
||||
FROM users
|
||||
WHERE did = ?
|
||||
`, did).Scan(&user.DID, &user.Handle, &user.PDSEndpoint, &user.Avatar, &user.LastSeen)
|
||||
`, did).Scan(&user.DID, &user.Handle, &user.PDSEndpoint, &avatar, &user.LastSeen)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
@@ -315,17 +316,23 @@ func GetUserByDID(db *sql.DB, did string) (*User, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Handle NULL avatar
|
||||
if avatar.Valid {
|
||||
user.Avatar = avatar.String
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// GetUserByHandle retrieves a user by handle
|
||||
func GetUserByHandle(db *sql.DB, handle string) (*User, error) {
|
||||
var user User
|
||||
var avatar sql.NullString
|
||||
err := db.QueryRow(`
|
||||
SELECT did, handle, pds_endpoint, avatar, last_seen
|
||||
FROM users
|
||||
WHERE handle = ?
|
||||
`, handle).Scan(&user.DID, &user.Handle, &user.PDSEndpoint, &user.Avatar, &user.LastSeen)
|
||||
`, handle).Scan(&user.DID, &user.Handle, &user.PDSEndpoint, &avatar, &user.LastSeen)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
@@ -334,6 +341,11 @@ func GetUserByHandle(db *sql.DB, handle string) (*User, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Handle NULL avatar
|
||||
if avatar.Valid {
|
||||
user.Avatar = avatar.String
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -285,7 +285,20 @@ func (p *Processor) ProcessSailorProfile(ctx context.Context, did string, record
|
||||
// This is called when Jetstream receives an identity event indicating a handle change.
|
||||
// The identity cache is invalidated to ensure the next lookup uses the new handle,
|
||||
// and the database is updated to reflect the change in the UI.
|
||||
//
|
||||
// Only processes events for users who already exist in our database (have ATCR activity).
|
||||
func (p *Processor) ProcessIdentity(ctx context.Context, did string, newHandle string) error {
|
||||
// Check if user exists in our database - only update if they're an ATCR user
|
||||
user, err := db.GetUserByDID(p.db, did)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check user existence: %w", err)
|
||||
}
|
||||
|
||||
// Skip if user doesn't exist - they don't have any ATCR activity (manifests, profiles, etc.)
|
||||
if user == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update handle in database
|
||||
if err := db.UpdateUserHandle(p.db, did, newHandle); err != nil {
|
||||
slog.Warn("Failed to update user handle in database",
|
||||
@@ -308,6 +321,7 @@ func (p *Processor) ProcessIdentity(ctx context.Context, did string, newHandle s
|
||||
slog.Info("Processed identity change event",
|
||||
"component", "processor",
|
||||
"did", did,
|
||||
"old_handle", user.Handle,
|
||||
"new_handle", newHandle)
|
||||
|
||||
return nil
|
||||
@@ -326,12 +340,25 @@ func (p *Processor) ProcessIdentity(ctx context.Context, did string, newHandle s
|
||||
// - If truly deactivated: Resolution fails and user won't appear in new queries
|
||||
//
|
||||
// This approach prevents data loss from PDS migrations while still handling deactivations.
|
||||
//
|
||||
// Only processes events for users who already exist in our database (have ATCR activity).
|
||||
func (p *Processor) ProcessAccount(ctx context.Context, did string, active bool, status string) error {
|
||||
// Only process deactivation events
|
||||
if active || status != "deactivated" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if user exists in our database - only update if they're an ATCR user
|
||||
user, err := db.GetUserByDID(p.db, did)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check user existence: %w", err)
|
||||
}
|
||||
|
||||
// Skip if user doesn't exist - they don't have any ATCR activity
|
||||
if user == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Invalidate cached identity data to force re-resolution on next lookup
|
||||
// This will discover if the account was migrated (new PDS) or truly deactivated (resolution fails)
|
||||
if err := atproto.InvalidateIdentity(ctx, did); err != nil {
|
||||
@@ -345,6 +372,7 @@ func (p *Processor) ProcessAccount(ctx context.Context, did string, active bool,
|
||||
slog.Info("Processed account deactivation event - cache invalidated",
|
||||
"component", "processor",
|
||||
"did", did,
|
||||
"handle", user.Handle,
|
||||
"status", status)
|
||||
|
||||
return nil
|
||||
|
||||
@@ -443,12 +443,7 @@ func (w *Worker) processIdentity(event *JetstreamEvent) error {
|
||||
}
|
||||
|
||||
identity := event.Identity
|
||||
slog.Info("Jetstream processing identity event",
|
||||
"did", identity.DID,
|
||||
"handle", identity.Handle,
|
||||
"seq", identity.Seq)
|
||||
|
||||
// Process via shared processor
|
||||
// Process via shared processor (only ATCR users will be logged at Info level)
|
||||
return w.processor.ProcessIdentity(context.Background(), identity.DID, identity.Handle)
|
||||
}
|
||||
|
||||
@@ -459,13 +454,7 @@ func (w *Worker) processAccount(event *JetstreamEvent) error {
|
||||
}
|
||||
|
||||
account := event.Account
|
||||
slog.Info("Jetstream processing account event",
|
||||
"did", account.DID,
|
||||
"active", account.Active,
|
||||
"status", account.Status,
|
||||
"seq", account.Seq)
|
||||
|
||||
// Process via shared processor
|
||||
// Process via shared processor (only ATCR users will be logged at Info level)
|
||||
return w.processor.ProcessAccount(context.Background(), account.DID, account.Active, account.Status)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
:root {
|
||||
--primary: #0066cc;
|
||||
--button-primary: #0066cc;
|
||||
--primary-dark: #0052a3;
|
||||
--secondary: #6c757d;
|
||||
--success: #28a745;
|
||||
@@ -16,6 +17,30 @@
|
||||
--hover-bg: #f9f9f9;
|
||||
--star: #fbbf24;
|
||||
|
||||
/* Navbar colors - stay consistent in dark mode */
|
||||
--navbar-bg: #1a1a1a;
|
||||
--navbar-fg: #ffffff;
|
||||
|
||||
/* Button text color */
|
||||
--btn-text: #ffffff;
|
||||
|
||||
/* Theme toggle icon */
|
||||
--theme-icon: '🌙';
|
||||
|
||||
/* Shadows */
|
||||
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||
--shadow-md: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
--shadow-lg: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
|
||||
/* Metadata badge */
|
||||
--metadata-badge-bg: #f0f0f0;
|
||||
--metadata-badge-text: var(--fg);
|
||||
|
||||
/* Version badge */
|
||||
--version-badge-bg: #f3e5f5;
|
||||
--version-badge-text: #7b1fa2;
|
||||
--version-badge-border: #ba68c8;
|
||||
|
||||
/* Hero section colors */
|
||||
--hero-bg-start: #f8f9fa;
|
||||
--hero-bg-end: #e9ecef;
|
||||
@@ -28,6 +53,61 @@
|
||||
--terminal-comment: #6a9955;
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
--primary: #60a5fa;
|
||||
--button-primary: #1d4ed8;
|
||||
--primary-dark: #1e40af;
|
||||
--secondary: #9ca3af;
|
||||
--success: #34d399;
|
||||
--success-bg: #064e3b;
|
||||
--warning: #fbbf24;
|
||||
--warning-bg: #78350f;
|
||||
--danger: #dc3545;
|
||||
--danger-bg: #7f1d1d;
|
||||
--bg: #2a2a2a;
|
||||
--fg: #e0e0e0;
|
||||
--border-dark: #9ca3af;
|
||||
--border: #404040;
|
||||
--code-bg: #1e1e1e;
|
||||
--hover-bg: #333333;
|
||||
--star: #fbbf24;
|
||||
|
||||
/* Navbar colors - stay consistent (always black) */
|
||||
--navbar-bg: #1a1a1a;
|
||||
--navbar-fg: #ffffff;
|
||||
|
||||
/* Button text color */
|
||||
--btn-text: #ffffff;
|
||||
|
||||
/* Theme toggle icon */
|
||||
--theme-icon: '☀️';
|
||||
|
||||
/* Shadows - lighter for dark backgrounds */
|
||||
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.3);
|
||||
--shadow-md: 0 2px 4px rgba(0, 0, 0, 0.4);
|
||||
--shadow-lg: 0 4px 12px rgba(0, 0, 0, 0.5);
|
||||
|
||||
/* Metadata badge - darker in dark mode */
|
||||
--metadata-badge-bg: #1e1e1e;
|
||||
--metadata-badge-text: #ffffff;
|
||||
|
||||
/* Version badge - swapped colors with softer purple background */
|
||||
--version-badge-bg: #9b59b6;
|
||||
--version-badge-text: #ffffff;
|
||||
--version-badge-border: #ba68c8;
|
||||
|
||||
/* Hero section colors */
|
||||
--hero-bg-start: #2d2d2d;
|
||||
--hero-bg-end: #1a1a1a;
|
||||
|
||||
/* Terminal colors - keep similar since already dark */
|
||||
--terminal-bg: #0d0d0d;
|
||||
--terminal-header-bg: #1a1a1a;
|
||||
--terminal-text: #d0d0d0;
|
||||
--terminal-prompt: #4ec9b0;
|
||||
--terminal-comment: #6a9955;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@@ -42,24 +122,24 @@ body {
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
max-width: 1920px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* Navigation */
|
||||
.navbar {
|
||||
background: var(--fg);
|
||||
color:var(--bg);
|
||||
background: var(--navbar-bg);
|
||||
color: var(--navbar-fg);
|
||||
padding: 1rem 2rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.nav-brand a {
|
||||
color:var(--bg);
|
||||
color: var(--navbar-fg);
|
||||
text-decoration: none;
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
@@ -90,7 +170,7 @@ body {
|
||||
}
|
||||
|
||||
.nav-links a {
|
||||
color:var(--fg);
|
||||
color: var(--navbar-fg);
|
||||
text-decoration: none;
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
@@ -110,7 +190,7 @@ body {
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
background: transparent;
|
||||
color:var(--bg);
|
||||
color: var(--navbar-fg);
|
||||
border: none;
|
||||
padding: 0.5rem;
|
||||
cursor: pointer;
|
||||
@@ -133,7 +213,7 @@ body {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
background: var(--primary);
|
||||
background: var(--button-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -153,14 +233,14 @@ body {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
background: var(--primary);
|
||||
background: var(--button-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
font-size: 2rem;
|
||||
text-transform: uppercase;
|
||||
color: var(--bg);
|
||||
color: var(--btn-text);
|
||||
}
|
||||
|
||||
.user-profile {
|
||||
@@ -176,7 +256,7 @@ body {
|
||||
}
|
||||
|
||||
.user-handle {
|
||||
color: var(--bg);
|
||||
color: var(--navbar-fg);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
@@ -195,7 +275,7 @@ body {
|
||||
background:var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
box-shadow: var(--shadow-lg);
|
||||
min-width: 200px;
|
||||
overflow: hidden;
|
||||
z-index: 1000;
|
||||
@@ -237,8 +317,8 @@ body {
|
||||
/* Buttons */
|
||||
button, .btn, .btn-primary, .btn-secondary {
|
||||
padding: 0.5rem 1rem;
|
||||
background: var(--primary);
|
||||
color:var(--bg);
|
||||
background: var(--button-primary);
|
||||
color: var(--btn-text);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
@@ -254,7 +334,7 @@ button:hover, .btn:hover, .btn-primary:hover, .btn-secondary:hover {
|
||||
|
||||
/* Override nav-links color for primary button */
|
||||
.nav-links .btn-primary {
|
||||
color: var(--bg);
|
||||
color: var(--btn-text);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
@@ -263,8 +343,14 @@ button:hover, .btn:hover, .btn-primary:hover, .btn-secondary:hover {
|
||||
|
||||
.btn-link {
|
||||
background: transparent;
|
||||
color:var(--bg);
|
||||
text-decoration: underline;
|
||||
color: var(--navbar-fg);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.theme-toggle-btn::before {
|
||||
content: var(--theme-icon);
|
||||
font-size: 1.2rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
@@ -275,7 +361,8 @@ button:hover, .btn:hover, .btn-primary:hover, .btn-secondary:hover {
|
||||
|
||||
.copy-btn {
|
||||
padding: 0.25rem 0.75rem;
|
||||
background: var(--primary);
|
||||
background: var(--button-primary);
|
||||
color: var(--btn-text);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
@@ -286,7 +373,7 @@ button:hover, .btn:hover, .btn-primary:hover, .btn-secondary:hover {
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
background:var(--bg);
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.push-header {
|
||||
@@ -396,14 +483,14 @@ button:hover, .btn:hover, .btn-primary:hover, .btn-secondary:hover {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 8px;
|
||||
background: var(--primary);
|
||||
background: var(--button-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
text-transform: uppercase;
|
||||
color: var(--bg);
|
||||
color: var(--btn-text);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -538,17 +625,17 @@ a.license-badge {
|
||||
}
|
||||
|
||||
a.license-badge:hover {
|
||||
background: var(--primary);
|
||||
color: var(--bg);
|
||||
border-color: var(--primary);
|
||||
background: var(--button-primary);
|
||||
color: var(--btn-text);
|
||||
border-color: var(--button-primary);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.version-badge {
|
||||
background: #f3e5f5;
|
||||
color: #7b1fa2;
|
||||
border: 1px solid #ba68c8;
|
||||
background: var(--version-badge-bg);
|
||||
color: var(--version-badge-text);
|
||||
border: 1px solid var(--version-badge-border);
|
||||
}
|
||||
|
||||
.repo-description {
|
||||
@@ -633,7 +720,7 @@ a.license-badge:hover {
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.settings-section h2 {
|
||||
@@ -702,7 +789,7 @@ a.license-badge:hover {
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
position: relative;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
@@ -817,7 +904,7 @@ a.license-badge:hover {
|
||||
padding: 2rem;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border);
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.login-form .form-group {
|
||||
@@ -877,7 +964,7 @@ a.license-badge:hover {
|
||||
border-radius: 8px;
|
||||
padding: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.repo-hero {
|
||||
@@ -899,14 +986,14 @@ a.license-badge:hover {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 12px;
|
||||
background: var(--primary);
|
||||
background: var(--button-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
font-size: 2.5rem;
|
||||
text-transform: uppercase;
|
||||
color: var(--bg);
|
||||
color: var(--btn-text);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -944,10 +1031,17 @@ a.license-badge:hover {
|
||||
margin: 0.5rem 0 0 0;
|
||||
}
|
||||
|
||||
.repo-actions {
|
||||
.repo-info-row {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
align-items: center;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.repo-actions {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.star-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -1002,9 +1096,8 @@ a.license-badge:hover {
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 1.5rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid var(--border);
|
||||
flex: 1;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.metadata-badge {
|
||||
@@ -1044,7 +1137,7 @@ a.license-badge:hover {
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.repo-section h2 {
|
||||
@@ -1077,7 +1170,7 @@ a.license-badge:hover {
|
||||
.tag-name-large {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
color: var(--primary);
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
.tag-timestamp {
|
||||
@@ -1166,8 +1259,8 @@ a.license-badge:hover {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
border-radius: 12px;
|
||||
background: var(--primary);
|
||||
color: var(--bg);
|
||||
background: var(--button-primary);
|
||||
color: var(--btn-text);
|
||||
white-space: nowrap;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
@@ -1235,7 +1328,7 @@ a.license-badge:hover {
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
background: var(--bg);
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
|
||||
box-shadow: var(--shadow-sm);
|
||||
transition: all 0.2s ease;
|
||||
text-decoration: none;
|
||||
color: var(--fg);
|
||||
@@ -1246,7 +1339,7 @@ a.license-badge:hover {
|
||||
}
|
||||
|
||||
.featured-card:hover {
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
||||
box-shadow: var(--shadow-md);
|
||||
border-color: var(--primary);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
@@ -1270,14 +1363,14 @@ a.license-badge:hover {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 8px;
|
||||
background: var(--primary);
|
||||
background: var(--button-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
text-transform: uppercase;
|
||||
color:var(--bg);
|
||||
color: var(--btn-text);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -1383,7 +1476,7 @@ a.license-badge:hover {
|
||||
margin: 0 auto 2.5rem;
|
||||
background: var(--terminal-bg);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);
|
||||
box-shadow: var(--shadow-lg);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -1453,9 +1546,9 @@ a.license-badge:hover {
|
||||
}
|
||||
|
||||
.btn-hero-primary {
|
||||
background: var(--primary);
|
||||
color: var(--bg);
|
||||
border: 2px solid var(--primary);
|
||||
background: var(--button-primary);
|
||||
color: var(--btn-text);
|
||||
border: 2px solid var(--button-primary);
|
||||
}
|
||||
|
||||
.btn-hero-primary:hover {
|
||||
@@ -1468,12 +1561,12 @@ a.license-badge:hover {
|
||||
.btn-hero-secondary {
|
||||
background: transparent;
|
||||
color: var(--primary);
|
||||
border: 2px solid var(--primary);
|
||||
border: 2px solid var(--button-primary);
|
||||
}
|
||||
|
||||
.btn-hero-secondary:hover {
|
||||
background: var(--primary);
|
||||
color: var(--bg);
|
||||
background: var(--button-primary);
|
||||
color: var(--btn-text);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
@@ -1496,7 +1589,7 @@ a.license-badge:hover {
|
||||
|
||||
.benefit-card:hover {
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
box-shadow: var(--shadow-md);
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
|
||||
@@ -1697,7 +1790,7 @@ a.license-badge:hover {
|
||||
/* README and Repository Layout */
|
||||
.repo-content-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 450px;
|
||||
grid-template-columns: 7fr 3fr;
|
||||
gap: 2rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,27 @@
|
||||
// Theme management
|
||||
// Load theme immediately to avoid flash
|
||||
(function() {
|
||||
const theme = localStorage.getItem('theme') || 'light';
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
})();
|
||||
|
||||
function toggleTheme() {
|
||||
const html = document.documentElement;
|
||||
const currentTheme = html.getAttribute('data-theme') || 'light';
|
||||
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
||||
html.setAttribute('data-theme', newTheme);
|
||||
localStorage.setItem('theme', newTheme);
|
||||
updateThemeIcon();
|
||||
}
|
||||
|
||||
function updateThemeIcon() {
|
||||
const themeBtn = document.getElementById('theme-toggle');
|
||||
if (!themeBtn) return;
|
||||
|
||||
const currentTheme = document.documentElement.getAttribute('data-theme') || 'light';
|
||||
themeBtn.setAttribute('aria-label', currentTheme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode');
|
||||
}
|
||||
|
||||
// Copy to clipboard
|
||||
function copyToClipboard(text) {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
</div>
|
||||
|
||||
<div class="nav-links">
|
||||
<button id="theme-toggle" onclick="toggleTheme()" class="btn-link theme-toggle-btn" aria-label="Toggle theme"></button>
|
||||
{{ if .User }}
|
||||
<div class="user-dropdown">
|
||||
<button class="user-menu-btn" id="user-menu-btn" aria-expanded="false" aria-haspopup="true">
|
||||
|
||||
@@ -30,17 +30,18 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Star Button -->
|
||||
<div class="repo-actions">
|
||||
<button class="star-btn{{ if .IsStarred }} starred{{ end }}" id="star-btn" onclick="toggleStar('{{ .Owner.Handle }}', '{{ .Repository.Name }}')">
|
||||
<span class="star-icon" id="star-icon">{{ if .IsStarred }}★{{ else }}☆{{ end }}</span>
|
||||
<span class="star-count" id="star-count">{{ .StarCount }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<!-- Star Button and Metadata Row -->
|
||||
<div class="repo-info-row">
|
||||
<div class="repo-actions">
|
||||
<button class="star-btn{{ if .IsStarred }} starred{{ end }}" id="star-btn" onclick="toggleStar('{{ .Owner.Handle }}', '{{ .Repository.Name }}')">
|
||||
<span class="star-icon" id="star-icon">{{ if .IsStarred }}★{{ else }}☆{{ end }}</span>
|
||||
<span class="star-count" id="star-count">{{ .StarCount }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Metadata Section -->
|
||||
{{ if or .Repository.Licenses .Repository.SourceURL .Repository.DocumentationURL .Repository.Version }}
|
||||
<div class="repo-metadata">
|
||||
<!-- Metadata Section -->
|
||||
{{ if or .Repository.Licenses .Repository.SourceURL .Repository.DocumentationURL .Repository.Version }}
|
||||
<div class="repo-metadata">
|
||||
{{ if .Repository.Version }}
|
||||
<span class="metadata-badge version-badge" title="Version">
|
||||
{{ .Repository.Version }}
|
||||
@@ -69,8 +70,11 @@
|
||||
Documentation
|
||||
</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ else }}
|
||||
<div class="repo-metadata"></div>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
<!-- Pull Command -->
|
||||
<div class="pull-command-section">
|
||||
|
||||
Reference in New Issue
Block a user