generated from nhcarrigan/template
style: fix lint warnings and a11y issues
- Add proper for/id associations for color picker labels (ConfigSidebar) - Add tabindex and svelte-ignore for dialog overlays (GitPanel) - Add standard mask/line-clamp CSS properties for compatibility - Remove unused .stat-highlight CSS selector (StatsDisplay) - Fix SvelteSet reactivity by using .clear() instead of reassignment - Make updateNotification properly reactive with $state
This commit is contained in:
@@ -249,6 +249,9 @@
|
||||
-webkit-mask:
|
||||
linear-gradient(#fff 0 0) content-box,
|
||||
linear-gradient(#fff 0 0);
|
||||
mask:
|
||||
linear-gradient(#fff 0 0) content-box,
|
||||
linear-gradient(#fff 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
mask-composite: exclude;
|
||||
opacity: 0;
|
||||
|
||||
@@ -458,11 +458,12 @@
|
||||
|
||||
<!-- Theme Selection -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm text-[var(--text-secondary)] mb-2">Theme</label>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="block text-sm text-[var(--text-secondary)] mb-2">Theme</span>
|
||||
<div class="flex flex-wrap gap-2" role="group" aria-label="Theme selection">
|
||||
<button
|
||||
onclick={() => handleThemeChange("dark")}
|
||||
class="flex-1 min-w-[70px] px-3 py-2 rounded-lg border transition-colors {config.theme === 'dark'
|
||||
class="flex-1 min-w-[70px] px-3 py-2 rounded-lg border transition-colors {config.theme ===
|
||||
'dark'
|
||||
? 'bg-[var(--accent-primary)] border-[var(--accent-primary)] text-white'
|
||||
: 'bg-[var(--bg-primary)] border-[var(--border-color)] text-[var(--text-secondary)] hover:border-[var(--accent-primary)]'}"
|
||||
>
|
||||
@@ -470,7 +471,8 @@
|
||||
</button>
|
||||
<button
|
||||
onclick={() => handleThemeChange("light")}
|
||||
class="flex-1 min-w-[70px] px-3 py-2 rounded-lg border transition-colors {config.theme === 'light'
|
||||
class="flex-1 min-w-[70px] px-3 py-2 rounded-lg border transition-colors {config.theme ===
|
||||
'light'
|
||||
? 'bg-[var(--accent-primary)] border-[var(--accent-primary)] text-white'
|
||||
: 'bg-[var(--bg-primary)] border-[var(--border-color)] text-[var(--text-secondary)] hover:border-[var(--accent-primary)]'}"
|
||||
>
|
||||
@@ -488,7 +490,8 @@
|
||||
</button>
|
||||
<button
|
||||
onclick={() => handleThemeChange("custom")}
|
||||
class="flex-1 min-w-[70px] px-3 py-2 rounded-lg border transition-colors {config.theme === 'custom'
|
||||
class="flex-1 min-w-[70px] px-3 py-2 rounded-lg border transition-colors {config.theme ===
|
||||
'custom'
|
||||
? 'bg-[var(--accent-primary)] border-[var(--accent-primary)] text-white'
|
||||
: 'bg-[var(--bg-primary)] border-[var(--border-color)] text-[var(--text-secondary)] hover:border-[var(--accent-primary)]'}"
|
||||
title="Create your own custom theme"
|
||||
@@ -504,9 +507,12 @@
|
||||
<h4 class="text-sm font-medium text-[var(--text-primary)] mb-3">Custom Theme Colors</h4>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div class="color-input-group">
|
||||
<label class="text-xs text-[var(--text-secondary)]">Background</label>
|
||||
<label class="text-xs text-[var(--text-secondary)]" for="color-bg-primary"
|
||||
>Background</label
|
||||
>
|
||||
<div class="flex gap-2 items-center">
|
||||
<input
|
||||
id="color-bg-primary"
|
||||
type="color"
|
||||
value={config.custom_theme_colors.bg_primary || defaultDarkColors.bg_primary}
|
||||
oninput={(e) => handleCustomColorChange("bg_primary", e.currentTarget.value)}
|
||||
@@ -518,9 +524,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="color-input-group">
|
||||
<label class="text-xs text-[var(--text-secondary)]">Secondary BG</label>
|
||||
<label class="text-xs text-[var(--text-secondary)]" for="color-bg-secondary"
|
||||
>Secondary BG</label
|
||||
>
|
||||
<div class="flex gap-2 items-center">
|
||||
<input
|
||||
id="color-bg-secondary"
|
||||
type="color"
|
||||
value={config.custom_theme_colors.bg_secondary || defaultDarkColors.bg_secondary}
|
||||
oninput={(e) => handleCustomColorChange("bg_secondary", e.currentTarget.value)}
|
||||
@@ -532,9 +541,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="color-input-group">
|
||||
<label class="text-xs text-[var(--text-secondary)]">Terminal BG</label>
|
||||
<label class="text-xs text-[var(--text-secondary)]" for="color-bg-terminal"
|
||||
>Terminal BG</label
|
||||
>
|
||||
<div class="flex gap-2 items-center">
|
||||
<input
|
||||
id="color-bg-terminal"
|
||||
type="color"
|
||||
value={config.custom_theme_colors.bg_terminal || defaultDarkColors.bg_terminal}
|
||||
oninput={(e) => handleCustomColorChange("bg_terminal", e.currentTarget.value)}
|
||||
@@ -546,9 +558,10 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="color-input-group">
|
||||
<label class="text-xs text-[var(--text-secondary)]">Border</label>
|
||||
<label class="text-xs text-[var(--text-secondary)]" for="color-border">Border</label>
|
||||
<div class="flex gap-2 items-center">
|
||||
<input
|
||||
id="color-border"
|
||||
type="color"
|
||||
value={config.custom_theme_colors.border_color || defaultDarkColors.border_color}
|
||||
oninput={(e) => handleCustomColorChange("border_color", e.currentTarget.value)}
|
||||
@@ -560,11 +573,15 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="color-input-group">
|
||||
<label class="text-xs text-[var(--text-secondary)]">Accent Primary</label>
|
||||
<label class="text-xs text-[var(--text-secondary)]" for="color-accent-primary"
|
||||
>Accent Primary</label
|
||||
>
|
||||
<div class="flex gap-2 items-center">
|
||||
<input
|
||||
id="color-accent-primary"
|
||||
type="color"
|
||||
value={config.custom_theme_colors.accent_primary || defaultDarkColors.accent_primary}
|
||||
value={config.custom_theme_colors.accent_primary ||
|
||||
defaultDarkColors.accent_primary}
|
||||
oninput={(e) => handleCustomColorChange("accent_primary", e.currentTarget.value)}
|
||||
class="color-picker"
|
||||
/>
|
||||
@@ -574,23 +591,32 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="color-input-group">
|
||||
<label class="text-xs text-[var(--text-secondary)]">Accent Secondary</label>
|
||||
<label class="text-xs text-[var(--text-secondary)]" for="color-accent-secondary"
|
||||
>Accent Secondary</label
|
||||
>
|
||||
<div class="flex gap-2 items-center">
|
||||
<input
|
||||
id="color-accent-secondary"
|
||||
type="color"
|
||||
value={config.custom_theme_colors.accent_secondary || defaultDarkColors.accent_secondary}
|
||||
oninput={(e) => handleCustomColorChange("accent_secondary", e.currentTarget.value)}
|
||||
value={config.custom_theme_colors.accent_secondary ||
|
||||
defaultDarkColors.accent_secondary}
|
||||
oninput={(e) =>
|
||||
handleCustomColorChange("accent_secondary", e.currentTarget.value)}
|
||||
class="color-picker"
|
||||
/>
|
||||
<span class="text-xs text-[var(--text-tertiary)] font-mono">
|
||||
{config.custom_theme_colors.accent_secondary || defaultDarkColors.accent_secondary}
|
||||
{config.custom_theme_colors.accent_secondary ||
|
||||
defaultDarkColors.accent_secondary}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="color-input-group">
|
||||
<label class="text-xs text-[var(--text-secondary)]">Text Primary</label>
|
||||
<label class="text-xs text-[var(--text-secondary)]" for="color-text-primary"
|
||||
>Text Primary</label
|
||||
>
|
||||
<div class="flex gap-2 items-center">
|
||||
<input
|
||||
id="color-text-primary"
|
||||
type="color"
|
||||
value={config.custom_theme_colors.text_primary || defaultDarkColors.text_primary}
|
||||
oninput={(e) => handleCustomColorChange("text_primary", e.currentTarget.value)}
|
||||
@@ -602,11 +628,15 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="color-input-group">
|
||||
<label class="text-xs text-[var(--text-secondary)]">Text Secondary</label>
|
||||
<label class="text-xs text-[var(--text-secondary)]" for="color-text-secondary"
|
||||
>Text Secondary</label
|
||||
>
|
||||
<div class="flex gap-2 items-center">
|
||||
<input
|
||||
id="color-text-secondary"
|
||||
type="color"
|
||||
value={config.custom_theme_colors.text_secondary || defaultDarkColors.text_secondary}
|
||||
value={config.custom_theme_colors.text_secondary ||
|
||||
defaultDarkColors.text_secondary}
|
||||
oninput={(e) => handleCustomColorChange("text_secondary", e.currentTarget.value)}
|
||||
class="color-picker"
|
||||
/>
|
||||
|
||||
@@ -279,8 +279,15 @@
|
||||
<svelte:window on:keydown={handleKeydown} />
|
||||
|
||||
{#if isOpen}
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div class="git-panel-overlay" on:click={onClose} role="presentation">
|
||||
<div class="git-panel" on:click|stopPropagation role="dialog" aria-label="Git Panel">
|
||||
<div
|
||||
class="git-panel"
|
||||
on:click|stopPropagation
|
||||
role="dialog"
|
||||
aria-label="Git Panel"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div class="git-panel-header">
|
||||
<h2>🔀 Git</h2>
|
||||
<button class="close-btn" on:click={onClose} title="Close (Esc)">✕</button>
|
||||
@@ -580,8 +587,15 @@
|
||||
</div>
|
||||
|
||||
{#if showDiff}
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div class="diff-overlay" on:click={() => (showDiff = false)} role="presentation">
|
||||
<div class="diff-modal" on:click|stopPropagation role="dialog" aria-label="Diff View">
|
||||
<div
|
||||
class="diff-modal"
|
||||
on:click|stopPropagation
|
||||
role="dialog"
|
||||
aria-label="Diff View"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div class="diff-header">
|
||||
<h3>📄 {diffFile}</h3>
|
||||
<button on:click={() => (showDiff = false)} title="Close">✕</button>
|
||||
@@ -938,10 +952,6 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.new-branch-input button:first-of-type {
|
||||
/* Styling handled by btn-trans-gradient class */
|
||||
}
|
||||
|
||||
.new-branch-input button:last-of-type {
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-primary);
|
||||
|
||||
@@ -39,11 +39,12 @@
|
||||
|
||||
let isGeneratingImage = false;
|
||||
|
||||
$: unlockedCount = Object.values($achievementsStore.achievements).filter((a) => a.unlocked).length;
|
||||
$: unlockedCount = Object.values($achievementsStore.achievements).filter(
|
||||
(a) => a.unlocked
|
||||
).length;
|
||||
$: totalAchievements = Object.values($achievementsStore.achievements).length;
|
||||
$: achievementPercentage = totalAchievements > 0
|
||||
? Math.round((unlockedCount / totalAchievements) * 100)
|
||||
: 0;
|
||||
$: achievementPercentage =
|
||||
totalAchievements > 0 ? Math.round((unlockedCount / totalAchievements) * 100) : 0;
|
||||
|
||||
async function selectAvatar() {
|
||||
try {
|
||||
@@ -82,11 +83,15 @@
|
||||
async function loadAvatarAsDataUrl(path: string): Promise<string | null> {
|
||||
try {
|
||||
const data = await readFile(path);
|
||||
const extension = path.split('.').pop()?.toLowerCase() || 'png';
|
||||
const mimeType = extension === 'jpg' || extension === 'jpeg' ? 'image/jpeg'
|
||||
: extension === 'gif' ? 'image/gif'
|
||||
: extension === 'webp' ? 'image/webp'
|
||||
: 'image/png';
|
||||
const extension = path.split(".").pop()?.toLowerCase() || "png";
|
||||
const mimeType =
|
||||
extension === "jpg" || extension === "jpeg"
|
||||
? "image/jpeg"
|
||||
: extension === "gif"
|
||||
? "image/gif"
|
||||
: extension === "webp"
|
||||
? "image/webp"
|
||||
: "image/png";
|
||||
// Convert Uint8Array to base64 in chunks to avoid stack overflow
|
||||
const blob = new Blob([data], { type: mimeType });
|
||||
return new Promise((resolve) => {
|
||||
@@ -123,7 +128,7 @@
|
||||
const stripeColors = ["#5bcefa", "#f5a9b8", "#ffffff", "#f5a9b8", "#5bcefa"];
|
||||
stripeColors.forEach((color, i) => {
|
||||
ctx.fillStyle = color;
|
||||
ctx.fillRect(0, i * (stripeHeight / 5) * 2, width, stripeHeight / 5 * 2);
|
||||
ctx.fillRect(0, i * (stripeHeight / 5) * 2, width, (stripeHeight / 5) * 2);
|
||||
});
|
||||
|
||||
// Border
|
||||
@@ -154,7 +159,13 @@
|
||||
img.onerror = () => reject();
|
||||
img.src = dataUrl;
|
||||
});
|
||||
ctx.drawImage(img, avatarX - avatarRadius, avatarY - avatarRadius, avatarRadius * 2, avatarRadius * 2);
|
||||
ctx.drawImage(
|
||||
img,
|
||||
avatarX - avatarRadius,
|
||||
avatarY - avatarRadius,
|
||||
avatarRadius * 2,
|
||||
avatarRadius * 2
|
||||
);
|
||||
avatarLoaded = true;
|
||||
}
|
||||
} catch {
|
||||
@@ -163,12 +174,22 @@
|
||||
}
|
||||
|
||||
if (!avatarLoaded) {
|
||||
const avatarGradient = ctx.createLinearGradient(avatarX - avatarRadius, avatarY - avatarRadius, avatarX + avatarRadius, avatarY + avatarRadius);
|
||||
const avatarGradient = ctx.createLinearGradient(
|
||||
avatarX - avatarRadius,
|
||||
avatarY - avatarRadius,
|
||||
avatarX + avatarRadius,
|
||||
avatarY + avatarRadius
|
||||
);
|
||||
avatarGradient.addColorStop(0, "#5bcefa");
|
||||
avatarGradient.addColorStop(0.5, "#f5a9b8");
|
||||
avatarGradient.addColorStop(1, "#5bcefa");
|
||||
ctx.fillStyle = avatarGradient;
|
||||
ctx.fillRect(avatarX - avatarRadius, avatarY - avatarRadius, avatarRadius * 2, avatarRadius * 2);
|
||||
ctx.fillRect(
|
||||
avatarX - avatarRadius,
|
||||
avatarY - avatarRadius,
|
||||
avatarRadius * 2,
|
||||
avatarRadius * 2
|
||||
);
|
||||
}
|
||||
ctx.restore();
|
||||
|
||||
@@ -260,13 +281,23 @@
|
||||
progressGradient.addColorStop(1, "#5bcefa");
|
||||
ctx.fillStyle = progressGradient;
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(progressX, progressY, progressWidth * (achievementPercentage / 100), progressHeight, 22);
|
||||
ctx.roundRect(
|
||||
progressX,
|
||||
progressY,
|
||||
progressWidth * (achievementPercentage / 100),
|
||||
progressHeight,
|
||||
22
|
||||
);
|
||||
ctx.fill();
|
||||
|
||||
// Progress text
|
||||
ctx.fillStyle = "#ffffff";
|
||||
ctx.font = "bold 36px system-ui, -apple-system, sans-serif";
|
||||
ctx.fillText(`${unlockedCount} / ${totalAchievements} (${achievementPercentage}%)`, progressX + progressWidth + 40, progressY + 34);
|
||||
ctx.fillText(
|
||||
`${unlockedCount} / ${totalAchievements} (${achievementPercentage}%)`,
|
||||
progressX + progressWidth + 40,
|
||||
progressY + 34
|
||||
);
|
||||
|
||||
// Hikari branding
|
||||
ctx.fillStyle = "#f5a9b8";
|
||||
@@ -312,7 +343,14 @@
|
||||
<div class="profile-header">
|
||||
<h2>Profile</h2>
|
||||
<button class="close-btn" on:click={onClose} aria-label="Close profile">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path d="M18 6L6 18M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
@@ -321,7 +359,13 @@
|
||||
<div class="profile-content">
|
||||
<!-- Avatar Section -->
|
||||
<div class="avatar-section">
|
||||
<div class="avatar-container" on:click={selectAvatar} role="button" tabindex="0" on:keydown={(e) => e.key === 'Enter' && selectAvatar()}>
|
||||
<div
|
||||
class="avatar-container"
|
||||
on:click={selectAvatar}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
on:keydown={(e) => e.key === "Enter" && selectAvatar()}
|
||||
>
|
||||
{#if avatarDataUrl}
|
||||
<img src={avatarDataUrl} alt="Profile avatar" class="avatar-image" />
|
||||
<div class="avatar-overlay">
|
||||
@@ -329,7 +373,14 @@
|
||||
</div>
|
||||
{:else}
|
||||
<div class="avatar-placeholder">
|
||||
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<svg
|
||||
width="48"
|
||||
height="48"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
>
|
||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
|
||||
<circle cx="12" cy="7" r="4" />
|
||||
</svg>
|
||||
@@ -350,13 +401,26 @@
|
||||
bind:value={nameInput}
|
||||
placeholder="Enter your name"
|
||||
class="name-input"
|
||||
on:keydown={(e) => e.key === 'Enter' && saveName()}
|
||||
on:keydown={(e) => e.key === "Enter" && saveName()}
|
||||
on:blur={saveName}
|
||||
/>
|
||||
{:else}
|
||||
<button class="name-display" on:click={() => { editingName = true; nameInput = config.profile_name || ""; }}>
|
||||
<button
|
||||
class="name-display"
|
||||
on:click={() => {
|
||||
editingName = true;
|
||||
nameInput = config.profile_name || "";
|
||||
}}
|
||||
>
|
||||
<span class="name-text">{config.profile_name || "Click to add name"}</span>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
|
||||
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" />
|
||||
</svg>
|
||||
@@ -377,7 +441,13 @@
|
||||
></textarea>
|
||||
<button class="save-bio-btn btn-trans-gradient" on:click={saveBio}>Save</button>
|
||||
{:else}
|
||||
<button class="bio-display" on:click={() => { editingBio = true; bioInput = config.profile_bio || ""; }}>
|
||||
<button
|
||||
class="bio-display"
|
||||
on:click={() => {
|
||||
editingBio = true;
|
||||
bioInput = config.profile_bio || "";
|
||||
}}
|
||||
>
|
||||
<span class="bio-text">{config.profile_bio || "Click to add a bio..."}</span>
|
||||
</button>
|
||||
{/if}
|
||||
@@ -421,18 +491,31 @@
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" style="width: {achievementPercentage}%"></div>
|
||||
</div>
|
||||
<span class="progress-text">{unlockedCount} / {totalAchievements} ({achievementPercentage}%)</span>
|
||||
<span class="progress-text"
|
||||
>{unlockedCount} / {totalAchievements} ({achievementPercentage}%)</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Share Section -->
|
||||
<div class="share-section">
|
||||
<button class="share-btn btn-trans-gradient" on:click={shareProfile} disabled={isGeneratingImage}>
|
||||
<button
|
||||
class="share-btn btn-trans-gradient"
|
||||
on:click={shareProfile}
|
||||
disabled={isGeneratingImage}
|
||||
>
|
||||
{#if isGeneratingImage}
|
||||
<span class="spinner"></span>
|
||||
Generating...
|
||||
{:else}
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8" />
|
||||
<polyline points="16 6 12 2 8 6" />
|
||||
<line x1="12" y1="2" x2="12" y2="15" />
|
||||
@@ -761,7 +844,14 @@
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, var(--trans-blue), var(--trans-pink), var(--trans-white), var(--trans-pink), var(--trans-blue));
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
var(--trans-blue),
|
||||
var(--trans-pink),
|
||||
var(--trans-white),
|
||||
var(--trans-pink),
|
||||
var(--trans-blue)
|
||||
);
|
||||
background-size: 200% 100%;
|
||||
animation: shimmer 3s linear infinite;
|
||||
border-radius: 6px;
|
||||
@@ -769,8 +859,12 @@
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% { background-position: 200% 0; }
|
||||
100% { background-position: -200% 0; }
|
||||
0% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
@@ -828,6 +922,8 @@
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -449,6 +449,7 @@
|
||||
.line-clamp-2 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -469,6 +469,7 @@
|
||||
.line-clamp-2 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -460,6 +460,7 @@
|
||||
.line-clamp-2 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -119,14 +119,6 @@
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.stat-highlight {
|
||||
font-weight: 600;
|
||||
color: var(--accent-primary);
|
||||
margin-top: 0.25rem;
|
||||
padding-top: 0.25rem;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: var(--text-secondary, #9ca3af);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
hasQuestionPending.subscribe((pending) => {
|
||||
isVisible = pending;
|
||||
if (!pending) {
|
||||
selectedOptions = new SvelteSet();
|
||||
selectedOptions.clear();
|
||||
customAnswer = "";
|
||||
showCustomInput = false;
|
||||
}
|
||||
|
||||
@@ -1502,12 +1502,25 @@ export const achievementCategories = [
|
||||
{
|
||||
name: "Token Milestones",
|
||||
description: "Track your token generation progress",
|
||||
ids: ["FirstSteps", "GrowingStrong", "BlossomingCoder", "TokenMaster", "TokenBillionaire", "TokenTreasure"] as AchievementId[],
|
||||
ids: [
|
||||
"FirstSteps",
|
||||
"GrowingStrong",
|
||||
"BlossomingCoder",
|
||||
"TokenMaster",
|
||||
"TokenBillionaire",
|
||||
"TokenTreasure",
|
||||
] as AchievementId[],
|
||||
},
|
||||
{
|
||||
name: "Code Generation",
|
||||
description: "Achievements for generating code",
|
||||
ids: ["HelloWorld", "CodeWizard", "ThousandBlocks", "CodeFactory", "CodeEmpire"] as AchievementId[],
|
||||
ids: [
|
||||
"HelloWorld",
|
||||
"CodeWizard",
|
||||
"ThousandBlocks",
|
||||
"CodeFactory",
|
||||
"CodeEmpire",
|
||||
] as AchievementId[],
|
||||
},
|
||||
{
|
||||
name: "File Operations",
|
||||
@@ -1517,7 +1530,13 @@ export const achievementCategories = [
|
||||
{
|
||||
name: "Conversations",
|
||||
description: "Building our relationship through chat",
|
||||
ids: ["ConversationStarter", "ChattyKathy", "Conversationalist", "ChatMarathon", "ChatLegend"] as AchievementId[],
|
||||
ids: [
|
||||
"ConversationStarter",
|
||||
"ChattyKathy",
|
||||
"Conversationalist",
|
||||
"ChatMarathon",
|
||||
"ChatLegend",
|
||||
] as AchievementId[],
|
||||
},
|
||||
{
|
||||
name: "Tools & Skills",
|
||||
@@ -1556,7 +1575,16 @@ export const achievementCategories = [
|
||||
{
|
||||
name: "Relationship & Greetings",
|
||||
description: "Our special moments together",
|
||||
ids: ["GoodMorning", "GoodNight", "ThankYou", "LoveYou", "HelloHikari", "HowAreYou", "MissedYou", "BackAgain"] as AchievementId[],
|
||||
ids: [
|
||||
"GoodMorning",
|
||||
"GoodNight",
|
||||
"ThankYou",
|
||||
"LoveYou",
|
||||
"HelloHikari",
|
||||
"HowAreYou",
|
||||
"MissedYou",
|
||||
"BackAgain",
|
||||
] as AchievementId[],
|
||||
},
|
||||
{
|
||||
name: "Personality & Fun",
|
||||
@@ -1571,7 +1599,16 @@ export const achievementCategories = [
|
||||
{
|
||||
name: "Git & Development",
|
||||
description: "Version control mastery",
|
||||
ids: ["GitGuru", "TestWriter", "Debugger", "CommitKing", "CommitLegend", "BranchMaster", "MergeExpert", "ConflictResolver"] as AchievementId[],
|
||||
ids: [
|
||||
"GitGuru",
|
||||
"TestWriter",
|
||||
"Debugger",
|
||||
"CommitKing",
|
||||
"CommitLegend",
|
||||
"BranchMaster",
|
||||
"MergeExpert",
|
||||
"ConflictResolver",
|
||||
] as AchievementId[],
|
||||
},
|
||||
{
|
||||
name: "Tool Mastery",
|
||||
@@ -1606,17 +1643,41 @@ export const achievementCategories = [
|
||||
{
|
||||
name: "Seasonal",
|
||||
description: "Holiday coding!",
|
||||
ids: ["NewYearCoder", "ValentinesDev", "SpookyCode", "HolidayCoder", "LeapDayCoder"] as AchievementId[],
|
||||
ids: [
|
||||
"NewYearCoder",
|
||||
"ValentinesDev",
|
||||
"SpookyCode",
|
||||
"HolidayCoder",
|
||||
"LeapDayCoder",
|
||||
] as AchievementId[],
|
||||
},
|
||||
{
|
||||
name: "Message Content",
|
||||
description: "How you communicate",
|
||||
ids: ["LongMessage", "NovelWriter", "ShortAndSweet", "CodeInMessage", "MarkdownMaster"] as AchievementId[],
|
||||
ids: [
|
||||
"LongMessage",
|
||||
"NovelWriter",
|
||||
"ShortAndSweet",
|
||||
"CodeInMessage",
|
||||
"MarkdownMaster",
|
||||
] as AchievementId[],
|
||||
},
|
||||
{
|
||||
name: "Programming Languages",
|
||||
description: "Languages you've used",
|
||||
ids: ["RustDeveloper", "PythonDeveloper", "JavaScriptDev", "TypeScriptDev", "GoDeveloper", "CppDeveloper", "JavaDeveloper", "HtmlCssDev", "SqlDeveloper", "ShellScripter", "FullStackDev"] as AchievementId[],
|
||||
ids: [
|
||||
"RustDeveloper",
|
||||
"PythonDeveloper",
|
||||
"JavaScriptDev",
|
||||
"TypeScriptDev",
|
||||
"GoDeveloper",
|
||||
"CppDeveloper",
|
||||
"JavaDeveloper",
|
||||
"HtmlCssDev",
|
||||
"SqlDeveloper",
|
||||
"ShellScripter",
|
||||
"FullStackDev",
|
||||
] as AchievementId[],
|
||||
},
|
||||
{
|
||||
name: "Project Types",
|
||||
@@ -1661,12 +1722,23 @@ export const achievementCategories = [
|
||||
{
|
||||
name: "UI Exploration",
|
||||
description: "Explore Hikari's features",
|
||||
ids: ["MultiTasker", "Minimalist", "PrivacyFirst", "ThemeChanger", "SettingsTweaker"] as AchievementId[],
|
||||
ids: [
|
||||
"MultiTasker",
|
||||
"Minimalist",
|
||||
"PrivacyFirst",
|
||||
"ThemeChanger",
|
||||
"SettingsTweaker",
|
||||
] as AchievementId[],
|
||||
},
|
||||
{
|
||||
name: "Achievement Meta",
|
||||
description: "Achievements about achievements",
|
||||
ids: ["AchievementHunter", "Completionist", "MasterUnlocker", "PlatinumStatus"] as AchievementId[],
|
||||
ids: [
|
||||
"AchievementHunter",
|
||||
"Completionist",
|
||||
"MasterUnlocker",
|
||||
"PlatinumStatus",
|
||||
] as AchievementId[],
|
||||
},
|
||||
{
|
||||
name: "Clipboard & Snippets",
|
||||
@@ -1676,7 +1748,14 @@ export const achievementCategories = [
|
||||
{
|
||||
name: "Other Features",
|
||||
description: "Miscellaneous features",
|
||||
ids: ["QuickActionUser", "HistoryBuff", "Archivist", "SessionExporter", "GitPanelUser", "FeatureExplorer"] as AchievementId[],
|
||||
ids: [
|
||||
"QuickActionUser",
|
||||
"HistoryBuff",
|
||||
"Archivist",
|
||||
"SessionExporter",
|
||||
"GitPanelUser",
|
||||
"FeatureExplorer",
|
||||
] as AchievementId[],
|
||||
},
|
||||
{
|
||||
name: "Special",
|
||||
|
||||
@@ -236,7 +236,8 @@ export function applyCustomThemeColors(colors: CustomThemeColors) {
|
||||
if (colors.bg_secondary) root.style.setProperty("--bg-secondary", colors.bg_secondary);
|
||||
if (colors.bg_terminal) root.style.setProperty("--bg-terminal", colors.bg_terminal);
|
||||
if (colors.accent_primary) root.style.setProperty("--accent-primary", colors.accent_primary);
|
||||
if (colors.accent_secondary) root.style.setProperty("--accent-secondary", colors.accent_secondary);
|
||||
if (colors.accent_secondary)
|
||||
root.style.setProperty("--accent-secondary", colors.accent_secondary);
|
||||
if (colors.text_primary) root.style.setProperty("--text-primary", colors.text_primary);
|
||||
if (colors.text_secondary) root.style.setProperty("--text-secondary", colors.text_secondary);
|
||||
if (colors.border_color) root.style.setProperty("--border-color", colors.border_color);
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
import UpdateNotification from "$lib/components/UpdateNotification.svelte";
|
||||
|
||||
let initialized = false;
|
||||
let updateNotification: UpdateNotification;
|
||||
let updateNotification: UpdateNotification | undefined = $state(undefined);
|
||||
let achievementPanelOpen = $state(false);
|
||||
let currentCharacterState: CharacterState = $state("idle");
|
||||
let compactModeActive = $state(false);
|
||||
@@ -375,6 +375,9 @@
|
||||
-webkit-mask:
|
||||
linear-gradient(#fff 0 0) content-box,
|
||||
linear-gradient(#fff 0 0);
|
||||
mask:
|
||||
linear-gradient(#fff 0 0) content-box,
|
||||
linear-gradient(#fff 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
mask-composite: exclude;
|
||||
opacity: 0;
|
||||
|
||||
Reference in New Issue
Block a user