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,54 +1,45 @@
<p align="center">
<img src="https://weforgecode.xyz/Spitfire/Branding/raw/branch/main/icon5.svg" alt="Logo" width="64" height="64">
</p>
<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>
## TO-DO:
- [ ] Add browser download/screenshots for this web browser website (optional)
### Blog entries should be fromated this way:
```md
[HEADER]
t: TITLE
d: SHORT-DESC
p: 2024-08-16 15:04
a: AUTHOR
[END]
# lorem ipsum
Vestibulum fermentum tortor id mi. Nullam at arcu a est sollicitudin euismod. Nullam faucibus mi quis velit. Mauris dictum facilisis augue. Nullam sapien sem, ornare ac, nonummy non, lobortis a enim. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Etiam commodo dui eget wisi. Mauris dictum facilisis augue. Etiam posuere lacus quis dolor. In sem justo, commodo ut, suscipit at, pharetra vitae, orci.
Vivamus luctus egestas leo. Phasellus faucibus molestie nisl. Etiam commodo dui eget wisi. Donec vitae arcu. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Etiam neque. Suspendisse sagittis ultrices augue. Suspendisse nisl. Etiam sapien elit, consequat eget, tristique non, venenatis quis, ante. Phasellus rhoncus. Maecenas libero.
```
*default path:*
```
/data/news/1.md
/data/news/2.md
...
```
### Based on HTML template:
[Stellar](https://html5up.net/stellar) by HTML5 UP
html5up.net | @ajlkn
### Licence:
This project is licensed under the Creative Commons Attribution 3.0 License (CCA 3.0). For more details, see the LICENSE file.
# Spitfire Browser Website
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 screenshots
- [ ] Add search-engine test
- [ ] Add working downloads
- [X] Add blog/updates
- [ ] Add config file
### Blog entries should be fromated this way:
```md
[HEADER]
t: TITLE
d: SHORT-DESC
p: 2024-08-16 15:04
a: AUTHOR
[END]
# lorem ipsum
Vestibulum fermentum tortor id mi. Nullam at arcu a est sollicitudin euismod. Nullam faucibus mi quis velit. Mauris dictum facilisis augue. Nullam sapien sem, ornare ac, nonummy non, lobortis a enim. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Etiam commodo dui eget wisi. Mauris dictum facilisis augue. Etiam posuere lacus quis dolor. In sem justo, commodo ut, suscipit at, pharetra vitae, orci.
Vivamus luctus egestas leo. Phasellus faucibus molestie nisl. Etiam commodo dui eget wisi. Donec vitae arcu. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Etiam neque. Suspendisse sagittis ultrices augue. Suspendisse nisl. Etiam sapien elit, consequat eget, tristique non, venenatis quis, ante. Phasellus rhoncus. Maecenas libero.
```
*default path:*
```
/data/news/1.md
/data/news/2.md
...
```
### Based on:
Stellar by HTML5 UP
html5up.net | @ajlkn
### Licence:
This project is licensed under the Creative Commons Attribution 3.0 License (CCA 3.0). For more details, see the LICENSE file.

26
go.mod
View file

@ -1,13 +1,13 @@
module spitfire-browser-website
go 1.18
require (
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 // indirect
github.com/gorilla/feeds v1.2.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
golang.org/x/sys v0.4.0 // indirect
)
module my-web
go 1.18
require (
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 // indirect
github.com/gorilla/feeds v1.2.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
golang.org/x/sys v0.4.0 // indirect
)

1121
main.go

File diff suppressed because it is too large Load diff

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 {
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 */
}
/* 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;
}
img {
display: block;
width: 100%;
}
.align-blog p, a, em {
text-align: left;
font-size: 1em;
line-height: 1.6;
}
/* .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;
}
#particles-js {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1; /* This value must be lower than other elements! */
}
.reset-styles * {
all: unset;
display: revert;
}

View file

@ -1,139 +1,64 @@
/* Adjust the gallery wrapper */
.gallery-wrapper {
position: relative;
width: 100%;
max-width: 800px;
margin: 0 auto; /* Center the gallery */
overflow: hidden; /* Ensure content is within bounds */
}
.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 {
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%);
}
.slick-dots li.slick-active button::before {
background-color: #fff; /* Active dot background color */
border-color: #fff; /* Active dot border color */
}
/* 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;
}
/* If you want a specific shape, like a square or a different background */
.slick-prev, .slick-next {
border-radius: 0 !important;
background: transparent !important;
}
/* 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;
}
.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%;
height: 100%;
object-fit: cover;
transition: transform 0.6s ease, filter 0.6s ease;
}
.gallery-item::before {
content: '';
position: absolute;
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;
}
.gallery-item:hover {
transform: scale(1.05);
filter: brightness(1.2);
}
.gallery-item:hover img {
transform: scale(1.1) rotate(2deg);
filter: grayscale(20%)
}
.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);
}
}
.gallery-item {
animation: pulse 5s infinite;
} */

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

