diff --git a/README.md b/README.md index 25dcf26..85f526d 100644 --- a/README.md +++ b/README.md @@ -1,149 +1,160 @@ -# crescent - -## A Search Engine and Study Ecosystem for Students - -**Version 1.0, 2025** +# πŸŒ™ crescent β€” the student-first search engine + +

+ Privacy-first β€’ Open-source β€’ Built for learning
+ A calm, ad-free search & study ecosystem for students +

+ +

+ + + + + + + + + + + + +

+ +

+ + + + +

--- -## Overview +## πŸš€ What is crescent? -crescent is a **privacy-focused, open-source search engine** designed specifically for students, researchers, and lifelong learners. It is more than just a search tool: crescent is a fully integrated learning ecosystem that helps students research, organize information, and study efficiently in a safe, distraction-free environment. +**crescent** is a **privacy-first, open-source search engine and study ecosystem** designed specifically for students. -By integrating **free and open educational data sources** such as Wikipedia, Wikidata, OpenLibrary, MathJS, NASA, and other public APIs, crescent enables students to explore knowledge without ads, tracking, accounts, or invasive analytics. +It replaces noisy, ad-driven search engines with a **calm learning environment** where students can: -The project is fully **open-source**, meaning anyone can inspect, modify, and extend it under the **CrescentDB Supreme Protection License (CSPL) v3.3**. crescent is built with student privacy, accessibility, and safety in mind, making it suitable for learners under 18. +- Research topics from trusted educational sources +- Take notes alongside results +- Organize study material +- Learn without being tracked, distracted, or profiled ---- +No ads. +No accounts. +No data collection. -## Core Philosophy - -1. **Student-Centric**: All features are optimized for learning, homework, research, and academic projects. -2. **Privacy First**: crescent does **not collect personal data**, track users, or use third-party analytics. -3. **Open-Source Freedom**: Anyone can study, modify, or contribute to the codebase under the CSPL license. -4. **Seamless Integration**: Search, references, notes, and study tools live in one interface to reduce context switching. -5. **Accessible and Safe**: No ads, no tracking, and a clean interface designed for students of all ages. +Just learning. --- -## Key Features - -### Search & Research - -* **Multi-Source Search**: Access Wikipedia, Wikidata, OpenLibrary, NASA datasets, and other free educational APIs. -* **Fast Reference Lookups**: Retrieve definitions, formulas, summaries, and factual information instantly. -* **Topic Aggregation**: Combine results from multiple sources into a single research view. -* **Smart Suggestions**: Discover related topics and concepts to support deeper learning. - -### Notes & Study Tools - -* **Personal Notes**: Create, edit, and organize notes alongside search results. -* **Bookmarks & Collections**: Save articles, references, and data points for later review. -* **Research Workspaces**: Group notes and searches by subject, topic, or project. -* **Highlight & Annotate**: Mark key information for revision and exam preparation. - -### Educational Utilities +## 🌟 Why crescent exists -* **Math Tools**: Evaluate expressions and formulas using the MathJS API. -* **Science Resources**: Access astronomy imagery and public scientific datasets via NASA APIs. -* **Dictionary & Language Tools**: Look up definitions, meanings, and vocabulary using free Dictionary APIs. -* **Weather & Climate Data**: Study weather patterns and climate data using OpenWeatherMap’s free tier. -* **Current Events**: Research recent news topics using open-access news APIs. +Modern search engines are optimized for: +- ads +- engagement +- profiling users -### Customization & Extensibility +crescent is optimized for: +- **understanding** +- **focus** +- **students** -* **API-Friendly Architecture**: Designed to support additional free and open educational APIs. -* **Open Source**: Developers can add features, improve performance, or extend tools. -* **Student Extensions**: Create scripts or plugins tailored for educational workflows. +It is built to be safe for learners of all ages, including under 18, and to respect privacy by default. --- -## Supported APIs (Free & Open) +## ✨ Features at a glance -| API Name | Purpose | Example Use Case | -| ------------------ | ------------------------------- | ------------------------------------------ | -| Wikipedia API | Encyclopedia articles | Research historical or scientific topics | -| Wikidata API | Structured knowledge | Build charts, timelines, or data summaries | -| OpenLibrary API | Books and authors | Find books for assignments or research | -| NASA APIs | Astronomy and science resources | Access space imagery and datasets | -| OpenWeatherMap API | Weather and climate data | Study climate patterns | -| MathJS API | Math expression evaluation | Calculate formulas or verify solutions | -| Dictionary API | Definitions and vocabulary | Language learning | -| NewsAPI | Current events | Research recent news topics | +### πŸ” Smart Educational Search +- Wikipedia, Wikidata, OpenLibrary, NASA & more +- Unified results across multiple sources +- Instant references, summaries & facts -> All listed APIs provide **free access tiers** suitable for educational and non-commercial use. +### πŸ“ Built-in Study Tools +- Notes linked to searches +- Bookmarks & collections +- Topic-based research workspaces +- Highlight & annotate key information ---- - -## Usage Guide +### 🧠 Learning Utilities +- Math expression evaluation (MathJS) +- Science & astronomy datasets (NASA) +- Dictionary & vocabulary tools +- Weather & climate data +- Current events research -1. **Open crescent** in your browser. -2. **Enter a query** to search across multiple educational sources. -3. **Create notes** and annotate important information. -4. **Organize research** by topic, subject, or project. -5. **Use study tools** such as math evaluation, references, or datasets. -6. **Review or export** saved notes and research. +### 🧩 Fully Open & Extensible +- API-friendly architecture +- Easy to add free educational sources +- Designed for student-built extensions --- -## Student Benefits +## πŸ”Œ Free & Open APIs Used + +| API | What it provides | +|----|------------------| +| Wikipedia | Encyclopedia articles | +| Wikidata | Structured knowledge | +| OpenLibrary | Books & authors | +| NASA APIs | Science & space data | +| MathJS | Math evaluation | +| Dictionary APIs | Vocabulary | +| OpenWeatherMap | Climate data | +| News APIs | Current events | -* Centralized research and study workflow. -* No ads, tracking, or data collection. -* Works across devices (desktop, tablet, mobile). -* Uses trusted, open educational data sources. -* Designed to be safe for under-18 users. +> crescent only uses **free and open-access data sources**. --- -## Contributing +## πŸŽ“ Perfect for -crescent is **fully open-source** and welcomes contributions from students, educators, and developers. +- Students & self-learners +- Homework & assignments +- Research projects +- Exam revision +- Privacy-conscious users -Ways to contribute: +--- -* Add or improve free educational API integrations. -* Suggest or implement new study or research features. -* Fix bugs, optimize performance, or improve accessibility. -* Share documentation, workflows, or templates for students. +## 🀝 Contributing (easy!) -All contributions are licensed under the **CrescentDB Supreme Protection License (CSPL) v3.3, 2025**. +You don’t need to be an expert to contribute. ---- +Ways to help: +- Improve UI or accessibility +- Add a new educational API +- Fix bugs or performance issues +- Improve documentation +- Suggest features students actually need -## Code of Conduct - -* Use crescent responsibly and ethically. -* Do not use the platform for illegal, unsafe, or harmful activities. -* Respect user privacy and the educational focus of the platform. +All contributions are welcome ❀️ --- -## License +## πŸ›‘ Privacy & Safety -Copyright 2025 **luvaary** +- No trackers +- No analytics +- No ads +- No accounts +- No dark patterns -Licensed under the **CrescentDB Supreme Protection License (CSPL) Version 3.3, 2025**. +crescent is designed to be **safe, ethical, and student-friendly**. --- -## Appendix: Student Workflow Examples - -1. **Research Paper Workflow** +## πŸ“„ License - * Search topics using Wikipedia and OpenLibrary. - * Organize sources and notes by subject. - * Review key points and references. +Β© 2025 **luvaary** +Licensed under the **CrescentDB Supreme Protection License (CSPL) v3.3** -2. **Homework Workflow** - - * Use MathJS for calculations and checks. - * Reference open educational resources. - * Save worked solutions for revision. +--- -3. **Exam Study Workflow** +## ⭐ Star the project - * Look up definitions and concepts. - * Bookmark important references. - * Organize material by chapter or topic. +If you believe students deserve better tools, +**please consider starring this repository** β€” it helps crescent reach more learners. +πŸŒ™ diff --git a/css/components.css b/css/components.css index 63d644d..085323b 100644 --- a/css/components.css +++ b/css/components.css @@ -1,9 +1,9 @@ .search-container { width: 100%; - max-width: 700px; + max-width: 720px; padding: 0 2rem; - margin-top: 2rem; - animation: fadeIn 1s ease-out 0.2s backwards; + margin-top: 2.5rem; + animation: fadeIn 0.9s cubic-bezier(.2,.8,.2,1) 0.15s backwards; } .search-box { @@ -13,17 +13,16 @@ .search-input { width: 100%; - padding: 1.25rem 1.5rem; - padding-right: 3.5rem; + padding: 1.3rem 1.6rem; + padding-right: 4rem; font-family: 'DM Sans', sans-serif; font-size: 1rem; border: 1px solid var(--border); - border-radius: 16px; - background: var(--bg-card); - backdrop-filter: blur(10px); + border-radius: 18px; + background: linear-gradient(180deg, var(--bg-card), var(--bg-secondary)); color: var(--text-primary); - transition: all 0.3s ease; - box-shadow: var(--shadow); + box-shadow: 0 10px 30px rgba(0,0,0,0.25), inset 0 1px 0 rgba(255,255,255,0.03); + transition: border-color 0.25s ease, box-shadow 0.25s ease, transform 0.2s ease; } .search-input::placeholder { @@ -32,25 +31,29 @@ .search-input:focus { outline: none; - border-color: var(--accent-soft); - box-shadow: var(--shadow-hover); + border-color: var(--accent); + box-shadow: 0 0 0 4px rgba(var(--accent-rgb), 0.15), 0 18px 40px rgba(0,0,0,0.35); + transform: translateY(-1px); } .search-btn { position: absolute; - right: 8px; + right: 10px; top: 50%; transform: translateY(-50%); - background: none; - border: none; - padding: 0.75rem; + background: var(--bg-secondary); + border: 1px solid var(--border); + padding: 0.6rem; + border-radius: 12px; cursor: pointer; color: var(--text-muted); - transition: color 0.2s ease; + transition: all 0.2s ease; } .search-btn:hover { color: var(--accent); + border-color: var(--accent-soft); + background: var(--bg-card); } .search-btn svg { @@ -62,39 +65,41 @@ display: flex; flex-wrap: wrap; justify-content: center; - gap: 0.5rem; - margin-top: 1.5rem; - animation: fadeIn 1s ease-out 0.4s backwards; + gap: 0.6rem; + margin-top: 1.6rem; + animation: fadeIn 1s cubic-bezier(.2,.8,.2,1) 0.3s backwards; } .api-chip { - padding: 0.5rem 1rem; + padding: 0.55rem 1.1rem; font-size: 0.75rem; + border-radius: 999px; border: 1px solid var(--border); - border-radius: 20px; background: var(--bg-card); color: var(--text-secondary); cursor: pointer; transition: all 0.2s ease; - letter-spacing: 0.02em; + letter-spacing: 0.04em; } .api-chip:hover { background: var(--accent-soft); border-color: var(--accent); color: var(--text-primary); + transform: translateY(-1px); } .api-chip.active { background: var(--accent); border-color: var(--accent); color: white; + box-shadow: 0 6px 20px rgba(var(--accent-rgb), 0.35); } .results-section { width: 100%; - max-width: 900px; - padding: 2rem; + max-width: 960px; + padding: 2.5rem 2rem; margin-top: 2rem; display: none; } @@ -103,14 +108,14 @@ display: flex; justify-content: space-between; align-items: center; - margin-bottom: 1.5rem; + margin-bottom: 1.75rem; padding-bottom: 1rem; border-bottom: 1px solid var(--border); } .results-header h2 { font-family: 'Cormorant Garamond', serif; - font-size: 1.25rem; + font-size: 1.35rem; font-weight: 400; color: var(--text-secondary); } @@ -122,40 +127,35 @@ .results-grid { display: grid; - gap: 1rem; + gap: 1.25rem; } .result-card { - background: var(--bg-card); + position: relative; + padding: 1.6rem 1.6rem 1.7rem; + background: linear-gradient(180deg, var(--bg-card), var(--bg-secondary)); border: 1px solid var(--border); - border-radius: 12px; - padding: 1.5rem; - transition: all 0.3s ease; + border-radius: 16px; cursor: pointer; - animation: slideUp 0.5s ease-out backwards; - position: relative; + animation: slideUp 0.5s cubic-bezier(.2,.8,.2,1) backwards; + transition: transform 0.25s ease, box-shadow 0.25s ease, border-color 0.25s ease; } .result-card:hover { - transform: translateY(-2px); - box-shadow: var(--shadow-hover); + transform: translateY(-3px); border-color: var(--accent-soft); -} - -@keyframes slideUp { - from { opacity: 0; transform: translateY(20px); } - to { opacity: 1; transform: translateY(0); } + box-shadow: 0 20px 50px rgba(0,0,0,0.4); } .result-source { display: inline-flex; align-items: center; gap: 0.5rem; - font-size: 0.7rem; + font-size: 0.65rem; color: var(--text-muted); text-transform: uppercase; - letter-spacing: 0.1em; - margin-bottom: 0.75rem; + letter-spacing: 0.12em; + margin-bottom: 0.8rem; } .source-dot { @@ -164,24 +164,24 @@ border-radius: 50%; } -.source-wikipedia { background: #636363; } +.source-wikipedia { background: #888; } .source-openlib { background: #d4a373; } -.source-dictionary { background: #81c784; } +.source-dictionary { background: #7ecb8a; } .source-nasa { background: #0b3d91; } .result-title { font-family: 'Cormorant Garamond', serif; - font-size: 1.25rem; + font-size: 1.35rem; font-weight: 400; color: var(--text-primary); - margin-bottom: 0.5rem; + margin-bottom: 0.6rem; line-height: 1.4; } .result-excerpt { font-size: 0.9rem; color: var(--text-secondary); - line-height: 1.6; + line-height: 1.65; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; @@ -190,8 +190,8 @@ .result-meta { display: flex; - gap: 1rem; - margin-top: 1rem; + gap: 1.2rem; + margin-top: 1.1rem; font-size: 0.75rem; color: var(--text-muted); } @@ -200,22 +200,23 @@ position: absolute; top: 1rem; right: 1rem; + width: 34px; + height: 34px; + border-radius: 50%; background: var(--bg-secondary); border: 1px solid var(--border); - border-radius: 50%; - width: 32px; - height: 32px; display: flex; align-items: center; justify-content: center; cursor: pointer; - font-size: 1rem; transition: all 0.2s ease; } .bookmark-btn:hover { background: var(--accent); - transform: scale(1.1); + border-color: var(--accent); + color: white; + transform: scale(1.12); } .loading { @@ -230,14 +231,27 @@ } .loader { - width: 40px; - height: 40px; + width: 42px; + height: 42px; border: 2px solid var(--border); border-top-color: var(--accent); border-radius: 50%; - animation: spin 1s linear infinite; + animation: spin 0.9s linear infinite; +} + +@keyframes slideUp { + from { + opacity: 0; + transform: translateY(18px); + } + to { + opacity: 1; + transform: translateY(0); + } } @keyframes spin { - to { transform: rotate(360deg); } + to { + transform: rotate(360deg); + } } diff --git a/css/features.css b/css/features.css index 40e5be4..5067af6 100644 --- a/css/features.css +++ b/css/features.css @@ -1,147 +1,171 @@ .notes-panel { position: fixed; - left: 20px; + left: 24px; top: 120px; - width: 280px; - background: var(--bg-card); - padding: 1.5rem; - border-radius: 12px; - box-shadow: var(--shadow); + width: 300px; + background: linear-gradient(180deg, var(--bg-card), var(--bg-secondary)); + padding: 1.6rem; + border-radius: 16px; border: 1px solid var(--border); - backdrop-filter: blur(10px); + box-shadow: 0 20px 50px rgba(0,0,0,0.4); + backdrop-filter: blur(14px); + transition: transform 0.25s ease, box-shadow 0.25s ease; +} + +.notes-panel:hover { + transform: translateY(-2px); + box-shadow: 0 26px 60px rgba(0,0,0,0.5); } .notes-panel h3 { font-family: 'Cormorant Garamond', serif; - font-size: 1.1rem; + font-size: 1.15rem; margin-bottom: 1rem; color: var(--text-primary); } .notes-panel textarea { width: 100%; - height: 200px; + height: 210px; border: 1px solid var(--border); - border-radius: 8px; - padding: 0.75rem; + border-radius: 12px; + padding: 0.85rem; font-family: inherit; font-size: 0.9rem; resize: vertical; background: var(--bg-secondary); color: var(--text-primary); + transition: border-color 0.2s ease, box-shadow 0.2s ease; +} + +.notes-panel textarea:focus { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 3px rgba(var(--accent-rgb), 0.15); } .notes-actions { display: flex; - gap: 0.5rem; - margin-top: 0.75rem; + gap: 0.6rem; + margin-top: 0.9rem; } .notes-actions button { flex: 1; - padding: 0.5rem; + padding: 0.55rem; background: var(--accent); color: white; border: none; - border-radius: 6px; + border-radius: 10px; cursor: pointer; font-size: 0.85rem; - transition: all 0.2s ease; + transition: transform 0.2s ease, box-shadow 0.2s ease, background 0.2s ease; } .notes-actions button:hover { background: var(--accent-soft); color: var(--text-primary); + transform: translateY(-1px); + box-shadow: 0 6px 18px rgba(var(--accent-rgb), 0.3); } .timer-widget { position: fixed; - bottom: 20px; - right: 20px; - background: var(--bg-card); - padding: 1.5rem; - border-radius: 12px; - box-shadow: var(--shadow); + bottom: 24px; + right: 24px; + min-width: 220px; + background: linear-gradient(180deg, var(--bg-card), var(--bg-secondary)); + padding: 1.6rem; + border-radius: 16px; border: 1px solid var(--border); - backdrop-filter: blur(10px); - min-width: 200px; + box-shadow: 0 20px 50px rgba(0,0,0,0.4); + backdrop-filter: blur(14px); text-align: center; + transition: transform 0.25s ease, box-shadow 0.25s ease; +} + +.timer-widget:hover { + transform: translateY(-2px); + box-shadow: 0 26px 60px rgba(0,0,0,0.5); } .timer-widget h3 { font-family: 'Cormorant Garamond', serif; - font-size: 1rem; + font-size: 1.05rem; margin-bottom: 1rem; color: var(--text-primary); } .timer-display { font-family: 'Cormorant Garamond', serif; - font-size: 2.5rem; + font-size: 2.7rem; color: var(--accent); margin-bottom: 1rem; } .timer-controls { display: flex; - gap: 0.5rem; + gap: 0.6rem; } .timer-controls button { flex: 1; - padding: 0.5rem; + padding: 0.55rem; background: var(--accent); color: white; border: none; - border-radius: 6px; + border-radius: 10px; cursor: pointer; font-size: 0.75rem; - transition: all 0.2s ease; + transition: transform 0.2s ease, box-shadow 0.2s ease, background 0.2s ease; } .timer-controls button:hover { background: var(--accent-soft); color: var(--text-primary); + transform: translateY(-1px); + box-shadow: 0 6px 18px rgba(var(--accent-rgb), 0.3); } .timer-controls button:disabled { - opacity: 0.5; + opacity: 0.45; cursor: not-allowed; } .history-panel { position: fixed; - left: 20px; - bottom: 20px; - width: 280px; - max-height: 400px; - background: var(--bg-card); - padding: 1.5rem; - border-radius: 12px; - box-shadow: var(--shadow); + left: 24px; + bottom: 24px; + width: 300px; + max-height: 420px; + background: linear-gradient(180deg, var(--bg-card), var(--bg-secondary)); + padding: 1.6rem; + border-radius: 16px; border: 1px solid var(--border); - backdrop-filter: blur(10px); + box-shadow: 0 20px 50px rgba(0,0,0,0.4); + backdrop-filter: blur(14px); overflow-y: auto; } .history-panel h3 { font-family: 'Cormorant Garamond', serif; - font-size: 1.1rem; + font-size: 1.15rem; margin-bottom: 1rem; color: var(--text-primary); } .history-item { - padding: 0.75rem; + padding: 0.85rem; background: var(--bg-secondary); - border-radius: 8px; - margin-bottom: 0.5rem; + border-radius: 12px; + margin-bottom: 0.6rem; cursor: pointer; - transition: all 0.2s ease; + transition: transform 0.2s ease, background 0.2s ease; } .history-item:hover { background: var(--accent-soft); + transform: translateY(-1px); } .history-query { @@ -163,15 +187,15 @@ .clear-history-btn { width: 100%; - padding: 0.5rem; + padding: 0.55rem; background: var(--text-muted); color: white; border: none; - border-radius: 6px; + border-radius: 10px; cursor: pointer; font-size: 0.75rem; - margin-top: 0.75rem; - transition: all 0.2s ease; + margin-top: 0.85rem; + transition: background 0.2s ease; } .clear-history-btn:hover { @@ -180,38 +204,39 @@ .bookmarks-panel { position: fixed; - right: 20px; + right: 24px; top: 120px; - width: 300px; - max-height: 500px; - background: var(--bg-card); - padding: 1.5rem; - border-radius: 12px; - box-shadow: var(--shadow); + width: 320px; + max-height: 520px; + background: linear-gradient(180deg, var(--bg-card), var(--bg-secondary)); + padding: 1.6rem; + border-radius: 16px; border: 1px solid var(--border); - backdrop-filter: blur(10px); + box-shadow: 0 20px 50px rgba(0,0,0,0.4); + backdrop-filter: blur(14px); overflow-y: auto; } .bookmarks-panel h3 { font-family: 'Cormorant Garamond', serif; - font-size: 1.1rem; + font-size: 1.15rem; margin-bottom: 1rem; color: var(--text-primary); } .bookmark-item { display: flex; - gap: 0.5rem; - padding: 0.75rem; + gap: 0.6rem; + padding: 0.85rem; background: var(--bg-secondary); - border-radius: 8px; - margin-bottom: 0.5rem; - transition: all 0.2s ease; + border-radius: 12px; + margin-bottom: 0.6rem; + transition: transform 0.2s ease, background 0.2s ease; } .bookmark-item:hover { background: var(--accent-soft); + transform: translateY(-1px); } .bookmark-content { @@ -239,19 +264,19 @@ .bookmark-excerpt { font-size: 0.75rem; color: var(--text-secondary); - line-height: 1.4; + line-height: 1.45; } .bookmark-remove { background: none; border: none; color: var(--text-muted); - font-size: 1.5rem; + font-size: 1.4rem; cursor: pointer; - transition: color 0.2s ease; + transition: color 0.2s ease, transform 0.2s ease; padding: 0; - width: 24px; - height: 24px; + width: 26px; + height: 26px; display: flex; align-items: center; justify-content: center; @@ -259,6 +284,7 @@ .bookmark-remove:hover { color: var(--accent); + transform: scale(1.15); } .bookmarks-empty { @@ -269,15 +295,15 @@ .clear-bookmarks-btn { width: 100%; - padding: 0.5rem; + padding: 0.55rem; background: var(--text-muted); color: white; border: none; - border-radius: 6px; + border-radius: 10px; cursor: pointer; font-size: 0.75rem; - margin-top: 0.75rem; - transition: all 0.2s ease; + margin-top: 0.85rem; + transition: background 0.2s ease; } .clear-bookmarks-btn:hover { @@ -287,35 +313,19 @@ .notification { position: fixed; top: 100px; - right: -300px; + right: -320px; background: var(--accent); color: white; - padding: 1rem 1.5rem; - border-radius: 8px; - box-shadow: var(--shadow-hover); + padding: 1rem 1.6rem; + border-radius: 12px; + box-shadow: 0 18px 40px rgba(0,0,0,0.4); z-index: 1000; - transition: right 0.3s ease; + transition: right 0.35s cubic-bezier(.2,.8,.2,1); font-size: 0.9rem; } .notification.show { - right: 20px; -} - -body.dark-mode { - --bg-primary: #1a1a1a; - --bg-secondary: #2a2a2a; - --bg-card: rgba(40, 40, 40, 0.9); - --text-primary: #ffffff; - --text-secondary: #cccccc; - --text-muted: #888888; - --border: rgba(255, 255, 255, 0.1); - --shadow: 0 4px 24px rgba(0, 0, 0, 0.3); - --shadow-hover: 0 8px 32px rgba(0, 0, 0, 0.4); -} - -body.dark-mode .orb { - opacity: 0.2; + right: 24px; } @media (max-width: 1200px) { @@ -324,7 +334,7 @@ body.dark-mode .orb { .bookmarks-panel { display: none; } - + .timer-widget { bottom: 80px; } diff --git a/css/main.css b/css/main.css index fd45589..85fc044 100644 --- a/css/main.css +++ b/css/main.css @@ -1,402 +1,127 @@ :root { --bg-primary: #f7f5f2; --bg-secondary: #fffef9; - --bg-card: rgba(255, 255, 255, 0.7); - --text-primary: #2c2c2c; - --text-secondary: #6b6b6b; - --text-muted: #9a9a9a; - --accent: #c4a87c; - --accent-soft: #e8dcc8; - --border: rgba(0, 0, 0, 0.06); - --shadow: 0 4px 24px rgba(0, 0, 0, 0.04); - --shadow-hover: 0 8px 32px rgba(0, 0, 0, 0.08); + --bg-card: rgba(255, 255, 255, 0.75); + --text-primary: #1f1f1f; + --text-secondary: #5f5f5f; + --text-muted: #8a8a8a; + --accent: #c2a36b; + --accent-soft: #efe5d6; + --border: rgba(0, 0, 0, 0.05); + --shadow: 0 10px 40px rgba(0, 0, 0, 0.06); + --shadow-hover: 0 16px 60px rgba(0, 0, 0, 0.1); --spotify-green: #1db954; } -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -html { - scroll-behavior: smooth; -} - body { font-family: 'DM Sans', sans-serif; background: var(--bg-primary); color: var(--text-primary); min-height: 100vh; overflow-x: hidden; - position: relative; } body::before { - content: ''; - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - pointer-events: none; - opacity: 0.03; - background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E"); - z-index: 9999; + opacity: 0.025; } .orb { - position: fixed; - border-radius: 50%; - filter: blur(80px); - opacity: 0.4; - pointer-events: none; - animation: float 20s ease-in-out infinite; -} - -.orb-1 { - width: 400px; - height: 400px; - background: linear-gradient(135deg, #e8dcc8 0%, #f0e6d8 100%); - top: -100px; - right: -100px; - animation-delay: 0s; -} - -.orb-2 { - width: 300px; - height: 300px; - background: linear-gradient(135deg, #d4e5f7 0%, #e8f0f8 100%); - bottom: -50px; - left: -50px; - animation-delay: -7s; -} - -.orb-3 { - width: 250px; - height: 250px; - background: linear-gradient(135deg, #e8d4d4 0%, #f5e8e8 100%); - top: 50%; - left: 30%; - animation-delay: -14s; -} - -@keyframes float { - 0%, 100% { transform: translate(0, 0) scale(1); } - 25% { transform: translate(20px, -20px) scale(1.05); } - 50% { transform: translate(-10px, 10px) scale(0.95); } - 75% { transform: translate(-20px, -10px) scale(1.02); } + filter: blur(90px); + opacity: 0.35; + will-change: transform; } nav { - position: fixed; - top: 0; - left: 0; - right: 0; - z-index: 100; - padding: 1.5rem 3rem; - display: flex; - justify-content: space-between; - align-items: center; - background: rgba(247, 245, 242, 0.8); - backdrop-filter: blur(20px); - border-bottom: 1px solid var(--border); -} - -.logo { - display: flex; - align-items: center; - gap: 0.75rem; - text-decoration: none; - color: var(--text-primary); -} - -.logo-icon { - width: 32px; - height: 32px; - position: relative; -} - -.crescent-moon { - fill: none; - stroke: var(--accent); - stroke-width: 2; - animation: glow 4s ease-in-out infinite; -} - -@keyframes glow { - 0%, 100% { opacity: 0.8; } - 50% { opacity: 1; } -} - -.logo-text { - font-family: 'Cormorant Garamond', serif; - font-size: 1.5rem; - font-weight: 400; - letter-spacing: 0.1em; -} - -.nav-center { - display: flex; - align-items: center; - gap: 2rem; -} - -.clock { - font-family: 'Cormorant Garamond', serif; - font-size: 1.1rem; - color: var(--text-secondary); - letter-spacing: 0.05em; - min-width: 100px; - text-align: center; -} - -.nav-links { - display: flex; - gap: 2rem; - list-style: none; -} - -.nav-links a { - text-decoration: none; - color: var(--text-secondary); - font-size: 0.85rem; - font-weight: 400; - letter-spacing: 0.05em; - transition: color 0.3s ease; - position: relative; -} - -.nav-links a::after { - content: ''; - position: absolute; - bottom: -4px; - left: 0; - width: 0; - height: 1px; - background: var(--accent); - transition: width 0.3s ease; -} - -.nav-links a:hover { - color: var(--text-primary); -} - -.nav-links a:hover::after { - width: 100%; -} - -main { - padding-top: 120px; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; -} - -.hero { - text-align: center; - padding: 4rem 2rem; - animation: fadeIn 1s ease-out; -} - -@keyframes fadeIn { - from { opacity: 0; transform: translateY(20px); } - to { opacity: 1; transform: translateY(0); } + background: rgba(247, 245, 242, 0.85); + backdrop-filter: blur(24px); } .hero h1 { - font-family: 'Cormorant Garamond', serif; - font-size: 3.5rem; - font-weight: 300; - letter-spacing: 0.15em; - margin-bottom: 0.5rem; - color: var(--text-primary); + font-size: clamp(2.4rem, 6vw, 3.6rem); + letter-spacing: 0.18em; } .hero p { - font-size: 1rem; - color: var(--text-secondary); - font-weight: 300; - letter-spacing: 0.05em; + max-width: 520px; + margin: 0 auto; } -.empty-state { - text-align: center; - padding: 4rem 2rem; - color: var(--text-muted); -} - -.empty-state svg { - width: 80px; - height: 80px; - margin-bottom: 1.5rem; - opacity: 0.3; -} - -.empty-state p { - font-size: 0.9rem; -} - -footer { - text-align: center; - padding: 3rem 2rem; - margin-top: auto; - color: var(--text-muted); - font-size: 0.75rem; -} - -footer p + p { - margin-top: 0.5rem; -} - -footer a { - color: var(--text-secondary); - text-decoration: none; - transition: color 0.2s ease; -} - -footer a:hover { - color: var(--accent); -} - -.spotify-tab { - position: fixed; - right: 0; - top: 50%; - transform: translateY(-50%); - z-index: 90; -} - -.spotify-toggle { +.notes-panel, +.history-panel, +.bookmarks-panel, +.timer-widget { background: var(--bg-card); - border: 1px solid var(--border); - border-right: none; - border-radius: 12px 0 0 12px; - padding: 1rem 0.75rem; - cursor: pointer; - display: flex; - flex-direction: column; - align-items: center; - gap: 0.5rem; - transition: all 0.3s ease; + backdrop-filter: blur(18px) saturate(1.1); box-shadow: var(--shadow); + transition: transform 0.25s ease, box-shadow 0.25s ease; } -.spotify-toggle:hover { - background: var(--bg-secondary); - padding-left: 1rem; -} - -.spotify-icon { - width: 24px; - height: 24px; - fill: var(--spotify-green); -} - -.spotify-toggle span { - writing-mode: vertical-rl; - text-orientation: mixed; - font-size: 0.7rem; - color: var(--text-muted); - letter-spacing: 0.1em; -} - -.spotify-panel { - position: fixed; - right: -320px; - top: 0; - width: 320px; - height: 100vh; - background: var(--bg-card); - backdrop-filter: blur(20px); - border-left: 1px solid var(--border); - padding: 6rem 1.5rem 2rem; - transition: right 0.4s cubic-bezier(0.4, 0, 0.2, 1); - z-index: 85; - overflow-y: auto; -} - -.spotify-panel.open { - right: 0; -} - -.spotify-panel h3 { - font-family: 'Cormorant Garamond', serif; - font-size: 1.25rem; - font-weight: 400; - margin-bottom: 1.5rem; - color: var(--text-primary); +.notes-panel:hover, +.history-panel:hover, +.bookmarks-panel:hover, +.timer-widget:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-hover); } -.spotify-placeholder { - background: linear-gradient(135deg, #1a1a1a, #2a2a2a); - border-radius: 12px; - padding: 1.5rem; - text-align: center; +button { + will-change: transform; } -.spotify-placeholder p { - color: #b3b3b3; - font-size: 0.85rem; - margin-bottom: 1rem; +button:hover { + transform: translateY(-1px); } -.spotify-connect { - background: var(--spotify-green); - color: white; - border: none; - padding: 0.75rem 1.5rem; - border-radius: 20px; - font-size: 0.85rem; +.notification { font-weight: 500; - cursor: pointer; - transition: transform 0.2s ease, opacity 0.2s ease; -} - -.spotify-connect:hover { - transform: scale(1.05); - opacity: 0.9; + border-radius: 10px; } -.close-spotify { - position: absolute; - top: 1.5rem; - right: 1.5rem; - background: none; - border: none; - font-size: 1.5rem; - color: var(--text-muted); - cursor: pointer; - transition: color 0.2s ease; +.spotify-toggle { + backdrop-filter: blur(18px); } -.close-spotify:hover { - color: var(--text-primary); +.spotify-panel { + backdrop-filter: blur(24px); } -@media (max-width: 768px) { - nav { - padding: 1rem 1.5rem; +@media (prefers-reduced-motion: reduce) { + * { + animation: none !important; + transition: none !important; } +} - .nav-links { +@media (max-width: 1200px) { + .notes-panel, + .history-panel, + .bookmarks-panel { display: none; } +} +@media (max-width: 768px) { .hero h1 { - font-size: 2.5rem; + letter-spacing: 0.12em; } - .search-container { - padding: 0 1rem; + .spotify-tab { + display: none; } +} - .api-categories { - padding: 0 1rem; - } +body.dark-mode { + --bg-primary: #0e0f12; + --bg-secondary: #15171c; + --bg-card: rgba(25, 27, 34, 0.85); + --text-primary: #f2f2f2; + --text-secondary: #c7c7c7; + --text-muted: #8a8a8a; + --border: rgba(255, 255, 255, 0.08); + --shadow: 0 10px 40px rgba(0, 0, 0, 0.45); + --shadow-hover: 0 16px 70px rgba(0, 0, 0, 0.6); +} - .results-section { - padding: 1rem; - } - } +body.dark-mode .orb { + opacity: 0.18; +} diff --git a/index.html b/index.html index f2fe8f6..2ca965a 100644 --- a/index.html +++ b/index.html @@ -3,95 +3,100 @@ - Crescent - Calm Search for Students + Crescent β€” Calm Search for Students + + - - + + + + -
-
-
+ + +
-
-
+

