5 examples

Redundant code

Repeated code unnecessarily bloating the codebase.

[ FAQ1 ]

What is redundant code?

Redundant code refers to repeated or unnecessary segments of logic or functionality that exist multiple times within an application. This duplication can occur due to copy-and-paste practices, lack of refactoring, or overlooked opportunities to reuse existing logic. Redundant code complicates maintenance, increases the risk of inconsistencies, and makes updates or bug fixes more error-prone. Adhering to principles like DRY (Don't Repeat Yourself) helps developers avoid redundant logic and improves code quality.
[ FAQ2 ]

How to remove redundant code

To remove redundant code, perform regular code reviews to identify repeated or unnecessary logic, clearly marking areas that require refactoring. Consolidate duplicate functionality into reusable functions, methods, or components, simplifying maintenance and enhancing consistency. Utilize automated code analysis tools and linters to detect redundancy proactively, making code cleanup more systematic. Embrace the DRY principle by encouraging developers to abstract common logic, eliminating duplication and streamlining codebases over time.
diff block
+/**
+ * NOTE: This class has helpers for observing a kafka producer which is useful for validating
+ * messages are created as expected.
+ *
+ * It does not mock the producer itself, for that see `producer.mock.ts`
+ */
+
+import { KafkaProducerWrapper, TopicMessage } from '../../../src/kafka/producer'
+import { parseJSON } from '../../../src/utils/json-parse'
+
+export type ParsedTopicMessage = {
+ topic: TopicMessage['topic']
+ messages: {
+ key: TopicMessage['messages'][number]['key']
+ value: Record<string, any> | null
+ headers?: TopicMessage['messages'][number]['headers']
+ }[]
+}
+
+export type DecodedKafkaMessage = {
+ topic: TopicMessage['topic']
+ key?: TopicMessage['messages'][number]['key']
+ value: Record<string, unknown>
+ headers?: TopicMessage['messages'][number]['headers']
+}
+
+export class KafkaProducerObserver {
+ public readonly produceSpy: jest.SpyInstance<
+ Promise<void>,
+ Parameters<typeof KafkaProducerWrapper.prototype.produce>
+ >
+
+ constructor(private producer: KafkaProducerWrapper) {
+ // Spy on the methods we need
+ this.produceSpy = jest.spyOn(producer, 'produce')
+ }
+
+ public getProducedMessages() {
+ return this.produceSpy.mock.calls.reduce((acc, call) => {
+ return acc.concat([
+ {
+ topic: call[0].topic,
+ messages: [
+ {
+ key: call[0].key,
+ value: call[0].value,
+ headers: call[0].headers,
+ },
+ ],
+ },
+ ])
+ }, [] as TopicMessage[])
+ }
+
+ public getParsedQueuedMessages(): ParsedTopicMessage[] {
+ const allMessages = this.getProducedMessages()
+ return allMessages.map((topicMessage) => ({
+ topic: topicMessage.topic,
+ messages: topicMessage.messages.map((message) => ({
+ key: message.key?.toString() ?? null,
+ value: message.value ? parseJSON(message.value.toString()) : null,
+ headers: message.headers,
+ })),
+ }))
+ }
+
+ public getProducedKafkaMessages(): DecodedKafkaMessage[] {
+ const queuedMessages = this.getParsedQueuedMessages()
+
+ const result: DecodedKafkaMessage[] = []
+
+ for (const topicMessage of queuedMessages) {
+ for (const message of topicMessage.messages) {
+ result.push({
+ topic: topicMessage.topic,
+ key: message.key,
+ value: message.value ?? {},
+ headers: message.headers,
+ })
+ }
+ }
+
+ return result
+ }
+
+ public getProducedKafkaMessagesWithHeaders(): DecodedKafkaMessage[] {
+ const queuedMessages = this.getParsedQueuedMessages()
+
+ const result: DecodedKafkaMessage[] = []
+
+ for (const topicMessage of queuedMessages) {
+ for (const message of topicMessage.messages) {
+ result.push({
+ topic: topicMessage.topic,
+ key: message.key,
+ value: message.value ?? {},
+ headers: message.headers,
+ })
+ }
+ }
+
+ return result
+ }
Greptile
greptile
logic: getProducedKafkaMessagesWithHeaders is identical to getProducedKafkaMessages - appears to be redundant code that should be removed
diff block
+#!/usr/bin/env python3
+"""
+CoinGecko Integration Update Script
+This script handles updating and synchronizing data from CoinGecko API.
+"""
+
+import os
+import sys
+import json
+import time
+import logging
+import requests
+from datetime import datetime
+
+# Constants
+JSON_INDENT = 2
+DEFAULT_TOP_COINS_LIMIT = 250
+DEFAULT_PAGE = 1
+REQUEST_TIMEOUT = 30
+API_VERSION = "1.0.0"
+ERROR_EXIT_CODE = 1
+
+# Configure logging
+logging.basicConfig(
+ level=logging.INFO,
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
+ handlers=[logging.StreamHandler(sys.stdout)]
+)
+logger = logging.getLogger("coingecko-integration")
+
+# Configuration
+COINGECKO_API_BASE = "https://api.coingecko.com/api/v3"
+COINGECKO_API_KEY = os.environ.get("COINGECKO_API_KEY", "")
+if not COINGECKO_API_KEY:
+ logger.warning("COINGECKO_API_KEY not set - running in free tier mode with rate limits")
+DATA_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "data", "coingecko")
+
+
+def get_headers():
+ """
+ Returns HTTP headers for CoinGecko API requests, including an API key if set.
+ """
+ headers = {
+ "Accept": "application/json"
+ }
+ if COINGECKO_API_KEY:
+ headers["X-CG-Pro-API-Key"] = COINGECKO_API_KEY
+ return headers
+
+
+def respect_rate_limits(response):
+ """Check response headers and respect rate limits"""
+ remaining = response.headers.get('X-RateLimit-Remaining')
+ if remaining and int(remaining) < 5:
+ reset_time = response.headers.get('X-RateLimit-Reset')
+ if reset_time:
+ sleep_time = max(int(reset_time) - time.time(), 0) + 1
+ logger.info(f"Rate limit approaching, sleeping for {sleep_time} seconds")
+ time.sleep(sleep_time)
+ else:
+ logger.info("Rate limit approaching, sleeping for 10 seconds")
+ time.sleep(10)
+ else:
+ # Add a small delay between requests regardless
+ time.sleep(0.5)
+
+def fetch_coins_list():
+ """
+ Fetches the complete list of coins from the CoinGecko API and saves it to a local JSON file.
+
+ Returns:
+ The list of coins as parsed from the API response, or None if the request fails.
+ """
+ url = f"{COINGECKO_API_BASE}/coins/list"
+ logger.info(f"Fetching coins list from {url}")
+
+ try:
+ response = requests.get(url, headers=get_headers())
+ try:
+ response.raise_for_status()
+ except requests.exceptions.RequestException as e:
+ logger.error(
+ f"Error fetching coins list: {str(e)}, "
+ f"Status Code: {e.response.status_code if hasattr(e, 'response') else 'N/A'}, "
+ f"Response: {e.response.text if hasattr(e, 'response') else 'N/A'}, "
+ f"URL: {url}"
+ )
+
+def fetch_coins_list():
+ raise
+ coins = response.json()
+
+ # Save to file
+ output_file = os.path.join(DATA_DIR, "coins_list.json")
+ with open(output_file, "w") as f:
+ json.dump(coins, f, indent=JSON_INDENT)
+
+ logger.info(f"Saved {len(coins)} coins to {output_file}")
+ return coins
+ except Exception as e:
+ logger.error(f"Error fetching coins list: {e}")
+ return None
+
+def fetch_global_data():
+ """
+ Fetches global cryptocurrency market data from the CoinGecko API.
+
+ Retrieves overall market statistics, saves the data to 'global.json' in the data directory,
+ and returns the parsed JSON data. Returns None if the request fails.
+ """
+ url = f"{COINGECKO_API_BASE}/global"
+ logger.info(f"Fetching global market data from {url}")
+
+ try:
+ response = requests.get(url, headers=get_headers())
+ response.raise_for_status()
+ respect_rate_limits(response)
+ global_data = response.json()
+
+ # Save to file
+ output_file = os.path.join(DATA_DIR, "global.json")
+ with open(output_file, "w") as f:
+ json.dump(global_data, f, indent=JSON_INDENT)
+
+ logger.info(f"Saved global market data to {output_file}")
+ return global_data
+ except Exception as e:
+ except Exception as e:
+ logger.error(
+ f"Error fetching top coins: {str(e)}, "
+ f"Status Code: {e.response.status_code if hasattr(e, 'response') else 'N/A'}, "
+ f"Response: {e.response.text if hasattr(e, 'response') else 'N/A'}, "
+ f"URL: {url}"
+ )
+ return None
+ return None
+
+def fetch_top_coins(limit=DEFAULT_TOP_COINS_LIMIT):
+ """
+ Fetches detailed market data for the top cryptocurrencies by market capitalization.
+
+ Retrieves market data for the top `limit` coins from the CoinGecko API, including price change percentages over 1 hour, 24 hours, and 7 days. Saves the resulting data to a JSON file in the data directory. Returns the data as a list of dictionaries, or None if the request fails.
+
+ Args:
+ limit: The number of top coins to fetch (default is 250).
+
+ Returns:
+ A list of dictionaries containing market data for each coin, or None on failure.
+ """
+ url = f"{COINGECKO_API_BASE}/coins/markets"
+ params = {
+ "vs_currency": "usd",
+ "order": "market_cap_desc",
+ "per_page": limit,
+ "page": DEFAULT_PAGE,
+ "sparkline": False,
+ "price_change_percentage": "1h,24h,7d"
+ }
+
+ logger.info(f"Fetching top {limit} coins from {url}")
+
+ try:
+ response = requests.get(url, headers=get_headers(), params=params, timeout=REQUEST_TIMEOUT)
+ response.raise_for_status()
+ respect_rate_limits(response)
+ top_coins = response.json()
+
+ # Save to file
+ output_file = os.path.join(DATA_DIR, f"top_{limit}_coins.json")
+ logger.info(f"Fetching top {limit} coins from {url}")
+
Greptile
greptile
I'll help you fix the duplicate logging and request code block in the `fetch_top_coins` function. The issue is that lines 170-177 duplicate the previous request code. Here's the fix:
suggested fix
logger.info(f"Fetching top {limit} coins from {url}")
    
     try:
         response = requests.get(url, headers=get_headers(), params=params, timeout=REQUEST_TIMEOUT)
         response.raise_for_status()
         respect_rate_limits(response)
         top_coins = response.json()
        
         # Save to file
         output_file = os.path.join(DATA_DIR, f"top_{limit}_coins.json")
         with open(output_file, "w") as f:
This suggestion: 1. Removes the duplicate logging statement 2. Removes the duplicate request code block 3. Keeps the original request logic intact 4. Maintains proper indentation 5. Continues with the file saving operation The fix removes the redundant code while preserving the core functionality of fetching and saving the top coins data.
diff block
+import { IconBrackets, IconInfo, IconServer } from '@posthog/icons'
+import { IconArrowLeft, IconEllipsis } from '@posthog/icons'
+import { useActions, useValues } from 'kea'
+import { CopyToClipboardInline } from 'lib/components/CopyToClipboard'
+import { DatabaseTableTree } from 'lib/components/DatabaseTableTree/DatabaseTableTree'
+import { Resizer } from 'lib/components/Resizer/Resizer'
+import { LemonButton } from 'lib/lemon-ui/LemonButton'
+import { LemonMenu } from 'lib/lemon-ui/LemonMenu'
+import { LemonTabs } from 'lib/lemon-ui/LemonTabs'
+import { useEffect, useState } from 'react'
+import { viewLinkLogic } from 'scenes/data-warehouse/viewLinkLogic'
+import { Scene } from 'scenes/sceneTypes'
+
+import { Sidebar } from '~/layout/navigation-3000/components/Sidebar'
+import { navigation3000Logic } from '~/layout/navigation-3000/navigationLogic'
+import { SidebarNavbarItem } from '~/layout/navigation-3000/types'
+import { variablesLogic } from '~/queries/nodes/DataVisualization/Components/Variables/variablesLogic'
+
+import { editorSceneLogic } from '../editorSceneLogic'
+import { editorSidebarLogic } from '../editorSidebarLogic'
+import { editorSizingLogic } from '../editorSizingLogic'
+import { QueryInfo } from './QueryInfo'
+import { QueryVariables } from './QueryVariables'
+
+export const EditorSidebar = ({
+ sidebarRef,
+ codeEditorKey,
+}: {
+ sidebarRef: React.RefObject<HTMLDivElement>
+ codeEditorKey: string
+}): JSX.Element => {
+ const { sidebarOverlayOpen } = useValues(editorSceneLogic)
+ const { sidebarWidth } = useValues(editorSizingLogic)
+ const { variablesForInsight } = useValues(variablesLogic)
+ const { setSidebarWidth } = useActions(navigation3000Logic)
+ const editorSizingLogicProps = editorSizingLogic.props
+
+ useEffect(() => {
+ setSidebarWidth(sidebarWidth)
+ }, [sidebarWidth])
+
+ // State to track active tab
+ const [activeTab, setActiveTab] = useState('tab1')
+
+ // Define tabs with icons
+ const tabs = [
+ {
+ key: 'tab1',
+ label: (
+ <div className="flex justify-center px-2">
+ <IconServer className="text-xl" />
+ </div>
+ ),
+ },
+ {
+ key: 'query_variables',
+ label: (
+ <div className="flex justify-center px-2 relative">
+ <IconBrackets className="text-xl" />
+ {variablesForInsight.length > 0 && (
+ <div className="absolute -top-1 -right-1 flex items-center justify-center bg-danger rounded-full text-white text-[9px] h-3 w-3 min-w-3">
+ {variablesForInsight.length}
+ </div>
+ )}
+ </div>
+ ),
+ },
+ {
+ key: 'query_info',
+ label: (
+ <div className="flex justify-center px-2">
+ <IconInfo className="text-xl" />
+ </div>
+ ),
+ },
+ ]
+
+ // Render the corresponding component based on active tab
+ const renderTabContent = (): JSX.Element => {
+ switch (activeTab) {
+ case 'tab1':
+ return <DatabaseExplorer isOpen={sidebarOverlayOpen} />
Greptile
greptile
logic: Case statement uses 'tab1' key but falls through to default with same return value - redundant code path
diff block
+import SwiftUI
+import Foundation
+import Charts
+
+// MARK: - Models
+
+struct EmissionsDetail: Decodable {
+ let Percentage: Double
+ let Emissions: Double
+}
+
+struct EmissionsBreakdown: Decodable {
+ let Production: EmissionsDetail
+ let Transportation: EmissionsDetail
+ let ProductUse: EmissionsDetail?
+ let EndOfLifeProcessing: EmissionsDetail?
+
+ // A computed property to unify keys
+ var breakdownEntries: [(String, EmissionsDetail)] {
+ var result: [(String, EmissionsDetail)] = []
+ if let production = Production as EmissionsDetail? {
+ result.append(("Production", production))
+ }
+ if let transportation = Transportation as EmissionsDetail? {
+ result.append(("Transportation", transportation))
+ }
+ // Attempt to use the "Product Use" key
+ if let productUse = ProductUse {
+ result.append(("Product Use", productUse))
+ } else if let productUse = ProductUse {
+ result.append(("Product Use", productUse))
Greptile
greptile
logic: This else-if condition is checking the same variable (ProductUse) that was already checked in the if statement above. This creates redundant code that will never execute.
diff block
+/* Font Imports */
+@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap');
+@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
+@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
+@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700&display=swap');
+@import url('https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500;600&display=swap');
+@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&display=swap');
+
+/*
+ Midnight Ocean Theme for Hydra Launcher
+ A modern dark theme with oceanic accents
+
+ Created with 💙 for the Hydra community
+ My GitHub: https://github.com/Vitanho98
+*/
+
+/* ========== ROOT VARIABLES ========== */
+:root {
+ /* Primary Colors */
+ --midnight-primary: #1a1f2c;
+ --midnight-secondary: #2a3142;
+ --midnight-secondary-light: #3a4152;
+ --midnight-accent: #253e52;
+ --midnight-accent-hover: #364f63;
+
+ /* Text Colors */
+ --midnight-text-primary: #f5f7fa;
+ --midnight-text-secondary: #cbd5e1;
+ --midnight-text-muted: #94a3b8;
+
+ /* Status Colors */
+ --midnight-success: #4caf50;
+ --midnight-warning: #ff9800;
+ --midnight-error: #f44336;
+ --midnight-info: #2196f3;
+
+ /* Border and Shadow */
+ --midnight-border: rgba(255, 255, 255, 0.1);
+ --midnight-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+
+ /* Container Colors */
+ --midnight-container-bg: #1e2433;
+ --midnight-settings-bg: #232836;
+ --midnight-hover-bg: rgba(100, 181, 246, 0.1);
+
+ /* Font Families */
+ --midnight-font-primary: 'Outfit', 'Poppins', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+ --midnight-font-secondary: 'Outfit', 'DM Sans', 'Inter', system-ui, sans-serif;
+ --midnight-font-mono: 'Fira Code', 'JetBrains Mono', monospace;
+
+ /* Font Weights */
+ --font-light: 300;
+ --font-regular: 400;
+ --font-medium: 500;
+ --font-semibold: 600;
+ --font-bold: 700;
+
+ /* Transitions */
+ --transition-fast: 0.2s ease;
+ --transition-base: 0.3s ease;
+ --transition-slow: 0.4s ease;
+
+ /* Border Radius */
+ --radius-sm: 4px;
+ --radius-md: 6px;
+ --radius-lg: 8px;
+ --radius-xl: 12px;
+ --radius-full: 100px;
+
+ /* Window Controls */
+ --title-height: 32px;
+ --title-button-width: 46px;
+ --title-icon-size: 16px;
+}
+
+/* ========== GLOBAL STYLES ========== */
+body {
+ background-color: var(--midnight-primary);
+ font-size: 14px;
+ background-image: linear-gradient(45deg,
+ var(--midnight-primary) 25%,
+ var(--midnight-settings-bg) 25%,
+ var(--midnight-settings-bg) 50%,
+ var(--midnight-primary) 50%,
+ var(--midnight-primary) 75%,
+ var(--midnight-settings-bg) 75%,
+ var(--midnight-settings-bg) 100%
+ );
+ background-size: 56.57px 56.57px;
+ min-height: 100vh;
+ margin: 0;
+ padding: 0;
+ overflow: hidden;
+ font-family: var(--midnight-font-primary);
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ color: var(--midnight-text-secondary);
+}
+
+/* ========== TYPOGRAPHY ========== */
+body,
+input,
+button,
+select,
+textarea {
+ font-family: var(--midnight-font-primary);
+}
+
+.settings__title,
+.container__title,
+.header__title,
+.modal__title,
+.game-card__title {
+ font-family: var(--midnight-font-secondary);
+ font-weight: var(--font-semibold);
+ letter-spacing: -0.02em;
+}
+
+.settings__section-title,
+.theme-preview-title,
+.modal__header {
+ color: var(--midnight-text-primary);
+ font-weight: 600;
+ letter-spacing: -0.01em;
+}
+
+.settings__item-label,
+.dropdown__item,
+.tab__item {
+ color: var(--midnight-text-secondary);
+ font-weight: 500;
+ letter-spacing: 0.01em;
+}
+
+/* ========== LAYOUT COMPONENTS ========== */
+/* Title Bar */
+.title-bar {
+ background-color: var(--midnight-primary);
+}
+
+.title-bar__section {
+ display: flex;
+ align-items: center;
+ height: 100%;
+}
+
+.title-bar__section--left {
+ padding-left: 12px;
+ gap: 8px;
+}
+
+.title-bar__section--right {
+ margin-left: auto;
+}
+
+.title-bar__app-icon {
+ width: var(--title-icon-size);
+ height: var(--title-icon-size);
+ -webkit-app-region: no-drag;
+}
+
+.title-bar__title {
+ font-size: 13px;
+ font-weight: var(--font-medium);
+ color: var(--midnight-text-secondary);
+ margin: 0;
+ padding: 0 8px;
+}
+
+.title-bar__button {
+ width: var(--title-button-width);
+ height: 100%;
+ border: none;
+ background: transparent;
+ color: var(--midnight-text-secondary);
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ -webkit-app-region: no-drag;
+ transition: var(--transition-fast);
+}
+
+.title-bar__button:hover {
+ background-color: var(--midnight-secondary);
+}
+
+.title-bar__button--close:hover {
+ background-color: var(--midnight-error);
+ color: var(--midnight-text-primary);
+}
+
+.title-bar__button-icon {
+ width: var(--title-icon-size);
+ height: var(--title-icon-size);
+ pointer-events: none;
+}
+
+/* ========== CONTAINERS ========== */
+/* App Container */
+.app-container {
+ width: 100%;
+ height: calc(100vh - var(--title-height));
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+ background-color: rgba(26, 31, 44, 0.95);
+ backdrop-filter: blur(10px);
+}
+
+.container__content {
+ background-color: var(--midnight-container-bg);
+ border-radius: 0px;
+ position: relative;
+ height: auto;
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ backdrop-filter: blur(10px);
+ border-left: 0px;
+}
+
+/* Settings Container */
+.settings__container {
+ background-color: transparent;
+ padding: 0px;
+ border-radius: 0px;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+}
+
+.settings__content {
+ flex: 1;
+ background-color: var(--midnight-container-bg);
+ padding: 1.5rem 2rem;
+ display: flex;
+ flex-direction: column;
+ border-radius: 0px;
+ gap: 1.5rem;
+ position: relative;
+ overflow-y: auto;
+ border: 0px;
+}
+
+/* ========== COMPONENTS ========== */
+/* Buttons */
+.button {
+ background-color: var(--midnight-accent);
+ border: 1px solid transparent;
+ padding: 0.75rem 1.5rem;
+ border-radius: 6px;
+ transition: all 0.3s ease;
+ cursor: pointer;
+ overflow: hidden;
+ font-weight: var(--font-medium);
+ letter-spacing: 0.01em;
+}
+
+.button:hover {
+ background-color: var(--midnight-accent-hover);
+ transform: translateY(-1px);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+}
+
+/* Forms */
+input, select, textarea {
+ background-color: var(--midnight-secondary);
+ border: 1px solid var(--midnight-border);
+ color: var(--midnight-text-primary);
+ padding: 0.75rem 1rem;
+ border-radius: 6px;
+ transition: all 0.3s ease;
+}
+
+input:focus, select:focus, textarea:focus {
+ border-color: var(--midnight-accent);
+ outline: none;
+ box-shadow: 0 0 0 2px rgba(100, 181, 246, 0.2);
+}
+
+/* ========== UTILITIES ========== */
+/* Scrollbar */
+::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+}
+
+::-webkit-scrollbar-track {
+ background: var(--midnight-primary);
+}
+
+::-webkit-scrollbar-thumb {
+ background: var(--midnight-secondary);
+ border-radius: 4px;
+}
+
+::-webkit-scrollbar-thumb:hover {
+ background: var(--midnight-accent);
+}
+
+/* Global Styles */
+body {
+ background-color: var(--midnight-primary);
+ font-size: 14px;
+ background-image: linear-gradient(45deg,
+ var(--midnight-primary) 25%,
+ var(--midnight-settings-bg) 25%,
+ var(--midnight-settings-bg) 50%,
+ var(--midnight-primary) 50%,
+ var(--midnight-primary) 75%,
+ var(--midnight-settings-bg) 75%,
+ var(--midnight-settings-bg) 100%
+ );
+ background-size: 56.57px 56.57px;
+ min-height: 100vh;
+ margin: 0;
+ padding: 0;
+ overflow: hidden;
+ font-family: var(--midnight-font-primary);
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ color: var(--midnight-text-secondary);
+}
+
+/* Title Bar */
+.title-bar {
+ background-color: var(--midnight-primary);
+}
+
+.title-bar__section {
+ display: flex;
+ align-items: center;
+ height: 100%;
+}
+
+.title-bar__section--left {
+ padding-left: 12px;
+ gap: 8px;
+}
+
+.title-bar__section--right {
+ margin-left: auto;
+}
+
+.title-bar__app-icon {
+ width: var(--title-icon-size);
+ height: var(--title-icon-size);
+ -webkit-app-region: no-drag;
+}
+
+.title-bar__title {
+ font-size: 13px;
+ font-weight: var(--font-medium);
+ color: var(--midnight-text-secondary);
+ margin: 0;
+ padding: 0 8px;
+}
+
+.title-bar__button {
+ width: var(--title-button-width);
+ height: 100%;
+ border: none;
+ background: transparent;
+ color: var(--midnight-text-secondary);
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ -webkit-app-region: no-drag;
+ transition: var(--transition-fast);
+}
+
+.title-bar__button:hover {
+ background-color: var(--midnight-secondary);
+}
+
+.title-bar__button--close:hover {
+ background-color: var(--midnight-error);
+ color: var(--midnight-text-primary);
+}
+
+.title-bar__button-icon {
+ width: var(--title-icon-size);
+ height: var(--title-icon-size);
+ pointer-events: none;
+}
Greptile
greptile
style: This title-bar section is duplicated from lines 137-197. Remove this redundant code to keep the stylesheet clean.