File diff suppressed because it is too large Load diff

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,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)
*/
(function($) {

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,22 +1,22 @@
///
// 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)
///
/* Icons */
ul.icons {
cursor: default;
list-style: none;
padding-left: 0;
li {
display: inline-block;
padding: 0 0.65em 0 0;
&:last-child {
padding-right: 0 !important;
}
}
///
/// Stellar by HTML5 UP
/// html5up.net | @ajlkn
/// Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
///
/* Icons */
ul.icons {
cursor: default;
list-style: none;
padding-left: 0;
li {
display: inline-block;
padding: 0 0.65em 0 0;
&:last-child {
padding-right: 0 !important;
}
}
}

View file

@ -1,223 +1,223 @@
// breakpoints.scss v1.0 | @ajlkn | MIT licensed */
// Vars.
/// Breakpoints.
/// @var {list}
$breakpoints: () !global;
// Mixins.
/// Sets breakpoints.
/// @param {map} $x Breakpoints.
@mixin breakpoints($x: ()) {
$breakpoints: $x !global;
}
/// Wraps @content in a @media block targeting a specific orientation.
/// @param {string} $orientation Orientation.
@mixin orientation($orientation) {
@media screen and (orientation: #{$orientation}) {
@content;
}
}
/// Wraps @content in a @media block using a given query.
/// @param {string} $query Query.
@mixin breakpoint($query: null) {
$breakpoint: null;
$op: null;
$media: null;
// Determine operator, breakpoint.
// Greater than or equal.
@if (str-slice($query, 0, 2) == '>=') {
$op: 'gte';
$breakpoint: str-slice($query, 3);
}
// Less than or equal.
@elseif (str-slice($query, 0, 2) == '<=') {
$op: 'lte';
$breakpoint: str-slice($query, 3);
}
// Greater than.
@elseif (str-slice($query, 0, 1) == '>') {
$op: 'gt';
$breakpoint: str-slice($query, 2);
}
// Less than.
@elseif (str-slice($query, 0, 1) == '<') {
$op: 'lt';
$breakpoint: str-slice($query, 2);
}
// Not.
@elseif (str-slice($query, 0, 1) == '!') {
$op: 'not';
$breakpoint: str-slice($query, 2);
}
// Equal.
@else {
$op: 'eq';
$breakpoint: $query;
}
// Build media.
@if ($breakpoint and map-has-key($breakpoints, $breakpoint)) {
$a: map-get($breakpoints, $breakpoint);
// Range.
@if (type-of($a) == 'list') {
$x: nth($a, 1);
$y: nth($a, 2);
// Max only.
@if ($x == null) {
// Greater than or equal (>= 0 / anything)
@if ($op == 'gte') {
$media: 'screen';
}
// Less than or equal (<= y)
@elseif ($op == 'lte') {
$media: 'screen and (max-width: ' + $y + ')';
}
// Greater than (> y)
@elseif ($op == 'gt') {
$media: 'screen and (min-width: ' + ($y + 1) + ')';
}
// Less than (< 0 / invalid)
@elseif ($op == 'lt') {
$media: 'screen and (max-width: -1px)';
}
// Not (> y)
@elseif ($op == 'not') {
$media: 'screen and (min-width: ' + ($y + 1) + ')';
}
// Equal (<= y)
@else {
$media: 'screen and (max-width: ' + $y + ')';
}
}
// Min only.
@else if ($y == null) {
// Greater than or equal (>= x)
@if ($op == 'gte') {
$media: 'screen and (min-width: ' + $x + ')';
}
// Less than or equal (<= inf / anything)
@elseif ($op == 'lte') {
$media: 'screen';
}
// Greater than (> inf / invalid)
@elseif ($op == 'gt') {
$media: 'screen and (max-width: -1px)';
}
// Less than (< x)
@elseif ($op == 'lt') {
$media: 'screen and (max-width: ' + ($x - 1) + ')';
}
// Not (< x)
@elseif ($op == 'not') {
$media: 'screen and (max-width: ' + ($x - 1) + ')';
}
// Equal (>= x)
@else {
$media: 'screen and (min-width: ' + $x + ')';
}
}
// Min and max.
@else {
// Greater than or equal (>= x)
@if ($op == 'gte') {
$media: 'screen and (min-width: ' + $x + ')';
}
// Less than or equal (<= y)
@elseif ($op == 'lte') {
$media: 'screen and (max-width: ' + $y + ')';
}
// Greater than (> y)
@elseif ($op == 'gt') {
$media: 'screen and (min-width: ' + ($y + 1) + ')';
}
// Less than (< x)
@elseif ($op == 'lt') {
$media: 'screen and (max-width: ' + ($x - 1) + ')';
}
// Not (< x and > y)
@elseif ($op == 'not') {
$media: 'screen and (max-width: ' + ($x - 1) + '), screen and (min-width: ' + ($y + 1) + ')';
}
// Equal (>= x and <= y)
@else {
$media: 'screen and (min-width: ' + $x + ') and (max-width: ' + $y + ')';
}
}
}
// String.
@else {
// Missing a media type? Prefix with "screen".
@if (str-slice($a, 0, 1) == '(') {
$media: 'screen and ' + $a;
}
// Otherwise, use as-is.
@else {
$media: $a;
}
}
}
// Output.
@media #{$media} {
@content;
}
// breakpoints.scss v1.0 | @ajlkn | MIT licensed */
// Vars.
/// Breakpoints.
/// @var {list}
$breakpoints: () !global;
// Mixins.
/// Sets breakpoints.
/// @param {map} $x Breakpoints.
@mixin breakpoints($x: ()) {
$breakpoints: $x !global;
}
/// Wraps @content in a @media block targeting a specific orientation.
/// @param {string} $orientation Orientation.
@mixin orientation($orientation) {
@media screen and (orientation: #{$orientation}) {
@content;
}
}
/// Wraps @content in a @media block using a given query.
/// @param {string} $query Query.
@mixin breakpoint($query: null) {
$breakpoint: null;
$op: null;
$media: null;
// Determine operator, breakpoint.
// Greater than or equal.
@if (str-slice($query, 0, 2) == '>=') {
$op: 'gte';
$breakpoint: str-slice($query, 3);
}
// Less than or equal.
@elseif (str-slice($query, 0, 2) == '<=') {
$op: 'lte';
$breakpoint: str-slice($query, 3);
}
// Greater than.
@elseif (str-slice($query, 0, 1) == '>') {
$op: 'gt';
$breakpoint: str-slice($query, 2);
}
// Less than.
@elseif (str-slice($query, 0, 1) == '<') {
$op: 'lt';
$breakpoint: str-slice($query, 2);
}
// Not.
@elseif (str-slice($query, 0, 1) == '!') {
$op: 'not';
$breakpoint: str-slice($query, 2);
}
// Equal.
@else {
$op: 'eq';
$breakpoint: $query;
}
// Build media.
@if ($breakpoint and map-has-key($breakpoints, $breakpoint)) {
$a: map-get($breakpoints, $breakpoint);
// Range.
@if (type-of($a) == 'list') {
$x: nth($a, 1);
$y: nth($a, 2);
// Max only.
@if ($x == null) {
// Greater than or equal (>= 0 / anything)
@if ($op == 'gte') {
$media: 'screen';
}
// Less than or equal (<= y)
@elseif ($op == 'lte') {
$media: 'screen and (max-width: ' + $y + ')';
}
// Greater than (> y)
@elseif ($op == 'gt') {
$media: 'screen and (min-width: ' + ($y + 1) + ')';
}
// Less than (< 0 / invalid)
@elseif ($op == 'lt') {
$media: 'screen and (max-width: -1px)';
}
// Not (> y)
@elseif ($op == 'not') {
$media: 'screen and (min-width: ' + ($y + 1) + ')';
}
// Equal (<= y)
@else {
$media: 'screen and (max-width: ' + $y + ')';
}
}
// Min only.
@else if ($y == null) {
// Greater than or equal (>= x)
@if ($op == 'gte') {
$media: 'screen and (min-width: ' + $x + ')';
}
// Less than or equal (<= inf / anything)
@elseif ($op == 'lte') {
$media: 'screen';
}
// Greater than (> inf / invalid)
@elseif ($op == 'gt') {
$media: 'screen and (max-width: -1px)';
}
// Less than (< x)
@elseif ($op == 'lt') {
$media: 'screen and (max-width: ' + ($x - 1) + ')';
}
// Not (< x)
@elseif ($op == 'not') {
$media: 'screen and (max-width: ' + ($x - 1) + ')';
}
// Equal (>= x)
@else {
$media: 'screen and (min-width: ' + $x + ')';
}
}
// Min and max.
@else {
// Greater than or equal (>= x)
@if ($op == 'gte') {
$media: 'screen and (min-width: ' + $x + ')';
}
// Less than or equal (<= y)
@elseif ($op == 'lte') {
$media: 'screen and (max-width: ' + $y + ')';
}
// Greater than (> y)
@elseif ($op == 'gt') {
$media: 'screen and (min-width: ' + ($y + 1) + ')';
}
// Less than (< x)
@elseif ($op == 'lt') {
$media: 'screen and (max-width: ' + ($x - 1) + ')';
}
// Not (< x and > y)
@elseif ($op == 'not') {
$media: 'screen and (max-width: ' + ($x - 1) + '), screen and (min-width: ' + ($y + 1) + ')';
}
// Equal (>= x and <= y)
@else {
$media: 'screen and (min-width: ' + $x + ') and (max-width: ' + $y + ')';
}
}
}
// String.
@else {
// Missing a media type? Prefix with "screen".
@if (str-slice($a, 0, 1) == '(') {
$media: 'screen and ' + $a;
}
// Otherwise, use as-is.
@else {
$media: $a;
}
}
}
// Output.
@media #{$media} {
@content;
}
}

View file

@ -1,376 +1,376 @@
// vendor.scss v1.0 | @ajlkn | MIT licensed */
// Vars.
/// Vendor prefixes.
/// @var {list}
$vendor-prefixes: (
'-moz-',
'-webkit-',
'-ms-',
''
);
/// Properties that should be vendorized.
/// Data via caniuse.com, github.com/postcss/autoprefixer, and developer.mozilla.org
/// @var {list}
$vendor-properties: (
// Animation.
'animation',
'animation-delay',
'animation-direction',
'animation-duration',
'animation-fill-mode',
'animation-iteration-count',
'animation-name',
'animation-play-state',
'animation-timing-function',
// Appearance.
'appearance',
// Backdrop filter.
'backdrop-filter',
// Background image options.
'background-clip',
'background-origin',
'background-size',
// Box sizing.
'box-sizing',
// Clip path.
'clip-path',
// Filter effects.
'filter',
// Flexbox.
'align-content',
'align-items',
'align-self',
'flex',
'flex-basis',
'flex-direction',
'flex-flow',
'flex-grow',
'flex-shrink',
'flex-wrap',
'justify-content',
'order',
// Font feature.
'font-feature-settings',
'font-language-override',
'font-variant-ligatures',
// Font kerning.
'font-kerning',
// Fragmented borders and backgrounds.
'box-decoration-break',
// Grid layout.
'grid-column',
'grid-column-align',
'grid-column-end',
'grid-column-start',
'grid-row',
'grid-row-align',
'grid-row-end',
'grid-row-start',
'grid-template-columns',
'grid-template-rows',
// Hyphens.
'hyphens',
'word-break',
// Masks.
'mask',
'mask-border',
'mask-border-outset',
'mask-border-repeat',
'mask-border-slice',
'mask-border-source',
'mask-border-width',
'mask-clip',
'mask-composite',
'mask-image',
'mask-origin',
'mask-position',
'mask-repeat',
'mask-size',
// Multicolumn.
'break-after',
'break-before',
'break-inside',
'column-count',
'column-fill',
'column-gap',
'column-rule',
'column-rule-color',
'column-rule-style',
'column-rule-width',
'column-span',
'column-width',
'columns',
// Object fit.
'object-fit',
'object-position',
// Regions.
'flow-from',
'flow-into',
'region-fragment',
// Scroll snap points.
'scroll-snap-coordinate',
'scroll-snap-destination',
'scroll-snap-points-x',
'scroll-snap-points-y',
'scroll-snap-type',
// Shapes.
'shape-image-threshold',
'shape-margin',
'shape-outside',
// Tab size.
'tab-size',
// Text align last.
'text-align-last',
// Text decoration.
'text-decoration-color',
'text-decoration-line',
'text-decoration-skip',
'text-decoration-style',
// Text emphasis.
'text-emphasis',
'text-emphasis-color',
'text-emphasis-position',
'text-emphasis-style',
// Text size adjust.
'text-size-adjust',
// Text spacing.
'text-spacing',
// Transform.
'transform',
'transform-origin',
// Transform 3D.
'backface-visibility',
'perspective',
'perspective-origin',
'transform-style',
// Transition.
'transition',
'transition-delay',
'transition-duration',
'transition-property',
'transition-timing-function',
// Unicode bidi.
'unicode-bidi',
// User select.
'user-select',
// Writing mode.
'writing-mode',
);
/// Values that should be vendorized.
/// Data via caniuse.com, github.com/postcss/autoprefixer, and developer.mozilla.org
/// @var {list}
$vendor-values: (
// Cross fade.
'cross-fade',
// Element function.
'element',
// Filter function.
'filter',
// Flexbox.
'flex',
'inline-flex',
// Grab cursors.
'grab',
'grabbing',
// Gradients.
'linear-gradient',
'repeating-linear-gradient',
'radial-gradient',
'repeating-radial-gradient',
// Grid layout.
'grid',
'inline-grid',
// Image set.
'image-set',
// Intrinsic width.
'max-content',
'min-content',
'fit-content',
'fill',
'fill-available',
'stretch',
// Sticky position.
'sticky',
// Transform.
'transform',
// Zoom cursors.
'zoom-in',
'zoom-out',
);
// Functions.
/// Removes a specific item from a list.
/// @author Hugo Giraudel
/// @param {list} $list List.
/// @param {integer} $index Index.
/// @return {list} Updated list.
@function remove-nth($list, $index) {
$result: null;
@if type-of($index) != number {
@warn "$index: #{quote($index)} is not a number for `remove-nth`.";
}
@else if $index == 0 {
@warn "List index 0 must be a non-zero integer for `remove-nth`.";
}
@else if abs($index) > length($list) {
@warn "List index is #{$index} but list is only #{length($list)} item long for `remove-nth`.";
}
@else {
$result: ();
$index: if($index < 0, length($list) + $index + 1, $index);
@for $i from 1 through length($list) {
@if $i != $index {
$result: append($result, nth($list, $i));
}
}
}
@return $result;
}
/// Replaces a substring within another string.
/// @author Hugo Giraudel
/// @param {string} $string String.
/// @param {string} $search Substring.
/// @param {string} $replace Replacement.
/// @return {string} Updated string.
@function str-replace($string, $search, $replace: '') {
$index: str-index($string, $search);
@if $index {
@return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
}
@return $string;
}
/// Replaces a substring within each string in a list.
/// @param {list} $strings List of strings.
/// @param {string} $search Substring.
/// @param {string} $replace Replacement.
/// @return {list} Updated list of strings.
@function str-replace-all($strings, $search, $replace: '') {
@each $string in $strings {
$strings: set-nth($strings, index($strings, $string), str-replace($string, $search, $replace));
}
@return $strings;
}
// Mixins.
/// Wraps @content in vendorized keyframe blocks.
/// @param {string} $name Name.
@mixin keyframes($name) {
@-moz-keyframes #{$name} { @content; }
@-webkit-keyframes #{$name} { @content; }
@-ms-keyframes #{$name} { @content; }
@keyframes #{$name} { @content; }
}
/// Vendorizes a declaration's property and/or value(s).
/// @param {string} $property Property.
/// @param {mixed} $value String/list of value(s).
@mixin vendor($property, $value) {
// Determine if property should expand.
$expandProperty: index($vendor-properties, $property);
// Determine if value should expand (and if so, add '-prefix-' placeholder).
$expandValue: false;
@each $x in $value {
@each $y in $vendor-values {
@if $y == str-slice($x, 1, str-length($y)) {
$value: set-nth($value, index($value, $x), '-prefix-' + $x);
$expandValue: true;
}
}
}
// Expand property?
@if $expandProperty {
@each $vendor in $vendor-prefixes {
#{$vendor}#{$property}: #{str-replace-all($value, '-prefix-', $vendor)};
}
}
// Expand just the value?
@elseif $expandValue {
@each $vendor in $vendor-prefixes {
#{$property}: #{str-replace-all($value, '-prefix-', $vendor)};
}
}
// Neither? Treat them as a normal declaration.
@else {
#{$property}: #{$value};
}
// vendor.scss v1.0 | @ajlkn | MIT licensed */
// Vars.
/// Vendor prefixes.
/// @var {list}
$vendor-prefixes: (
'-moz-',
'-webkit-',
'-ms-',
''
);
/// Properties that should be vendorized.
/// Data via caniuse.com, github.com/postcss/autoprefixer, and developer.mozilla.org
/// @var {list}
$vendor-properties: (
// Animation.
'animation',
'animation-delay',
'animation-direction',
'animation-duration',
'animation-fill-mode',
'animation-iteration-count',
'animation-name',
'animation-play-state',
'animation-timing-function',
// Appearance.
'appearance',
// Backdrop filter.
'backdrop-filter',
// Background image options.
'background-clip',
'background-origin',
'background-size',
// Box sizing.
'box-sizing',
// Clip path.
'clip-path',
// Filter effects.
'filter',
// Flexbox.
'align-content',
'align-items',
'align-self',
'flex',
'flex-basis',
'flex-direction',
'flex-flow',
'flex-grow',
'flex-shrink',
'flex-wrap',
'justify-content',
'order',
// Font feature.
'font-feature-settings',
'font-language-override',
'font-variant-ligatures',
// Font kerning.
'font-kerning',
// Fragmented borders and backgrounds.
'box-decoration-break',
// Grid layout.
'grid-column',
'grid-column-align',
'grid-column-end',
'grid-column-start',
'grid-row',
'grid-row-align',
'grid-row-end',
'grid-row-start',
'grid-template-columns',
'grid-template-rows',
// Hyphens.
'hyphens',
'word-break',
// Masks.
'mask',
'mask-border',
'mask-border-outset',
'mask-border-repeat',
'mask-border-slice',
'mask-border-source',
'mask-border-width',
'mask-clip',
'mask-composite',
'mask-image',
'mask-origin',
'mask-position',
'mask-repeat',
'mask-size',
// Multicolumn.
'break-after',
'break-before',
'break-inside',
'column-count',
'column-fill',
'column-gap',
'column-rule',
'column-rule-color',
'column-rule-style',
'column-rule-width',
'column-span',
'column-width',
'columns',
// Object fit.
'object-fit',
'object-position',
// Regions.
'flow-from',
'flow-into',
'region-fragment',
// Scroll snap points.
'scroll-snap-coordinate',
'scroll-snap-destination',
'scroll-snap-points-x',
'scroll-snap-points-y',
'scroll-snap-type',
// Shapes.
'shape-image-threshold',
'shape-margin',
'shape-outside',
// Tab size.
'tab-size',
// Text align last.
'text-align-last',
// Text decoration.
'text-decoration-color',
'text-decoration-line',
'text-decoration-skip',
'text-decoration-style',
// Text emphasis.
'text-emphasis',
'text-emphasis-color',
'text-emphasis-position',
'text-emphasis-style',
// Text size adjust.
'text-size-adjust',
// Text spacing.
'text-spacing',
// Transform.
'transform',
'transform-origin',
// Transform 3D.
'backface-visibility',
'perspective',
'perspective-origin',
'transform-style',
// Transition.
'transition',
'transition-delay',
'transition-duration',
'transition-property',
'transition-timing-function',
// Unicode bidi.
'unicode-bidi',
// User select.
'user-select',
// Writing mode.
'writing-mode',
);
/// Values that should be vendorized.
/// Data via caniuse.com, github.com/postcss/autoprefixer, and developer.mozilla.org
/// @var {list}
$vendor-values: (
// Cross fade.
'cross-fade',
// Element function.
'element',
// Filter function.
'filter',
// Flexbox.
'flex',
'inline-flex',
// Grab cursors.
'grab',
'grabbing',
// Gradients.
'linear-gradient',
'repeating-linear-gradient',
'radial-gradient',
'repeating-radial-gradient',
// Grid layout.
'grid',
'inline-grid',
// Image set.
'image-set',
// Intrinsic width.
'max-content',
'min-content',
'fit-content',
'fill',
'fill-available',
'stretch',
// Sticky position.
'sticky',
// Transform.
'transform',
// Zoom cursors.
'zoom-in',
'zoom-out',
);
// Functions.
/// Removes a specific item from a list.
/// @author Hugo Giraudel
/// @param {list} $list List.
/// @param {integer} $index Index.
/// @return {list} Updated list.
@function remove-nth($list, $index) {
$result: null;
@if type-of($index) != number {
@warn "$index: #{quote($index)} is not a number for `remove-nth`.";
}
@else if $index == 0 {
@warn "List index 0 must be a non-zero integer for `remove-nth`.";
}
@else if abs($index) > length($list) {
@warn "List index is #{$index} but list is only #{length($list)} item long for `remove-nth`.";
}
@else {
$result: ();
$index: if($index < 0, length($list) + $index + 1, $index);
@for $i from 1 through length($list) {
@if $i != $index {
$result: append($result, nth($list, $i));
}
}
}
@return $result;
}
/// Replaces a substring within another string.
/// @author Hugo Giraudel
/// @param {string} $string String.
/// @param {string} $search Substring.
/// @param {string} $replace Replacement.
/// @return {string} Updated string.
@function str-replace($string, $search, $replace: '') {
$index: str-index($string, $search);
@if $index {
@return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
}
@return $string;
}
/// Replaces a substring within each string in a list.
/// @param {list} $strings List of strings.
/// @param {string} $search Substring.
/// @param {string} $replace Replacement.
/// @return {list} Updated list of strings.
@function str-replace-all($strings, $search, $replace: '') {
@each $string in $strings {
$strings: set-nth($strings, index($strings, $string), str-replace($string, $search, $replace));
}
@return $strings;
}
// Mixins.
/// Wraps @content in vendorized keyframe blocks.
/// @param {string} $name Name.
@mixin keyframes($name) {
@-moz-keyframes #{$name} { @content; }
@-webkit-keyframes #{$name} { @content; }
@-ms-keyframes #{$name} { @content; }
@keyframes #{$name} { @content; }
}
/// Vendorizes a declaration's property and/or value(s).
/// @param {string} $property Property.
/// @param {mixed} $value String/list of value(s).
@mixin vendor($property, $value) {
// Determine if property should expand.
$expandProperty: index($vendor-properties, $property);
// Determine if value should expand (and if so, add '-prefix-' placeholder).
$expandValue: false;
@each $x in $value {
@each $y in $vendor-values {
@if $y == str-slice($x, 1, str-length($y)) {
$value: set-nth($value, index($value, $x), '-prefix-' + $x);
$expandValue: true;
}
}
}
// Expand property?
@if $expandProperty {
@each $vendor in $vendor-prefixes {
#{$vendor}#{$property}: #{str-replace-all($value, '-prefix-', $vendor)};
}
}
// Expand just the value?
@elseif $expandValue {
@each $vendor in $vendor-prefixes {
#{$property}: #{str-replace-all($value, '-prefix-', $vendor)};
}
}
// Neither? Treat them as a normal declaration.
@else {
#{$property}: #{$value};
}
}

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
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)
-->
<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,63 +1,63 @@
<!DOCTYPE HTML>
<!--
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)
-->
<html>
<head>
<link rel="icon" type="image/png" href="static/images/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="#f1f1f1" data-react-helmet="true" name="theme-color" />
<meta name="darkreader-lock">
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
<link rel="stylesheet" href="static/css/main.css" />
<link rel="stylesheet" href="static/css/stars.css" />
<noscript><link rel="stylesheet" href="static/css/noscript.css" /></noscript>
<style>
.download-section {
text-align: center;
padding: 2em 0;
}
<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/favicon.png" property="og:image" />
<meta content="#f1f1f1" data-react-helmet="true" name="theme-color" />
<meta name="darkreader-lock">
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
<link rel="stylesheet" href="static/css/main.css" />
<link rel="stylesheet" href="static/css/stars.css" />
<noscript><link rel="stylesheet" href="static/css/noscript.css" /></noscript>
<style>
.download-section {
text-align: center;
padding: 2em 0;
}
.download-buttons {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 1em;
}
.download-buttons {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 1em;
}
.download-button {
border-radius: 0.5em;
padding: 1em;
width: 220px;
height: 220px;
text-align: center;
transition: background-color 0.3s ease;
}
.download-button {
border-radius: 0.5em;
padding: 1em;
width: 220px;
height: 220px;
text-align: center;
transition: background-color 0.3s ease;
}
.download-button:hover {
background-color: #444;
}
.download-button:hover {
background-color: #444;
}
.download-button img {
width: 48px;
height: 48px;
}
.download-button img {
width: 48px;
height: 48px;
}
.download-button span {
display: block;
margin-top: 0.5em;
font-size: 1.2em;
}
</style>
</head>
.download-button span {
display: block;
margin-top: 0.5em;
font-size: 1.2em;
}
</style>
</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 id="stars"></div>
<div id="stars2"></div>
<div id="stars3"></div>
</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> -->
</div>
<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>
<script defer src="/static/js/autocomplete.js"></script>
</body>
</html>

View file

@ -1,131 +1,115 @@
<!DOCTYPE HTML>
<!--
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)
-->
<html>
<head>
<link rel="icon" type="image/png" href="static/images/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="#f1f1f1" data-react-helmet="true" name="theme-color" />
<meta name="darkreader-lock">
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
<link rel="stylesheet" href="/static/css/main.css" />
<link rel="stylesheet" href="/static/css/stars.css" />
<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>
<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; top: 0; width: 100%; z-index: 1000;">
🚧👷‍♂️ This site is under construction! Please check back later. 👷‍♀️🚧
</div> -->
<!-- Wrapper -->
<div id="wrapper">
<!-- Header -->
<header id="header" class="alt">
<span class="logo">
<a href="/"><img src="/static/images/logo.svg" alt="Spitfire Logo" /></a>
</span>
<h1>Spitfire Browser News</h1>
</header>
<!-- Main -->
<div id="main">
<!-- Stable Release Section -->
<section id="cta" class="main">
<div class="center-text">
<header class="major">
<div class="align-blog"> <p>{{.Content}}</p> </div>
</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>
<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>
</ul>
</footer>
</div>
</section>
</div>
<!-- Footer -->
<footer id="footer">
<section>
<h2>Support me</h2>
<ul class="icons">
<li>
<a href="#" class="icon alt">
<img src="/static/images/icons/regular/heart.svg" alt="LibrePay">
<span class="label">LibrePay</span>
</a>
</li>
<li>
<a href="https://weforgecode.xyz/Spitfire/" class="icon alt">
<img src="/static/images/icons/brands/git-alt.svg" alt="Forgejo">
<span class="label">Forgejo</span>
</a>
</li>
<li>
<a href="#" class="icon alt">
<img src="/static/images/icons/brands/youtube.svg" alt="YouTube">
<span class="label">YouTube</span>
</a>
</li>
</ul>
</section>
<section>
<p class="copyright">&copy; Spitfire Browser. Design based on <a href="https://html5up.net">HTML5 UP</a>.</p>
</section>
</footer>
</div>
<!-- Scripts -->
<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/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>
<!DOCTYPE HTML>
<!--
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="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/favicon.png" property="og:image" />
<meta content="#f1f1f1" data-react-helmet="true" name="theme-color" />
<meta name="darkreader-lock">
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
<link rel="stylesheet" href="/static/css/main.css" />
<link rel="stylesheet" href="/static/css/stars.css" />
<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>
<body class="is-preload">
<!-- Star Background Divs -->
<div id="star-background">
<div id="stars"></div>
<div id="stars2"></div>
<div id="stars3"></div>
</div>
<!-- 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>
<!-- Wrapper -->
<div id="wrapper">
<!-- Header -->
<header id="header" class="alt">
<span class="logo">
<a href="/"><img src="/static/images/logo.svg" alt="Spitfire Logo" /></a>
</span>
<h1>Spitfire Browser News</h1>
</header>
<!-- Main -->
<div id="main">
<!-- Stable Release Section -->
<section id="cta" class="main">
<div class="center-text">
<header class="major">
<div class="align-blog"> <p>{{.Content}}</p> </div>
</header>
<footer class="major">
<ul class="actions special">
{{if .PrevLink}}
<li><a href="{{.PrevLink}}" class="button primary small">Previous</a></li>
{{end}}
<li><a href="/" class="button primary small">Home</a></li>
{{if .NextLink}}
<li><a href="{{.NextLink}}" class="button primary small">Next</a></li>
{{end}}
</ul>
</footer>
</div>
</section>
</div>
<!-- Footer -->
<footer id="footer">
<section>
<h2>Support me</h2>
<ul class="icons">
<li>
<a href="#" class="icon alt">
<img src="/static/images/icons/regular/heart.svg" alt="LibrePay">
<span class="label">LibrePay</span>
</a>
</li>
<li>
<a href="https://weforgecode.xyz/Spitfire/" class="icon alt">
<img src="/static/images/icons/brands/git-alt.svg" alt="Forgejo">
<span class="label">Forgejo</span>
</a>
</li>
<li>
<a href="#" class="icon alt">
<img src="/static/images/icons/brands/youtube.svg" alt="YouTube">
<span class="label">YouTube</span>
</a>
</li>
</ul>
</section>
<section>
<p class="copyright">&copy; Spitfire Browser. Design based on <a href="https://html5up.net">HTML5 UP</a>.</p>
</section>
</footer>
</div>
<!-- Scripts -->
<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>
</body>
</html>