Notes

+
- - + +
-
+

Study Timer

-
25:00
+
25:00
- - - + + +
-
+

Search History

- +
-
+

Bookmarks

- +
-
- +
+

Now Playing

- +

Connect your Spotify account to see what's playing

- +
@@ -103,21 +108,23 @@

Crescent

- +
@@ -132,16 +139,16 @@

Crescent

Results

0 results
- +
- +
- + @@ -151,11 +158,11 @@

Results

- + diff --git a/js/app.js b/js/app.js index 72442d9..ab5fafb 100644 --- a/js/app.js +++ b/js/app.js @@ -3,60 +3,40 @@ import { updateClock, toggleSpotify, fadeInPage } from './ui.js'; document.addEventListener('DOMContentLoaded', () => { console.log('πŸŒ™ Crescent initialized'); - + updateClock(); setInterval(updateClock, 1000); - + initSearch(); - setupEventListeners(); - + fadeInPage(); - loadFeatures(); }); function setupEventListeners() { const searchBtn = document.getElementById('searchBtn'); - if (searchBtn) { - searchBtn.addEventListener('click', performSearch); - } - const spotifyToggle = document.getElementById('spotifyToggle'); const closeSpotify = document.getElementById('closeSpotify'); - - if (spotifyToggle) { - spotifyToggle.addEventListener('click', toggleSpotify); - } - - if (closeSpotify) { - closeSpotify.addEventListener('click', toggleSpotify); - } + + searchBtn?.addEventListener('click', performSearch); + spotifyToggle?.addEventListener('click', toggleSpotify); + closeSpotify?.addEventListener('click', toggleSpotify); } async function loadFeatures() { - try { - const { initNotes } = await import('./features/notes.js'); - initNotes(); - } catch (e) {} - - try { - const { initHistory } = await import('./features/history.js'); - initHistory(); - } catch (e) {} - - try { - const { initDarkMode } = await import('./features/darkmode.js'); - initDarkMode(); - } catch (e) {} - - try { - const { initTimer } = await import('./features/timer.js'); - initTimer(); - } catch (e) {} - - try { - const { initBookmarks } = await import('./features/bookmarks.js'); - initBookmarks(); - } catch (e) {} + const features = [ + { path: './features/notes.js', init: 'initNotes' }, + { path: './features/history.js', init: 'initHistory' }, + { path: './features/darkmode.js', init: 'initDarkMode' }, + { path: './features/timer.js', init: 'initTimer' }, + { path: './features/bookmarks.js', init: 'initBookmarks' }, + ]; + + for (const f of features) { + try { + const module = await import(f.path); + module[f.init]?.(); + } catch {} + } } diff --git a/js/search.js b/js/search.js index 9f382c1..445f316 100644 --- a/js/search.js +++ b/js/search.js @@ -4,12 +4,9 @@ export const apiConfigs = { class: 'source-wikipedia', search: async (query) => { try { - const response = await fetch( - `https://en.wikipedia.org/api/rest_v1/page/summary/${encodeURIComponent(query)}` - ); - if (!response.ok) return []; - - const data = await response.json(); + const res = await fetch(`https://en.wikipedia.org/api/rest_v1/page/summary/${encodeURIComponent(query)}`); + if (!res.ok) return []; + const data = await res.json(); return [{ title: data.title, excerpt: data.extract, @@ -18,23 +15,16 @@ export const apiConfigs = { url: data.content_urls?.desktop?.page, meta: { type: 'Article' } }]; - } catch (error) { - console.error('Wikipedia API error:', error); - return []; - } + } catch { return []; } } }, - openlib: { name: 'Open Library', class: 'source-openlib', search: async (query) => { try { - const response = await fetch( - `https://openlibrary.org/search.json?q=${encodeURIComponent(query)}&limit=5` - ); - const data = await response.json(); - + const res = await fetch(`https://openlibrary.org/search.json?q=${encodeURIComponent(query)}&limit=5`); + const data = await res.json(); return (data.docs || []).map(book => ({ title: book.title, excerpt: `By ${book.author_name?.join(', ') || 'Unknown'} Β· First published ${book.first_publish_year || 'N/A'}`, @@ -43,52 +33,36 @@ export const apiConfigs = { url: `https://openlibrary.org${book.key}`, meta: { type: 'Book' } })); - } catch (error) { - console.error('Open Library API error:', error); - return []; - } + } catch { return []; } } }, - dictionary: { name: 'Dictionary', class: 'source-dictionary', search: async (query) => { try { - const response = await fetch( - `https://api.dictionaryapi.dev/api/v2/entries/en/${encodeURIComponent(query)}` - ); - if (!response.ok) return []; - - const data = await response.json(); + const res = await fetch(`https://api.dictionaryapi.dev/api/v2/entries/en/${encodeURIComponent(query)}`); + if (!res.ok) return []; + const data = await res.json(); return data.map(entry => ({ title: entry.word, - excerpt: entry.meanings?.map(m => - `(${m.partOfSpeech}) ${m.definitions?.[0]?.definition}` - ).join(' Β· ') || '', + excerpt: entry.meanings?.map(m => `(${m.partOfSpeech}) ${m.definitions?.[0]?.definition}`).join(' Β· ') || '', source: 'Dictionary', sourceClass: 'source-dictionary', url: entry.sourceUrls?.[0], meta: { type: 'Definition', phonetic: entry.phonetic } })); - } catch (error) { - console.error('Dictionary API error:', error); - return []; - } + } catch { return []; } } }, - nasa: { name: 'NASA', class: 'source-nasa', search: async (query) => { try { - const response = await fetch( - `https://images-api.nasa.gov/search?q=${encodeURIComponent(query)}&media_type=image` - ); - if (!response.ok) return []; - - const data = await response.json(); + const res = await fetch(`https://images-api.nasa.gov/search?q=${encodeURIComponent(query)}&media_type=image`); + if (!res.ok) return []; + const data = await res.json(); return (data.collection?.items || []).slice(0, 5).map(item => ({ title: item.data?.[0]?.title || 'Untitled', excerpt: item.data?.[0]?.description?.substring(0, 200) || '', @@ -97,10 +71,7 @@ export const apiConfigs = { url: item.links?.[0]?.href, meta: { type: 'Image', date: item.data?.[0]?.date_created?.split('T')[0] } })); - } catch (error) { - console.error('NASA API error:', error); - return []; - } + } catch { return []; } } } }; @@ -116,13 +87,14 @@ export function initSearch() { }); }); - document.getElementById('searchInput').addEventListener('keypress', (e) => { + const input = document.getElementById('searchInput'); + input?.addEventListener('keypress', e => { if (e.key === 'Enter') performSearch(); }); } export async function performSearch() { - const query = document.getElementById('searchInput').value.trim(); + const query = document.getElementById('searchInput')?.value.trim(); if (!query) return; const resultsSection = document.getElementById('resultsSection'); @@ -138,13 +110,9 @@ export async function performSearch() { try { let results = []; - if (selectedAPI === 'all') { - const promises = Object.values(apiConfigs).map(api => - api.search(query).catch(() => []) - ); - const allResults = await Promise.all(promises); - results = allResults.flat(); + const all = await Promise.all(Object.values(apiConfigs).map(api => api.search(query).catch(() => []))); + results = all.flat(); } else if (apiConfigs[selectedAPI]) { results = await apiConfigs[selectedAPI].search(query); } @@ -152,49 +120,33 @@ export async function performSearch() { loading.classList.remove('active'); resultsCount.textContent = `${results.length} result${results.length !== 1 ? 's' : ''}`; - if (results.length === 0) { - resultsGrid.innerHTML = ` -
-

No results found. Try a different search term or source.

-
- `; + if (!results.length) { + resultsGrid.innerHTML = `

No results found.

`; return; } - results.forEach((result, index) => { + results.forEach((res, i) => { const card = document.createElement('div'); card.className = 'result-card'; - card.style.animationDelay = `${index * 0.1}s`; + card.style.animationDelay = `${i * 0.08}s`; card.innerHTML = ` -
- - ${result.source} -
-

${result.title}

-

${result.excerpt}

+
${res.source}
+

${res.title}

+

${res.excerpt}

- ${result.meta?.type || 'Result'} - ${result.meta?.phonetic ? `${result.meta.phonetic}` : ''} - ${result.meta?.date ? `${result.meta.date}` : ''} + ${res.meta?.type || 'Result'} + ${res.meta?.phonetic ? `${res.meta.phonetic}` : ''} + ${res.meta?.date ? `${res.meta.date}` : ''}
- + `; - card.onclick = () => { - if (result.url) window.open(result.url, '_blank'); - }; + card.onclick = () => res.url && window.open(res.url, '_blank'); resultsGrid.appendChild(card); }); - - if (typeof window.addToSearchHistory === 'function') { - window.addToSearchHistory(query, results.length); - } - } catch (error) { + if (typeof window.addToSearchHistory === 'function') window.addToSearchHistory(query, results.length); + } catch { loading.classList.remove('active'); - resultsGrid.innerHTML = ` -
-

Something went wrong. Please try again.

-
- `; + resultsGrid.innerHTML = `

Something went wrong. Please try again.

`; } } diff --git a/js/ui.js b/js/ui.js index 349ee89..d9f33ea 100644 --- a/js/ui.js +++ b/js/ui.js @@ -1,19 +1,27 @@ export function updateClock() { + const clock = document.getElementById('clock'); + if (!clock) return; + const now = new Date(); - const hours = now.getHours().toString().padStart(2, '0'); - const minutes = now.getMinutes().toString().padStart(2, '0'); - document.getElementById('clock').textContent = `${hours}:${minutes}`; + clock.textContent = now.toLocaleTimeString([], { + hour: '2-digit', + minute: '2-digit', + hour12: false + }); } export function toggleSpotify() { const panel = document.getElementById('spotifyPanel'); + if (!panel) return; panel.classList.toggle('open'); } export function fadeInPage() { - document.body.style.opacity = '0'; - document.body.style.transition = 'opacity 0.5s ease'; - setTimeout(() => { - document.body.style.opacity = '1'; - }, 100); + requestAnimationFrame(() => { + document.body.style.opacity = '0'; + document.body.style.transition = 'opacity 0.5s ease'; + requestAnimationFrame(() => { + document.body.style.opacity = '1'; + }); + }); }