Compare commits

..

1 commit

Author SHA1 Message Date
partisan
0d8b5adf67 idk 2024-08-25 21:28:18 +02:00
32 changed files with 6077 additions and 5893 deletions

View file

@ -1,22 +1,14 @@
<p align="center">
<img src="https://weforgecode.xyz/Spitfire/Branding/raw/branch/main/icon5.svg" alt="Logo" width="64" height="64">
</p>
# Spitfire Browser Website
<p align="center" style="font-size: 32px;">
<strong>Spitfire Browser Website</strong>
</p>
<p align="center">
Unlike some other browser sites flexing with their 98.8% TypeScript (yikes), i keep it cool with minimal JavaScript just enough to get the job done.
</p>
<p align="center">
Spitfire Browser's website is built without Next.js, TypeScript, and Tailwind CSS or any other BS.
</p>
Spitfire Browser is a fast, secure, and elegant web browser built on Firefox. This repository contains the source code for the Spitfire Browser's official website.
## TO-DO:
- [ ] Add browser download/screenshots for this web browser website (optional)
- [ ] Add screenshots
- [ ] Add search-engine test
- [ ] Add working downloads
- [X] Add blog/updates
- [ ] Add config file
### Blog entries should be fromated this way:
@ -43,10 +35,9 @@ Vivamus luctus egestas leo. Phasellus faucibus molestie nisl. Etiam commodo dui
...
```
### Based on HTML template:
[Stellar](https://html5up.net/stellar) by HTML5 UP
### Based on:
Stellar by HTML5 UP
html5up.net | @ajlkn
### Licence:

2
go.mod
View file

@ -1,4 +1,4 @@
module spitfire-browser-website
module my-web
go 1.18

133
main.go
View file

@ -6,6 +6,7 @@ import (
"flag"
"fmt"
"html/template"
"io/ioutil"
"log"
"net/http"
"os"
@ -33,11 +34,6 @@ const (
discordWebhookURL = "YOUR_DISCORD_WEBHOOK_URL"
)
/*
Spitfire Browser by Internet Addict (https://spitfirebrowser.com)
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
*/
type Blog struct {
Name string
Entries []BlogEntry
@ -105,17 +101,14 @@ func main() {
log.Fatalf("Error getting blogs: %v", err)
}
// Start the periodic notification checker
go startNotificationChecker(10 * time.Minute)
// Start watching for changes in the data directory
go watchForChanges(dataDir)
// Serve static files (CSS, JS, etc.) from the /static directory
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(staticDir))))
// Custom handler to serve only directories and their contents under /news-assets/
http.Handle("/news-assets/", http.StripPrefix("/news-assets/", http.HandlerFunc(serveDirectoriesOnly)))
// Serve files in the /data/news/ directory under /news-assets/
http.Handle("/news-assets/", http.StripPrefix("/news-assets/", http.FileServer(http.Dir(dataDir+"/news/"))))
// Serve downloads.html at /downloads
http.HandleFunc("/download", func(w http.ResponseWriter, r *http.Request) {
@ -127,6 +120,8 @@ func main() {
renderTemplate(w, "download-linux.html", nil)
})
http.HandleFunc("/suggestions", handleSuggestions)
// Route for generating the RSS feed for all blogs
http.HandleFunc("/rss", func(w http.ResponseWriter, r *http.Request) {
siteURL := fmt.Sprintf("http://%s", r.Host) // or you can use a fixed base URL
@ -192,15 +187,6 @@ func main() {
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil))
}
func startNotificationChecker(interval time.Duration) {
ticker := time.NewTicker(interval)
defer ticker.Stop()
for range ticker.C {
checkAndSendNotifications()
}
}
func renderTemplate(w http.ResponseWriter, tmpl string, data interface{}) {
tmplPath := filepath.Join(templateDir, tmpl)
t, err := template.ParseFiles(tmplPath)
@ -221,41 +207,6 @@ func renderIndex(w http.ResponseWriter) {
renderTemplate(w, "index.html", nil)
}
// Helper function to add loading="lazy" to all img tags
func injectLazyLoading(htmlContent string) string {
// Split the content by <img tags to process them individually
parts := strings.Split(htmlContent, "<img")
// Start with the first part which is before any <img tag
modifiedContent := parts[0]
// Iterate over the remaining parts
for _, part := range parts[1:] {
// Find the closing bracket of the img tag
endOfTag := strings.Index(part, ">")
if endOfTag == -1 {
// If no closing bracket is found, add the remaining part as is
modifiedContent += "<img" + part
continue
}
// Extract the actual <img tag
imgTag := part[:endOfTag]
// Check if loading="lazy" is already present
if !strings.Contains(imgTag, "loading=") {
// Insert loading="lazy" before the closing of the img tag
imgTag = " loading=\"lazy\"" + imgTag
}
// Rebuild the full content with the modified img tag
modifiedContent += "<img" + imgTag + part[endOfTag:]
}
return modifiedContent
}
// For redering HTML Blogs
func renderBlogEntry(w http.ResponseWriter, r *http.Request, blogName string, entryNumber int) {
var blog Blog
for _, b := range blogs {
@ -289,32 +240,34 @@ func renderBlogEntry(w http.ResponseWriter, r *http.Request, blogName string, en
}
}
// Convert .md to HTML
htmlContent := blackfriday.Run([]byte(entry.Content))
// Double check "/news-assets/" in URL of images
htmlContent = bytes.ReplaceAll(htmlContent, []byte("src=\"./"), []byte(fmt.Sprintf("src=\"/news-assets/%d/", entryNumber)))
// Apply lazy loading to the generated HTML content
htmlContentWithLazyLoading := injectLazyLoading(string(htmlContent))
pageData := PageData{
Title: entry.Title,
Date: entry.Date.Format("2006-01-02 15:04"),
Desc: entry.Description,
Author: entry.Author,
Content: template.HTML(htmlContentWithLazyLoading),
Content: template.HTML(htmlContent),
PrevLink: prevLink,
NextLink: nextLink,
}
renderTemplate(w, "news.html", pageData)
// Handle notifications if they haven't been sent yet
if !entry.Notified {
sendNotifications(entry)
entry.Notified = true
saveNotifiedEntry(entry.Number)
}
}
func getBlogs(dir string) ([]Blog, error) {
var blogs []Blog
files, err := os.ReadDir(dir)
files, err := ioutil.ReadDir(dir)
if err != nil {
return nil, err
}
@ -335,7 +288,7 @@ func getBlogs(dir string) ([]Blog, error) {
func getBlogEntries(dir string) (Blog, error) {
var entries []BlogEntry
files, err := os.ReadDir(dir)
files, err := ioutil.ReadDir(dir)
if err != nil {
return Blog{}, err
}
@ -364,7 +317,7 @@ func getBlogEntries(dir string) (Blog, error) {
}
func parseMarkdownFile(path string) (BlogEntry, error) {
content, err := os.ReadFile(path)
content, err := ioutil.ReadFile(path)
if err != nil {
return BlogEntry{}, err
}
@ -482,59 +435,6 @@ func watchForChanges(dir string) {
select {}
}
// serveDirectoriesOnly handles requests and only serves directories and their contents.
func serveDirectoriesOnly(w http.ResponseWriter, r *http.Request) {
requestedPath := filepath.Join(dataDir, "news", r.URL.Path)
// Check if the requested path is a directory
fileInfo, err := os.Stat(requestedPath)
if err != nil {
http.NotFound(w, r) // If the path doesn't exist, return a 404
return
}
// Block access to any files at the root level or the .git directory
if isRootLevel(requestedPath) || isRestrictedDirectory(requestedPath) {
http.NotFound(w, r) // Block access to root-level files like .md and the .git directory
return
}
// Block access to any .md files at the root level
if isRootLevel(requestedPath) && strings.HasSuffix(requestedPath, ".md") {
http.NotFound(w, r) // Block access to root-level .md files
return
}
// If the path is a directory, serve its contents
if fileInfo.IsDir() {
http.FileServer(http.Dir(requestedPath)).ServeHTTP(w, r)
return
}
// Serve the file within the subdirectory
http.FileServer(http.Dir(filepath.Join(dataDir, "news"))).ServeHTTP(w, r)
}
// isRootLevel checks if the file is at the root level of /news-assets/
func isRootLevel(path string) bool {
relPath, err := filepath.Rel(filepath.Join(dataDir, "news"), path)
if err != nil {
return false
}
// The relative path should not contain any slashes if it's at the root level
return !strings.Contains(relPath, string(os.PathSeparator))
}
// isRestrictedDirectory checks if the path is within a restricted directory like .git
func isRestrictedDirectory(path string) bool {
// Normalize the path
cleanPath := filepath.Clean(path)
// Check if the path contains .git directory or other restricted directories
return strings.Contains(cleanPath, string(os.PathSeparator)+".git") || strings.HasSuffix(cleanPath, ".git")
}
func handleFileChange(path string, isNew bool) {
if filepath.Ext(path) == ".md" {
creationTimesM.Lock()
@ -546,7 +446,6 @@ func handleFileChange(path string, isNew bool) {
dir := filepath.Dir(path)
blogName := filepath.Base(dir)
updateBlogEntries(blogName, path)
checkAndSendNotifications()
}
}

52
printing.go Normal file
View file

@ -0,0 +1,52 @@
package main
import (
"fmt"
"time"
)
var LogLevel = 1
// printDebug logs debug-level messages when LogLevel is set to 4.
func printDebug(format string, args ...interface{}) {
if LogLevel >= 4 {
logMessage("DEBUG", format, args...)
}
}
// printInfo logs info-level messages when LogLevel is set to 3 or higher.
func printInfo(format string, args ...interface{}) {
if LogLevel >= 3 {
logMessage("INFO", format, args...)
}
}
// printWarn logs warning-level messages when LogLevel is set to 2 or higher.
func printWarn(format string, args ...interface{}) {
if LogLevel >= 2 {
logMessage("WARN", format, args...)
}
}
// printErr logs error-level messages regardless of LogLevel.
func printErr(format string, args ...interface{}) {
if LogLevel >= 1 {
logMessage("ERROR", format, args...)
}
}
// printMessage logs messages without a specific log level (e.g., general output).
func printMessage(format string, args ...interface{}) {
logMessage("", format, args...)
}
// logMessage handles the actual logging logic without using the default logger's timestamp.
func logMessage(level string, format string, args ...interface{}) {
timestamp := time.Now().Format("2006-01-02 15:04:05")
message := fmt.Sprintf(format, args...)
if level != "" {
fmt.Printf("[%s %s] %s\n", timestamp, level, message)
} else {
fmt.Printf("[%s] %s\n", timestamp, message)
}
}

63
rss.go
View file

@ -3,11 +3,10 @@ package main
import (
"fmt"
"net/http"
"strings"
"time"
"github.com/gorilla/feeds"
"github.com/russross/blackfriday/v2"
"github.com/russross/blackfriday/v2" // Import the Markdown library
)
func generateAtomFeed(w http.ResponseWriter, blogs []Blog, siteURL string) {
@ -19,50 +18,30 @@ func generateAtomFeed(w http.ResponseWriter, blogs []Blog, siteURL string) {
Created: time.Now(),
}
// Add self link
feed.Link = &feeds.Link{Href: fmt.Sprintf("%s/rss", siteURL), Rel: "self"}
for _, blog := range blogs {
for _, entry := range blog.Entries {
// Convert Markdown content to HTML
htmlContent := blackfriday.Run([]byte(entry.Content))
// Ensure all image paths are absolute URLs (Idiot proofing)
absoluteContent := strings.ReplaceAll(string(htmlContent), "src=\"/", fmt.Sprintf("src=\"%s/", siteURL))
// Ensure unique and stable ID
entryID := fmt.Sprintf("%s/%s/%d", siteURL, blog.Name, entry.Number)
// Create a summary if needed (using the first 200 characters of the content, for example)
summary := entry.Description
if summary == "" {
if len(entry.Content) > 200 {
summary = entry.Content[:200] + "..."
} else {
summary = entry.Content
}
}
feed.Items = append(feed.Items, &feeds.Item{
Title: entry.Title,
Link: &feeds.Link{Href: entryID, Rel: "alternate"},
Description: summary, // This can be used as the summary
Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/%d", siteURL, blog.Name, entry.Number), Rel: "alternate"},
Description: entry.Description, // This can be used as the summary
Author: &feeds.Author{Name: entry.Author},
Id: entryID,
Id: fmt.Sprintf("%s/%s/%d", siteURL, blog.Name, entry.Number),
Updated: entry.Date,
Content: absoluteContent,
Content: string(htmlContent),
})
}
}
// Generate Atom feed with the correct content-type and charset
w.Header().Set("Content-Type", "application/atom+xml; charset=UTF-8")
atom, err := feed.ToAtom()
if err != nil {
http.Error(w, "Error generating Atom feed", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/atom+xml")
w.Write([]byte(atom))
}
@ -75,47 +54,27 @@ func generateBlogAtomFeed(w http.ResponseWriter, blog Blog, siteURL string) {
Created: time.Now(),
}
// Add self link
feed.Link = &feeds.Link{Href: fmt.Sprintf("%s/%s/rss", siteURL, blog.Name), Rel: "self"}
for _, entry := range blog.Entries {
// Convert Markdown content to HTML
htmlContent := blackfriday.Run([]byte(entry.Content))
// Ensure all image paths are absolute URLs (Idiot proofing)
absoluteContent := strings.ReplaceAll(string(htmlContent), "src=\"/", fmt.Sprintf("src=\"%s/", siteURL))
// Ensure unique and stable ID
entryID := fmt.Sprintf("%s/%s/%d", siteURL, blog.Name, entry.Number)
// Create a summary if needed (using the first 200 characters of the content, for example)
summary := entry.Description
if summary == "" {
if len(entry.Content) > 200 {
summary = entry.Content[:200] + "..."
} else {
summary = entry.Content
}
}
feed.Items = append(feed.Items, &feeds.Item{
Title: entry.Title,
Link: &feeds.Link{Href: entryID, Rel: "alternate"},
Description: summary, // This can be used as the summary
Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/%d", siteURL, blog.Name, entry.Number), Rel: "alternate"},
Description: entry.Description, // This can be used as the summary
Author: &feeds.Author{Name: entry.Author},
Id: entryID,
Id: fmt.Sprintf("%s/%s/%d", siteURL, blog.Name, entry.Number),
Updated: entry.Date,
Content: absoluteContent,
Content: string(htmlContent),
})
}
// Generate Atom feed with the correct content-type and charset
w.Header().Set("Content-Type", "application/atom+xml; charset=UTF-8")
atom, err := feed.ToAtom()
if err != nil {
http.Error(w, "Error generating Atom feed", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/atom+xml")
w.Write([]byte(atom))
}

21
run.bat
View file

@ -1,21 +0,0 @@
@echo off
REM Default values
set PORT=8080
REM Parse command-line arguments
:parse_args
if "%~1"=="" goto run_app
if "%~1"=="-p" (
set PORT=%~2
shift
shift
goto parse_args
) else (
echo Unknown parameter passed: %~1
exit /b 1
)
:run_app
REM Run the Go application with the parsed flags
go run discord.go rss.go telegram.go save.go main.go -p=%PORT%

2
run.sh
View file

@ -13,4 +13,4 @@ while [[ "$#" -gt 0 ]]; do
done
# Run the Go application with the parsed flags
go run discord.go rss.go telegram.go save.go main.go -p=$PORT
go run printing.go suggestions.go discord.go rss.go telegram.go save.go main.go -p=$PORT

34
save.go
View file

@ -2,49 +2,35 @@ package main
import (
"encoding/json"
"io/ioutil"
"log"
"os"
"time"
)
func loadNotifiedEntries() {
file, err := os.Open(notifiedFilePath)
data, err := ioutil.ReadFile(notifiedFilePath)
if err != nil {
if os.IsNotExist(err) {
return
return // No file, no entries notified yet
}
log.Fatalf("Error loading notified entries: %v", err)
log.Fatalf("Error reading notified entries file: %v", err)
}
defer file.Close()
err = json.NewDecoder(file).Decode(&notifiedEntries)
err = json.Unmarshal(data, &notifiedEntries)
if err != nil {
log.Fatalf("Error decoding notified entries: %v", err)
log.Fatalf("Error parsing notified entries file: %v", err)
}
}
func saveNotifiedEntry(entryNumber int) {
notifiedEntries[entryNumber] = true
file, err := os.Create(notifiedFilePath)
data, err := json.Marshal(notifiedEntries)
if err != nil {
log.Fatalf("Error saving notified entries: %v", err)
log.Fatalf("Error serializing notified entries: %v", err)
}
defer file.Close()
err = json.NewEncoder(file).Encode(notifiedEntries)
err = ioutil.WriteFile(notifiedFilePath, data, 0644)
if err != nil {
log.Fatalf("Error encoding notified entries: %v", err)
}
}
func checkAndSendNotifications() {
for _, blog := range blogs {
for _, entry := range blog.Entries {
if !entry.Notified && !time.Now().Before(entry.Date) {
sendNotifications(entry)
entry.Notified = true
saveNotifiedEntry(entry.Number)
}
}
log.Fatalf("Error writing notified entries file: %v", err)
}
}

View file

@ -1,51 +1,13 @@
/* Global settings for images and videos */
img, video {
img {
display: block;
width: 85%;
height: auto;
margin: 20px auto; /* Centers the image/video horizontally and adds space above and below */
border-radius: 10px; /* Add rounded corners */
width: 100%;
}
/* Exclude images inside the .icons class from global settings */
ul.icons img {
display: inline; /* Override display: block */
width: auto; /* Override width: 100% */
border-radius: 0; /* Remove rounded corners */
margin-top: 0; /* Remove top margin */
margin-bottom: 0; /* Remove bottom margin */
}
/* Align blog text and links */
.align-blog p, a, em {
text-align: left;
font-size: 1em;
line-height: 1.6;
}
/* Increase margin top for all headings */
.align-blog h1,
.align-blog h2,
.align-blog h3,
.align-blog h4,
.align-blog h5,
.align-blog h6 {
margin-top: 30px; /* Adjust this value to increase space above headings */
}
/* Icons specific styles */
ul.icons {
cursor: default;
list-style: none;
padding-left: 0;
}
ul.icons li {
display: inline-block;
padding: 0 0.65em 0 0;
}
ul.icons li:last-child {
padding-right: 0 !important;
}
/* .align-blog h1, h2, h3, h4, h5 {
} */

View file

@ -25,18 +25,8 @@
overflow: visible;
}
.the-slider img {
width: 90%;
height: auto;
margin: 0 auto;
border-radius: 10px;
}
.reset-styles * {
all: unset;
display: revert;
}
#particles-js {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1; /* This value must be lower than other elements! */
}

View file

@ -1,139 +1,64 @@
/* Adjust the gallery wrapper */
.gallery-wrapper {
.fancy-gallery {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 20px;
margin-top: 30px;
}
.gallery-item {
position: relative;
overflow: hidden;
border-radius: 15px;
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.2);
transform: scale(1);
transition: transform 0.4s ease-in-out, filter 0.4s ease-in-out;
}
.gallery-item img {
width: 100%;
max-width: 800px;
margin: 0 auto; /* Center the gallery */
overflow: hidden; /* Ensure content is within bounds */
height: 100%;
object-fit: cover;
transition: transform 0.6s ease, filter 0.6s ease;
}
.gallery-wrapper img {
display: block;
width: 100%;
height: auto;
border: 1px solid #dddddd3d;
}
/* Slick carousel specific styles */
.slick-prev, .slick-next {
background: #444; /* Button background color */
border: none; /* Remove any border */
border-radius: 50%; /* Make the buttons circular */
color: #fff; /* Text color */
font-size: 18px; /* Font size for the arrow */
height: 40px; /* Button height */
width: 40px; /* Button width */
line-height: 40px; /* Center text vertically */
text-align: center; /* Center text horizontally */
z-index: 1000; /* Make sure buttons are above the carousel images */
position: absolute;
top: 50%; /* Position vertically centered */
transform: translateY(-50%);
}
.slick-prev {
left: 0px;
}
.slick-next {
right: 0px;
}
/* Center the dots under the image */
.slick-dots {
text-align: center;
margin-top: 15px; /* Space between image and dots */
padding-left: 0;
list-style: none;
}
.slick-dots li {
display: inline-block; /* Align dots horizontally */
margin: 0 8px; /* Space between dots */
}
.slick-dots li button {
background: #ccc; /* Dot background color */
border: none; /* Remove any border */
border-radius: 50%; /* Make the dots circular */
width: 12px; /* Dot width */
height: 12px; /* Dot height */
position: relative;
}
.slick-dots li button::before {
.gallery-item::before {
content: '';
display: block;
width: 16px; /* Adjusted circle width */
height: 16px; /* Adjusted circle height */
border-radius: 50%;
background-color: transparent;
border: 2px solid #ccc; /* Circle border color */
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(45deg, rgba(255, 0, 150, 0.3), rgba(0, 204, 255, 0.3));
mix-blend-mode: overlay;
opacity: 0;
transition: opacity 0.6s ease;
}
.slick-dots li.slick-active button::before {
background-color: #fff; /* Active dot background color */
border-color: #fff; /* Active dot border color */
.gallery-item:hover {
transform: scale(1.05);
filter: brightness(1.2);
}
/* This is what I get for global styling and using templates */
/* Override the border-radius to remove rounded corners */
.slick-prev, .slick-next {
border-radius: 0 !important;
background: none !important;
box-shadow: none !important;
.gallery-item:hover img {
transform: scale(1.1) rotate(2deg);
filter: grayscale(20%)
}
/* If you want a specific shape, like a square or a different background */
.slick-prev, .slick-next {
border-radius: 0 !important;
background: transparent !important;
.gallery-item:hover::before {
opacity: 1;
}
/*
@keyframes pulse {
0%, 100% {
transform: scale(1);
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.2);
}
50% {
transform: scale(1.02);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.3);
}
}
/* Ensure the active dot buttons are not rounded */
.slick-dots li button {
border-radius: 0 !important;
background: none !important;
}
.slick-dots li button::before {
border-radius: 0 !important;
}
/* General button override */
button[type="button"][role="tab"] {
border-radius: 0 !important;
background: none !important;
box-shadow: none !important;
border: none !important;
color: inherit !important;
padding: 0 !important;
}
/* Restore the round shape for the dots */
.slick-dots li button {
border-radius: 50% !important;
background: #ccc !important;
width: 12px;
height: 12px;
}
/* Ensure the pseudo-element is also circular */
.slick-dots li button::before {
border-radius: 50% !important;
background-color: transparent;
border: 2px solid #ccc;
width: 16px;
height: 16px;
}
/* Style for the active dot */
.slick-dots li.slick-active button::before {
background-color: #fff;
border-color: #fff;
}
.gallery-item {
animation: pulse 5s infinite;
} */

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View file

@ -5,8 +5,8 @@
@import 'assets/webfonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwlxdu.woff2';
/*
Spitfire Browser by Internet Addict (https://weforgecode.xyz/Spitfire/Website)
Based on Stellar by HTML5 UP | @ajlkn
Stellar by HTML5 UP
html5up.net | @ajlkn
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
*/
@ -129,7 +129,7 @@ input, select, textarea {
body, input, select, textarea {
font-family: "Source Sans Pro", Helvetica, sans-serif;
font-size: 14pt;
font-size: 17pt;
font-weight: 300;
line-height: 1.65;
}
@ -159,10 +159,10 @@ input, select, textarea {
}
a {
-moz-transition: color 0.3s ease, border-bottom 0.3s ease;
-webkit-transition: color 0.3s ease, border-bottom 0.3s ease;
-ms-transition: color 0.3s ease, border-bottom 0.3s ease;
transition: color 0.3s ease, border-bottom 0.3s ease;
-moz-transition: color 0.2s ease, border-bottom 0.2s ease;
-webkit-transition: color 0.2s ease, border-bottom 0.2s ease;
-ms-transition: color 0.2s ease, border-bottom 0.2s ease;
transition: color 0.2s ease, border-bottom 0.2s ease;
text-decoration: none;
border-bottom: dotted 1px;
color: inherit;
@ -1836,10 +1836,10 @@ input, select, textarea {
-webkit-appearance: none;
-ms-appearance: none;
appearance: none;
-moz-transition: background-color 0.3s ease-in-out, color 0.3s ease-in-out;
-webkit-transition: background-color 0.3s ease-in-out, color 0.3s ease-in-out;
-ms-transition: background-color 0.3s ease-in-out, color 0.3s ease-in-out;
transition: background-color 0.3s ease-in-out, color 0.3s ease-in-out;
-moz-transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
-webkit-transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
-ms-transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
border-radius: 8px;
border: 0;
cursor: pointer;
@ -2218,10 +2218,10 @@ input, select, textarea {
.icon {
text-decoration: none;
-moz-transition: background-color 0.3s ease-in-out, color 0.3s ease-in-out;
-webkit-transition: background-color 0.3s ease-in-out, color 0.3s ease-in-out;
-ms-transition: background-color 0.3s ease-in-out, color 0.3s ease-in-out;
transition: background-color 0.3s ease-in-out, color 0.3s ease-in-out;
-moz-transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
-webkit-transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
-ms-transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
border-bottom: none;
position: relative;
}
@ -3220,10 +3220,10 @@ input, select, textarea {
/* Nav */
#nav {
-moz-transition: background-color 0.3s ease, border-top-left-radius 0.3s ease, border-top-right-radius 0.3s ease, padding 0.3s ease;
-webkit-transition: background-color 0.3s ease, border-top-left-radius 0.3s ease, border-top-right-radius 0.3s ease, padding 0.3s ease;
-ms-transition: background-color 0.3s ease, border-top-left-radius 0.3s ease, border-top-right-radius 0.3s ease, padding 0.3s ease;
transition: background-color 0.3s ease, border-top-left-radius 0.3s ease, border-top-right-radius 0.3s ease, padding 0.3s ease;
-moz-transition: background-color 0.2s ease, border-top-left-radius 0.2s ease, border-top-right-radius 0.2s ease, padding 0.2s ease;
-webkit-transition: background-color 0.2s ease, border-top-left-radius 0.2s ease, border-top-right-radius 0.2s ease, padding 0.2s ease;
-ms-transition: background-color 0.2s ease, border-top-left-radius 0.2s ease, border-top-right-radius 0.2s ease, padding 0.2s ease;
transition: background-color 0.2s ease, border-top-left-radius 0.2s ease, border-top-right-radius 0.2s ease, padding 0.2s ease;
background-color: #0a0a0a;
color: #fafafa;
position: absolute;
@ -3277,10 +3277,10 @@ input, select, textarea {
}
#nav ul li {
-moz-transition: margin 0.3s ease;
-webkit-transition: margin 0.3s ease;
-ms-transition: margin 0.3s ease;
transition: margin 0.3s ease;
-moz-transition: margin 0.2s ease;
-webkit-transition: margin 0.2s ease;
-ms-transition: margin 0.2s ease;
transition: margin 0.2s ease;
display: inline-block;
margin: 0 0.35em;
padding: 0;
@ -3288,10 +3288,10 @@ input, select, textarea {
}
#nav ul li a {
-moz-transition: font-size 0.3s ease;
-webkit-transition: font-size 0.3s ease;
-ms-transition: font-size 0.3s ease;
transition: font-size 0.3s ease;
-moz-transition: font-size 0.2s ease;
-webkit-transition: font-size 0.2s ease;
-ms-transition: font-size 0.2s ease;
transition: font-size 0.2s ease;
display: inline-block;
height: 2.25em;
line-height: 2.25em;
@ -3301,19 +3301,15 @@ input, select, textarea {
box-shadow: inset 0 0 0 1px transparent;
}
#nav ul li a:hover {
background-color: #080808;
}
#nav ul li a.active {
transition: all 0.3s ease;
background-color: #1a1a1a;
background-color: #000000;
box-shadow: none;
}
#nav ul li a:hover {
transition: all 0.3s ease;
background-color: #000000;
}
#nav.alt {
position: fixed;
top: 0;

237
static/css/search.css Normal file
View file

@ -0,0 +1,237 @@
/* inter-300 - latin */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 300;
src: local(''),
url('/static/fonts/inter-v12-latin-300.woff2') format('woff2'),
/* Chrome 26+, Opera 23+, Firefox 39+ */
url('/static/fonts/inter-v12-latin-300.woff') format('woff');
/* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* inter-regular - latin */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
src: local(''),
url('/static/fonts/inter-v12-latin-regular.woff2') format('woff2'),
/* Chrome 26+, Opera 23+, Firefox 39+ */
url('/static/fonts/inter-v12-latin-regular.woff') format('woff');
/* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* inter-700 - latin */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 700;
src: local(''),
url('/static/fonts/inter-v12-latin-700.woff2') format('woff2'),
/* Chrome 26+, Opera 23+, Firefox 39+ */
url('/static/fonts/inter-v12-latin-700.woff') format('woff');
/* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* material-icons-round-regular - latin */
@font-face {
font-display: swap;
/* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Material Icons Round';
font-style: normal;
font-weight: 400;
src: url('/static/webfonts/material-icons-round-v108-latin-regular.woff2') format('woff2');
/* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
#search-wrapper-ico,
#clearSearch {
background: none;
border: none;
color: var(--fg);
position: absolute;
top: 7px;
right: 10px;
cursor: pointer;
}
#search-wrapper-ico:hover,
#clearSearch:hover {
transition: all .3s ease;
color: var(--blue);
}
#sub-search-wrapper-ico {
background: none;
border: none;
cursor: pointer;
font-size: 17px;
/* will be set to 17px if icon pack can be loaded. */
padding-right: 0px;
margin-right: 0px;
}
.search-container {
text-align: center;
margin-top: 10%;
}
.search-container h1 {
font-size: 70px;
color: var(--font-fg);
font-family: 'Inter';
}
.search-container input {
width: 90%;
color: var(--font-fg);
background-color: var(--search-bg-input);
font-size: inherit;
font-family: sans-serif;
border: none;
margin-right: 100%;
margin-left: 3px;
}
.search-container-results-btn {
display: flex;
color: var(--blue);
}
.search-container-results-btn:hover button {
transition: all 0.3s ease;
color: var(--blue);
}
#search-wrapper-ico,
#clearSearch {
background: none;
border: none;
color: var(--fg);
position: absolute;
top: 7px;
right: 10px;
cursor: pointer;
}
.search-button-wrapper button:hover {
border: 1px solid #5f6368;
cursor: pointer;
}
.wrapper {
margin: 0 auto;
background: var(--search-bg-input);
border-radius: 22px;
position: absolute;
width: 520px;
overflow: hidden;
margin-left: auto;
margin-right: auto;
left: 0;
right: 0;
z-index: 2;
border: 1px solid var(--search-bg-input-border);
}
.wrapper input {
padding: 10px;
}
.wrapper-results {
margin: 0 auto;
background: var(--search-bg-input);
border-radius: 22px;
position: absolute;
width: 628px;
overflow: hidden;
margin-top: 0px;
top: 18px;
left: 170px;
z-index: 2;
border: 1px solid var(--search-bg-input-border);
}
.wrapper-results:hover,
.wrapper-results:focus-within,
.wrapper:hover,
.wrapper:focus-within {
box-shadow: 0px 6px 12px rgba(0, 0, 0, 0.24);
transition: all 0.3s cubic-bezier(.25, .8, .25, 1);
}
.autocomplete {
padding: 0px;
}
.autocomplete ul {
margin: 0;
padding: 0;
}
.autocomplete ul li {
list-style: none;
opacity: 0;
display: none;
padding: 8px 12px;
}
.show .autocomplete ul li {
opacity: 1;
display: block;
text-align: left;
}
.show .autocomplete {
padding-top: 10px;
padding-bottom: 10px;
color: var(--font-fg);
}
.autocomplete ul li:hover {
cursor: pointer;
background: var(--search-select);
}
@media only screen and (max-width: 750px) {
.wrapper {
width: 86%;
position: absolute;
float: none;
margin-top: 0px;
margin-bottom: 0px;
margin-left: auto;
margin-right: auto;
display: block;
margin-top: 0px;
top: 110px;
left: 4px;
}
.wrapper input {
padding: 10px;
max-width: 92%;
}
.results-search-container {
margin-left: auto;
margin-right: auto;
text-align: center;
}
.results-search-container input {
width: 84%;
margin-right: 100%;
margin-left: 3px;
}
#search-wrapper-ico {
top: 5px;
}
}

View file

@ -1,2 +0,0 @@
.slick-slider{position:relative;display:block;box-sizing:border-box;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-touch-callout:none;-khtml-user-select:none;-ms-touch-action:pan-y;touch-action:pan-y;-webkit-tap-highlight-color:transparent}.slick-list{position:relative;display:block;overflow:hidden;margin:0;padding:0}.slick-list:focus{outline:0}.slick-list.dragging{cursor:pointer;cursor:hand}.slick-slider .slick-list,.slick-slider .slick-track{-webkit-transform:translate3d(0,0,0);-moz-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.slick-track{position:relative;top:0;left:0;display:block;margin-left:auto;margin-right:auto}.slick-track:after,.slick-track:before{display:table;content:''}.slick-track:after{clear:both}.slick-loading .slick-track{visibility:hidden}.slick-slide{display:none;float:left;height:100%;min-height:1px}[dir=rtl] .slick-slide{float:right}.slick-slide img{display:block}.slick-slide.slick-loading img{display:none}.slick-slide.dragging img{pointer-events:none}.slick-initialized .slick-slide{display:block}.slick-loading .slick-slide{visibility:hidden}.slick-vertical .slick-slide{display:block;height:auto;border:1px solid transparent}.slick-arrow.slick-hidden{display:none}
/*# sourceMappingURL=/sm/fb3ed351cd5c0f1f30f88778ee1f9b056598e6d25ac4fdcab1eebcd8be521cd9.map */

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

315
static/js/autocomplete.js Normal file
View file

@ -0,0 +1,315 @@
/**
* @source: ./script.js (originally from araa-search on Github)
*
* @licstart The following is the entire license notice for the
* JavaScript code in this page.
*
* Copyright (C) 2023 Extravi
*
* The JavaScript code in this page is free software: you can
* redistribute it and/or modify it under the terms of the GNU Affero
* General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* The code is distributed WITHOUT ANY WARRANTY; without even the
* implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero General Public License for more details.
*
* As additional permission under GNU Affero General Public License
* section 7, you may distribute non-source (e.g., minimized or compacted)
* forms of that code without the copy of the GNU Affero General Public
* License normally required by section 4, provided you include this
* license notice and a URL through which recipients can access the
* Corresponding Source.
*
* @licend The above is the entire license notice
* for the JavaScript code in this page.
*/
// Removes the 'Apply Settings' button for Javascript users,
// since changing any of the elements causes the settings to apply
// automatically.
let resultsSave = document.querySelector(".results-save");
if (resultsSave != null) {
resultsSave.style.display = "none";
}
const searchInput = document.getElementById('search-input');
const searchWrapper = document.querySelectorAll('.wrapper, .wrapper-results')[0];
const resultsWrapper = document.querySelector('.autocomplete');
const clearSearch = document.querySelector("#clearSearch");
async function getSuggestions(query) {
try {
const params = new URLSearchParams({ "q": query }).toString();
const response = await fetch(`/suggestions?${params}`);
const data = await response.json();
return data[1]; // Return only the array of suggestion strings
} catch (error) {
console.error(error);
}
}
let currentIndex = -1; // Keep track of the currently selected suggestion
let results = [];
searchInput.addEventListener('input', async () => {
let input = searchInput.value;
if (input.length) {
results = await getSuggestions(input);
}
renderResults(results);
currentIndex = -1; // Reset index when we return new results
});
searchInput.addEventListener("focus", async () => {
let input = searchInput.value;
if (results.length === 0 && input.length != 0) {
results = await getSuggestions(input);
}
renderResults(results);
})
clearSearch.style.visibility = "visible"; // Only show the clear search button for JS users.
clearSearch.addEventListener("click", () => {
searchInput.value = "";
searchInput.focus();
})
searchInput.addEventListener('keydown', (event) => {
if (event.key === 'ArrowUp' || event.key === 'ArrowDown') {
event.preventDefault(); // Prevent the cursor from moving in the search input
// Find the currently selected suggestion element
const selectedSuggestion = resultsWrapper.querySelector('.selected');
if (selectedSuggestion) {
selectedSuggestion.classList.remove('selected'); // Deselect the currently selected suggestion
}
// Increment or decrement the current index based on the arrow key pressed
if (event.key === 'ArrowUp') {
currentIndex--;
} else {
currentIndex++;
}
// Wrap around the index if it goes out of bounds
if (currentIndex < 0) {
currentIndex = resultsWrapper.querySelectorAll('li').length - 1;
} else if (currentIndex >= resultsWrapper.querySelectorAll('li').length) {
currentIndex = 0;
}
// Select the new suggestion
resultsWrapper.querySelectorAll('li')[currentIndex].classList.add('selected');
// Update the value of the search input
searchInput.value = resultsWrapper.querySelectorAll('li')[currentIndex].textContent;
}
});
function renderResults(results) {
if (!results || !results.length || !searchInput.value) {
return searchWrapper.classList.remove('show');
}
let content = '';
results.forEach((item) => {
content += `<li>${item}</li>`;
});
// Only show the autocomplete suggestions if the search input has a non-empty value
if (searchInput.value) {
searchWrapper.classList.add('show');
}
resultsWrapper.innerHTML = `<ul>${content}</ul>`;
}
resultsWrapper.addEventListener('click', (event) => {
if (event.target.tagName === 'LI') {
// Set the value of the search input to the clicked suggestion
searchInput.value = event.target.textContent;
// Reset the current index
currentIndex = -1;
// Submit the form
searchWrapper.querySelector('input[type="submit"]').click();
// Remove the show class from the search wrapper
searchWrapper.classList.remove('show');
}
});
document.addEventListener("keypress", (event) => {
if (document.activeElement == searchInput) {
// Allow the '/' character to be pressed when searchInput is active
} else if (document.querySelector(".calc") != null) {
// Do nothing if the calculator is available, so the division keybinding
// will still work
}
else if (event.key == "/") {
event.preventDefault();
searchInput.focus();
searchInput.selectionStart = searchInput.selectionEnd = searchInput.value.length;
}
})
// Add event listener to hide autocomplete suggestions when clicking outside of search-input or wrapper
document.addEventListener('click', (event) => {
// Check if the target of the event is the search-input or any of its ancestors
if (!searchInput.contains(event.target) && !searchWrapper.contains(event.target)) {
// Remove the show class from the search wrapper
searchWrapper.classList.remove('show');
}
});
// Load material icons. If the file cannot be loaded,
// skip them and put a warning in the console.
const font = new FontFace('Material Icons Round', 'url("/fonts/material-icons-round-v108-latin-regular.woff2") format("woff2")');
font.load().then(() => {
const icons = document.getElementsByClassName('material-icons-round');
// Display all icons.
for (let icon of icons) {
icon.style.visibility = 'visible';
}
// Ensure icons for the different types of searches are sized correctly.
document.querySelectorAll('#sub-search-wrapper-ico').forEach((el) => {
el.style.fontSize = '17px';
});
}).catch(() => {
console.warn('Failed to load Material Icons Round. Hiding any icons using said pack.');
});
// load image after server side processing
window.addEventListener('DOMContentLoaded', function () {
var knoTitleElement = document.getElementById('kno_title');
var kno_title = knoTitleElement.dataset.knoTitle;
fetch(kno_title)
.then(response => response.json())
.then(data => {
const pageId = Object.keys(data.query.pages)[0];
const thumbnailSource = data.query.pages[pageId].thumbnail.source;
const url = "/img_proxy?url=" + thumbnailSource;
// update the img tag with url and add kno_wiki_show
var imgElement = document.querySelector('.kno_wiki');
imgElement.src = url;
imgElement.classList.add('kno_wiki_show');
console.log(url);
})
.catch(error => {
console.log('Error fetching data:', error);
});
});
const urlParams = new URLSearchParams(window.location.search);
if (document.querySelectorAll(".search-active")[1].getAttribute("value") === "image") {
// image viewer for image search
const closeButton = document.querySelector('.image-close');
const imageView = document.querySelector('.image_view');
const images = document.querySelector('.images');
const viewImageImg = document.querySelector('.view-image-img');
const imageSource = document.querySelector('.image-source');
const imageFull = document.querySelector(".full-size");
const imageProxy = document.querySelector('.proxy-size');
const imageViewerLink = document.querySelector('.image-viewer-link');
const imageSize = document.querySelector('.image-size');
const fullImageSize = document.querySelector(".full-image-size");
const imageAlt = document.querySelector('.image-alt');
const openImageViewer = document.querySelectorAll('.open-image-viewer');
const imageBefore = document.querySelector('.image-before');
const imageNext = document.querySelector('.image-next');
let currentImageIndex = 0;
closeButton.addEventListener('click', function () {
imageView.classList.remove('image_show');
imageView.classList.add('image_hide');
for (const image of document.querySelectorAll(".image_selected")) {
image.classList = ['image'];
}
images.classList.add('images_viewer_hidden');
});
openImageViewer.forEach((image, index) => {
image.addEventListener('click', function (event) {
event.preventDefault();
currentImageIndex = index;
showImage();
});
});
document.addEventListener('keydown', function (event) {
if (searchInput == document.activeElement)
return;
if (event.key === 'ArrowLeft') {
currentImageIndex = (currentImageIndex - 1 + openImageViewer.length) % openImageViewer.length;
showImage();
}
else if (event.key === 'ArrowRight') {
currentImageIndex = (currentImageIndex + 1) % openImageViewer.length;
showImage();
}
});
imageBefore.addEventListener('click', function () {
currentImageIndex = (currentImageIndex - 1 + openImageViewer.length) % openImageViewer.length;
showImage();
});
imageNext.addEventListener('click', function () {
currentImageIndex = (currentImageIndex + 1) % openImageViewer.length;
showImage();
});
function showImage() {
for (const image of document.querySelectorAll(".image_selected")) {
image.classList = ['image'];
}
const current_image = document.querySelectorAll(".image")[currentImageIndex];
current_image.classList.add("image_selected");
var rect = current_image.getBoundingClientRect();
if (!(rect.top >= 0 && rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth))) {
current_image.scrollIntoView(false);
}
const src = openImageViewer[currentImageIndex].getAttribute('src');
const alt = openImageViewer[currentImageIndex].getAttribute('alt');
const data = openImageViewer[currentImageIndex].getAttribute('data');
const clickableLink = openImageViewer[currentImageIndex].closest('.clickable');
const href = clickableLink.getAttribute('href');
viewImageImg.src = src;
imageProxy.href = src;
imageFull.href = data;
imageSource.href = href;
imageSource.textContent = href;
imageViewerLink.href = href;
images.classList.remove('images_viewer_hidden');
imageView.classList.remove('image_hide');
imageView.classList.add('image_show');
imageAlt.textContent = alt;
fullImageSize.textContent = document.querySelector(".image_selected .resolution").textContent;
getImageSize(src).then(size => {
imageSize.textContent = size;
});
}
function getImageSize(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = function () {
const size = `${this.width} x ${this.height}`;
resolve(size);
};
img.onerror = function () {
reject('Error loading image');
};
img.src = url;
});
}
}

View file

@ -1,6 +1,6 @@
/*
Spitfire Browser by Internet Addict (https://weforgecode.xyz/Spitfire/Website)
Based on Stellar by HTML5 UP | @ajlkn
Stellar by HTML5 UP
html5up.net | @ajlkn
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
*/

File diff suppressed because one or more lines are too long

View file

@ -1,110 +0,0 @@
{
"particles": {
"number": {
"value": 160,
"density": {
"enable": true,
"value_area": 800
}
},
"color": {
"value": "#ffffff"
},
"shape": {
"type": "circle",
"stroke": {
"width": 0,
"color": "#000000"
},
"polygon": {
"nb_sides": 5
},
"image": {
"src": "img/github.svg",
"width": 100,
"height": 100
}
},
"opacity": {
"value": 1,
"random": true,
"anim": {
"enable": true,
"speed": 1,
"opacity_min": 0,
"sync": false
}
},
"size": {
"value": 3,
"random": true,
"anim": {
"enable": false,
"speed": 4,
"size_min": 0.3,
"sync": false
}
},
"line_linked": {
"enable": false,
"distance": 150,
"color": "#ffffff",
"opacity": 0.4,
"width": 1
},
"move": {
"enable": true,
"speed": 1,
"direction": "none",
"random": true,
"straight": false,
"out_mode": "out",
"bounce": false,
"attract": {
"enable": false,
"rotateX": 600,
"rotateY": 600
}
}
},
"interactivity": {
"detect_on": "canvas",
"events": {
"onhover": {
"enable": true,
"mode": "bubble"
},
"onclick": {
"enable": true,
"mode": "repulse"
},
"resize": true
},
"modes": {
"grab": {
"distance": 400,
"line_linked": {
"opacity": 1
}
},
"bubble": {
"distance": 250,
"size": 0,
"duration": 2,
"opacity": 0,
"speed": 3
},
"repulse": {
"distance": 400,
"duration": 0.4
},
"push": {
"particles_nb": 4
},
"remove": {
"particles_nb": 2
}
}
},
"retina_detect": true
}

View file

@ -1,110 +0,0 @@
{
"particles": {
"number": {
"value": 160,
"density": {
"enable": true,
"value_area": 800
}
},
"color": {
"value": "#ffffff"
},
"shape": {
"type": "circle",
"stroke": {
"width": 0,
"color": "#000000"
},
"polygon": {
"nb_sides": 5
},
"image": {
"src": "img/github.svg",
"width": 10,
"height": 10
}
},
"opacity": {
"value": 0.7,
"random": true,
"anim": {
"enable": true,
"speed": 0.47948982282851027,
"opacity_min": 0,
"sync": false
}
},
"size": {
"value": 2,
"random": true,
"anim": {
"enable": false,
"speed": 4,
"size_min": 0.3,
"sync": false
}
},
"line_linked": {
"enable": false,
"distance": 150,
"color": "#ffffff",
"opacity": 0.4,
"width": 1
},
"move": {
"enable": true,
"speed": 0,
"direction": "none",
"random": true,
"straight": false,
"out_mode": "out",
"bounce": false,
"attract": {
"enable": false,
"rotateX": 600,
"rotateY": 600
}
}
},
"interactivity": {
"detect_on": "canvas",
"events": {
"onhover": {
"enable": true,
"mode": "bubble"
},
"onclick": {
"enable": true,
"mode": "repulse"
},
"resize": true
},
"modes": {
"grab": {
"distance": 400,
"line_linked": {
"opacity": 1
}
},
"bubble": {
"distance": 250,
"size": 0,
"duration": 2,
"opacity": 0,
"speed": 3
},
"repulse": {
"distance": 400,
"duration": 0.4
},
"push": {
"particles_nb": 4
},
"remove": {
"particles_nb": 2
}
}
},
"retina_detect": true
}

View file

@ -1,16 +0,0 @@
$(document).ready(function(){
$('.the-slider').slick({
dots: true,
infinite: true,
speed: 300,
slidesToShow: 1,
adaptiveHeight: true,
autoplay: true,
autoplaySpeed: 2000,
prevArrow: '<button type="button" class="slick-prev"></button>',
nextArrow: '<button type="button" class="slick-next"></button>',
customPaging: function(slider, i) {
return '<button type="button">' + '</button>';
}
});
});

File diff suppressed because one or more lines are too long

View file

@ -1,7 +1,7 @@
///
// Spitfire Browser by Internet Addict (https://weforgecode.xyz/Spitfire/Website)
// Based on Stellar by HTML5 UP | @ajlkn
// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
/// Stellar by HTML5 UP
/// html5up.net | @ajlkn
/// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
///
/* Icons */

160
suggestions.go Normal file
View file

@ -0,0 +1,160 @@
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
)
func handleSuggestions(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query().Get("q")
if query == "" {
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, `["",[]]`)
return
}
// Define the fallback sequence with Google lower in the hierarchy
suggestionSources := []func(string) []string{
fetchDuckDuckGoSuggestions,
fetchEdgeSuggestions,
fetchBraveSuggestions,
fetchEcosiaSuggestions,
fetchQwantSuggestions,
fetchStartpageSuggestions,
// fetchGoogleSuggestions, // I advise against it, but you can use it if you want to
}
var suggestions []string
for _, fetchFunc := range suggestionSources {
suggestions = fetchFunc(query)
if len(suggestions) > 0 {
printDebug("Suggestions found using %T", fetchFunc)
break
} else {
printWarn("%T did not return any suggestions or failed.", fetchFunc)
}
}
if len(suggestions) == 0 {
printErr("All suggestion services failed. Returning empty response.")
}
// Return the final suggestions as JSON
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, `["",%s]`, toJSONStringArray(suggestions))
}
func fetchGoogleSuggestions(query string) []string {
encodedQuery := url.QueryEscape(query)
url := fmt.Sprintf("http://suggestqueries.google.com/complete/search?client=firefox&q=%s", encodedQuery)
printDebug("Fetching suggestions from Google: %s", url)
return fetchSuggestionsFromURL(url)
}
func fetchDuckDuckGoSuggestions(query string) []string {
encodedQuery := url.QueryEscape(query)
url := fmt.Sprintf("https://duckduckgo.com/ac/?q=%s&type=list", encodedQuery)
printDebug("Fetching suggestions from DuckDuckGo: %s", url)
return fetchSuggestionsFromURL(url)
}
func fetchEdgeSuggestions(query string) []string {
encodedQuery := url.QueryEscape(query)
url := fmt.Sprintf("https://api.bing.com/osjson.aspx?query=%s", encodedQuery)
printDebug("Fetching suggestions from Edge (Bing): %s", url)
return fetchSuggestionsFromURL(url)
}
func fetchBraveSuggestions(query string) []string {
encodedQuery := url.QueryEscape(query)
url := fmt.Sprintf("https://search.brave.com/api/suggest?q=%s", encodedQuery)
printDebug("Fetching suggestions from Brave: %s", url)
return fetchSuggestionsFromURL(url)
}
func fetchEcosiaSuggestions(query string) []string {
encodedQuery := url.QueryEscape(query)
url := fmt.Sprintf("https://ac.ecosia.org/?q=%s&type=list", encodedQuery)
printDebug("Fetching suggestions from Ecosia: %s", url)
return fetchSuggestionsFromURL(url)
}
func fetchQwantSuggestions(query string) []string {
encodedQuery := url.QueryEscape(query)
url := fmt.Sprintf("https://api.qwant.com/v3/suggest?q=%s", encodedQuery)
printDebug("Fetching suggestions from Qwant: %s", url)
return fetchSuggestionsFromURL(url)
}
func fetchStartpageSuggestions(query string) []string {
encodedQuery := url.QueryEscape(query)
url := fmt.Sprintf("https://startpage.com/suggestions?q=%s", encodedQuery)
printDebug("Fetching suggestions from Startpage: %s", url)
return fetchSuggestionsFromURL(url)
}
func fetchSuggestionsFromURL(url string) []string {
resp, err := http.Get(url)
if err != nil {
printWarn("Error fetching suggestions from %s: %v", url, err)
return []string{}
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
printWarn("Error reading response body from %s: %v", url, err)
return []string{}
}
// Log the Content-Type for debugging
contentType := resp.Header.Get("Content-Type")
printDebug("Response Content-Type from %s: %s", url, contentType)
// Check if the body is non-empty
if len(body) == 0 {
printWarn("Received empty response body from %s", url)
return []string{}
}
// Attempt to parse the response as JSON regardless of Content-Type
var parsedResponse []interface{}
if err := json.Unmarshal(body, &parsedResponse); err != nil {
printErr("Error parsing JSON from %s: %v", url, err)
printDebug("Response body: %s", string(body))
return []string{}
}
// Ensure the response structure is as expected
if len(parsedResponse) < 2 {
printWarn("Unexpected response format from %v: %v", url, string(body))
return []string{}
}
suggestions := []string{}
if items, ok := parsedResponse[1].([]interface{}); ok {
for _, item := range items {
if suggestion, ok := item.(string); ok {
suggestions = append(suggestions, suggestion)
}
}
} else {
printErr("Unexpected suggestions format in response from: %v", url)
}
return suggestions
}
func toJSONStringArray(strings []string) string {
result := ""
for i, str := range strings {
result += fmt.Sprintf(`"%s"`, str)
if i < len(strings)-1 {
result += ","
}
}
return "[" + result + "]"
}

View file

@ -1,17 +1,17 @@
<!DOCTYPE HTML>
<!--
Spitfire Browser by Internet Addict (https://weforgecode.xyz/Spitfire/Website)
Based on Stellar by HTML5 UP | @ajlkn
Stellar by HTML5 UP
html5up.net | @ajlkn
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
-->
<html>
<head>
<link rel="icon" type="image/png" href="static/images/favicon.png">
<link rel="icon" type="image/png" href="favicon.png">
<title>Spitfire Browser - Downloads</title>
<meta content="🌐 Spitfire Browser" property="og:title" />
<meta content="Privacy respecting user friendly web browser." property="og:description" />
<meta content="https://spitfirebrowser.com/" property="og:url" />
<meta content="https://spitfirebrowser.com/static/images/favicon.png" property="og:image" />
<meta content="https://spitfirebrowser.com/favicon.png" property="og:image" />
<meta content="#f1f1f1" data-react-helmet="true" name="theme-color" />
<meta name="darkreader-lock">
@ -31,7 +31,7 @@
</div>
<!-- Top Floater Footer -->
<div style="background-color: red; color: white; text-align: center; padding: 10px 0; position: fixed; bottom: 0; width: 100%; z-index: 1000;">
<div style="background-color: red; color: white; text-align: center; padding: 10px 0; position: fixed; top: 0; width: 100%; z-index: 1000;">
🚧👷‍♂️ This site is under construction! Please check back later. 👷‍♀️🚧
</div>

View file

@ -1,17 +1,17 @@
<!DOCTYPE HTML>
<!--
Spitfire Browser by Internet Addict (https://weforgecode.xyz/Spitfire/Website)
Based on Stellar by HTML5 UP | @ajlkn
Stellar by HTML5 UP
html5up.net | @ajlkn
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
-->
<html>
<head>
<link rel="icon" type="image/png" href="static/images/favicon.png">
<head>
<link rel="icon" type="image/png" href="favicon.png">
<title>Spitfire Browser - Downloads</title>
<meta content="🌐 Spitfire Browser" property="og:title" />
<meta content="Privacy respecting user friendly web browser." property="og:description" />
<meta content="https://spitfirebrowser.com/" property="og:url" />
<meta content="https://spitfirebrowser.com/static/images/favicon.png" property="og:image" />
<meta content="https://spitfirebrowser.com/favicon.png" property="og:image" />
<meta content="#f1f1f1" data-react-helmet="true" name="theme-color" />
<meta name="darkreader-lock">
@ -57,7 +57,7 @@
font-size: 1.2em;
}
</style>
</head>
</head>
<body class="is-preload">
<!-- Star Background Divs -->
@ -68,7 +68,7 @@
</div>
<!-- Top Floater Footer -->
<div style="background-color: red; color: white; text-align: center; padding: 10px 0; position: fixed; bottom: 0; width: 100%; z-index: 1000;">
<div style="background-color: red; color: white; text-align: center; padding: 10px 0; position: fixed; top: 0; width: 100%; z-index: 1000;">
🚧👷‍♂️ This site is under construction! Please check back later. 👷‍♀️🚧
</div>

View file

@ -1,17 +1,17 @@
<!DOCTYPE HTML>
<!--
Spitfire Browser by Internet Addict (https://weforgecode.xyz/Spitfire/Website)
Based on Stellar by HTML5 UP | @ajlkn
Spitfire Browser by Internet Addict
Based on Stellar by HTML5 UP
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
-->
<html>
<head>
<link rel="icon" type="image/png" href="static/images/favicon.png">
<link rel="icon" type="image/png" href="favicon.png">
<title>Spitfire Browser - Fast. Secure. Elegant.</title>
<meta content="🌐 Spitfire Browser" property="og:title" />
<meta content="Privacy respecting user friendly web browser." property="og:description" />
<meta content="https://spitfirebrowser.com/" property="og:url" />
<meta content="https://spitfirebrowser.com/static/images/favicon.png" property="og:image" />
<meta content="https://spitfirebrowser.com/favicon.png" property="og:image" />
<meta content="#f1f1f1" data-react-helmet="true" name="theme-color" />
<meta name="darkreader-lock">
@ -21,23 +21,20 @@
<link rel="stylesheet" href="/static/css/stars.css" />
<link rel="stylesheet" href="/static/css/fancy-gallery.css" />
<link rel="stylesheet" href="/static/css/extras.css" />
<link rel="stylesheet" href="/static/css/slick.min.css" />
<link rel="stylesheet" href="/static/css/search.css" />
<noscript><link rel="stylesheet" href="/static/css/noscript.css" /></noscript>
</head>
<body class="is-preload">
<!-- Star Background Divs -->
<div id="star-background">
<div id="particles-js"></div>
<noscript>
<div id="stars"></div>
<div id="stars2"></div>
<div id="stars3"></div>
</noscript>
</div>
<!-- Top Floater Footer -->
<div style="background-color: red; color: white; text-align: center; padding: 10px 0; position: fixed; bottom: 0; width: 100%; z-index: 1000;">
<div style="background-color: red; color: white; text-align: center; padding: 10px 0; position: fixed; top: 0; width: 100%; z-index: 1000;">
🚧👷‍♂️ This site is under construction! Please check back later. 👷‍♀️🚧
</div>
@ -51,7 +48,6 @@
</span>
<h1>Spitfire Browser</h1>
<p>Fast. Secure. Elegant.</p>
<br>
<a href="/download" class="button">Download Now</a>
</header>
@ -60,8 +56,8 @@
<ul>
<li><a href="#intro" class="active">Introduction</a></li>
<li><a href="#features">Features</a></li>
<li><a href="#search">Search</a></li>
<li><a href="#security">Security</a></li>
<li><a href="#cta">Get Started</a></li>
</ul>
</nav>
@ -73,21 +69,18 @@
<header class="major">
<h2>Welcome to Spitfire Browser</h2>
</header>
<p>Spitfire Browser is your gateway to a fast, secure, and elegant browsing experience. Built on Firefox, Spitfire includes essential features like ad blocking, enhanced security, and anonymous browsing with Ocásek search engine.</p>
<br> <!-- br? I call it super.fancy.tailor.r.css.react.native.way.to.add.super.padding.on.top.of.images.T3.js.express.lodash.moment.axios.jquery.vue.nft.d3.angular.bloat.typescript.webpack.web3.babel.parcel.T4 -->
<div class="gallery-wrapper">
<div class="the-slider"> <!-- Is this fucking centered? -->
<div><img src="/static/images/screenshots/1.png" alt="Screenshot 1"></div>
<div><img src="/static/images/screenshots/1.png" alt="Screenshot 2"></div>
<div><img src="/static/images/screenshots/1.png" alt="Screenshot 3"></div>
<p>Spitfire Browser is your gateway to a fast, secure, and elegant browsing experience. Built on Firefox, Spitfire includes essential features like ad blocking, enhanced security, and anonymous browsing with Warp search engine.</p>
<div class="box alt">
<div class="fancy-gallery">
<div class="gallery-item"><img src="/static/images/screenshots/1.png" alt="Screenshot 1" /></div>
<div class="gallery-item"><img src="/static/images/screenshots/1.png" alt="Screenshot 2" /></div>
</div>
</div>
<!-- <ul class="actions js-disabled">
<li><a href="#intro" class="button">See More Screenshots</a></li>
</ul> -->
<ul class="actions">
<li><a href="#features" class="button">More Screenshots</a></li>
</ul>
</div>
</section>
<!-- Features Section -->
<section id="features" class="main special">
<header class="major">
@ -122,20 +115,11 @@
<li>
<span class="icon solid major style6 fa-search"></span>
<h3>Anonymous Search</h3>
<p><a herf="#search">Ocásek search engine</a> allows you to browse Google results anonymously.</p>
<p><a herf="https://search.spitfirebrowser.com/">Warp search engine</a> allows you to browse Google results anonymously.</p>
</li>
</ul>
</section>
<!-- Try Search -->
<section id="search" class="main special">
<header class="major">
<h2>Try Searching</h2>
<p></p>
</header>
<iframe src="https://search.spitfirebrowser.com" width="100%" height="700px" style="border:none; border-radius: 10px; overflow:hidden;"></iframe>
</section>
<!-- Security Section -->
<section id="security" class="main special">
<header class="major">
@ -181,6 +165,28 @@
</footer>
</section>
<!-- Search -->
<section id="search" class="main special">
<div class="reset-styles">
<form action="https://search.spitfirebrowser.com/search" class="search-container" method="post" autocomplete="off">
<h1>Ocásek</h1>
<div class="wrapper">
<input type="text" name="q" autofocus id="search-input" placeholder="Type to search..." />
<button id="search-wrapper-ico" class="material-icons-round" name="t" value="text" type="submit">search</button>
<div class="autocomplete">
<ul>
</ul>
</div>
</div>
<div class="search-button-wrapper">
<input type="hidden" name="p" value="1">
<!-- <button name="t" value="text" type="submit">Search Text</button>
<button name="t" value="image" type="submit">Search Images</button> -->
</div>
</form>
</div>
</section>
<!-- Get Started -->
<section id="cta" class="main special">
<header class="major">
@ -209,13 +215,13 @@
</a>
</li>
<li>
<a href="https://weforgecode.xyz/Spitfire" class="icon alt">
<a href="#" class="icon alt">
<img src="/static/images/icons/brands/git-alt.svg" alt="Forgejo">
<span class="label">Forgejo</span>
</a>
</li>
<li>
<a href="https://www.youtube.com/@Internet.Addict" class="icon alt">
<a href="#" class="icon alt">
<img src="/static/images/icons/brands/youtube.svg" alt="YouTube">
<span class="label">YouTube</span>
</a>
@ -230,23 +236,14 @@
</div>
<!-- Scripts -->
<script src="/static/js/jquery.min.js"></script>
<script src="/static/js/slick.min.js"></script>
<script src="/static/js/jquery.min.js" defer></script>
<script src="/static/js/jquery.scrollex.min.js" defer></script>
<script src="/static/js/jquery.scrolly.min.js" defer></script>
<script src="/static/js/browser.min.js" defer></script>
<script src="/static/js/breakpoints.min.js" defer></script>
<script src="/static/js/util.js" defer></script>
<script src="/static/js/main.js" defer></script>
<script src="/static/js/slick-conf.js" defer></script>
<script src="/static/js/particles.min.js"></script>
<script>
/* particlesJS.load(@dom-id, @path-json, @callback (optional)); */
particlesJS.load('particles-js', '/static/js/particlesjs-config.json', function() {
console.log('callback - particles.js config loaded');
});
</script>
<script defer src="/static/js/autocomplete.js"></script>
</script>
</body>
</html>

View file

@ -1,17 +1,17 @@
<!DOCTYPE HTML>
<!--
Spitfire Browser by Internet Addict (https://weforgecode.xyz/Spitfire/Website)
Based on Stellar by HTML5 UP | @ajlkn
Stellar by HTML5 UP
html5up.net | @ajlkn
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
-->
<html>
<head>
<link rel="icon" type="image/png" href="static/images/favicon.png">
<head>
<link rel="icon" type="image/png" href="favicon.png">
<title>{{.Title}}</title>
<meta content="🌐 {{.Title}}" property="og:title" />
<meta content="Privacy respecting user friendly web browser." property="og:description" />
<meta content="https://spitfirebrowser.com/" property="og:url" />
<meta content="https://spitfirebrowser.com/static/images/favicon.png" property="og:image" />
<meta content="https://spitfirebrowser.com/favicon.png" property="og:image" />
<meta content="#f1f1f1" data-react-helmet="true" name="theme-color" />
<meta name="darkreader-lock">
@ -22,23 +22,20 @@
<link rel="stylesheet" href="/static/css/extras.css" />
<link rel="stylesheet" href="/static/css/blog.css" />
<noscript><link rel="stylesheet" href="/static/css/noscript.css" /></noscript>
</head>
</head>
<body class="is-preload">
<!-- Star Background Divs -->
<div id="star-background">
<div id="particles-js"></div>
<noscript>
<div id="stars"></div>
<div id="stars2"></div>
<div id="stars3"></div>
</noscript>
</div>
<!-- Top Floater Footer
<!-- Top Floater Footer -->
<div style="background-color: red; color: white; text-align: center; padding: 10px 0; position: fixed; top: 0; width: 100%; z-index: 1000;">
🚧👷‍♂️ This site is under construction! Please check back later. 👷‍♀️🚧
</div> -->
</div>
<!-- Wrapper -->
<div id="wrapper">
@ -61,19 +58,13 @@
</header>
<footer class="major">
<ul class="actions special">
<li>
<a href="{{if .PrevLink}}{{.PrevLink}}{{else}}#{{end}}"
class="button primary small {{if not .PrevLink}}disabled{{end}}">
Previous
</a>
</li>
{{if .PrevLink}}
<li><a href="{{.PrevLink}}" class="button primary small">Previous</a></li>
{{end}}
<li><a href="/" class="button primary small">Home</a></li>
<li>
<a href="{{if .NextLink}}{{.NextLink}}{{else}}#{{end}}"
class="button primary small {{if not .NextLink}}disabled{{end}}">
Next
</a>
</li>
{{if .NextLink}}
<li><a href="{{.NextLink}}" class="button primary small">Next</a></li>
{{end}}
</ul>
</footer>
</div>
@ -119,13 +110,6 @@
<script src="/static/js/breakpoints.min.js" defer></script>
<script src="/static/js/util.js" defer></script>
<script src="/static/js/main.js" defer></script>
<script src="/static/js/particles.min.js"></script>
<script>
/* particlesJS.load(@dom-id, @path-json, @callback (optional)); */
particlesJS.load('particles-js', '/static/js/particlesjs-config.json', function() {
console.log('callback - particles.js config loaded');
});
</script>
</body>
</html>