← back  |  source code

Backend (cbackend.go)

package main

//// who saw old ver new ver use less ai and more effincy more secure more clean i have worked on improve my backend and go skills i still use ai and i still indpend on ai on secuirty side
//// all names are changed by ai to made this code understandable
import (
	"compress/gzip"
	"crypto/rand"
	"encoding/json"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
	"html"
	"image"
	_ "image/gif"
	_ "image/jpeg"
	_ "image/png"
	"io"
	"mime"
	"mime/multipart"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"time"
)

var (
	db          *gorm.DB
	uploadDir   string
	hexColor    = regexp.MustCompile(`^#[0-9a-fA-F]{6}$`)
)

const (
	MaxMediaSize   = 50 << 20
	MaxProfileSize = 50 << 20
	MaxRequestSize = 55 << 20
	MaxUploadRAM   = 1 << 20
)

type user struct {
	ID               uint      `gorm:"primaryKey"`
	Username         string    `gorm:"uniqueIndex;not null" json:"username"`
	Displayname      string    `json:"displayname"`
	Userid           string    `gorm:"uniqueIndex;not null" json:"-"`
	Namecolor        string    `gorm:"default:#ffffff" json:"namecolor"`
	Bio              string    `gorm:"type:text" json:"bio"`
	Biocolor         string    `gorm:"default:#ffffff" json:"biocolor"`
	Jointext         string    `gorm:"default:since" json:"jointext"`
	Joincolor        string    `gorm:"default:#ffffff" json:"joincolor"`
	Avatar           string    `json:"avatar"`
	Banner           string    `json:"banner"`
	Profilebg        string    `json:"profilebg"`
	Tab1name         string    `gorm:"default:posts" json:"tab1name"`
	Tab2name         string    `gorm:"default:likes" json:"tab2name"`
	Editprofiletext  string    `gorm:"default:edit profile" json:"editprofiletext"`
	Editprofilecolor string    `gorm:"default:#ffffff" json:"editprofilecolor"`
	Tabcolor         string    `gorm:"default:#ffffff" json:"tabcolor"`
	Postcolor        string    `gorm:"default:#ffffff" json:"postcolor"`
	Cugrid           string    `gorm:"type:text" json:"cugrid"`
	Support          string    `gorm:"type:text" json:"support"`
	Supportlinks     string    `gorm:"type:text" json:"supportlinks"`
	Tms              bool      `gorm:"default:false" json:"-"`
	Dob              string    `json:"dob"`
	Createtime       time.Time `gorm:"index" json:"createtime"`
	Lastpost         time.Time `json:"-"`
	Lastseen         time.Time `gorm:"index" json:"-"`
}

type post struct {
	ID         uint      `gorm:"primaryKey"`
	Username   string    `gorm:"index;not null" json:"username"`
	Content    string    `gorm:"type:text" json:"content"`
	Image      string    `gorm:"type:text" json:"image"`
	Mediasize  string    `gorm:"type:text" json:"mediasize"`
	Starco     int       `gorm:"default:0" json:"starco"`
	Likeco     int       `gorm:"default:0" json:"likeco"`
	Viewsco    int       `gorm:"default:0" json:"viewco"`
	Commentco  int       `gorm:"default:0" json:"commentco"`
	Supporturl string    `json:"supporturl"`
	Createtime time.Time `gorm:"index" json:"time"`
}

type star struct {
	ID       uint   `gorm:"primaryKey"`
	Username string `gorm:"uniqueIndex:star_user_post"`
	Postid   uint   `gorm:"uniqueIndex:star_user_post"`
}

type like struct {
	ID       uint   `gorm:"primaryKey"`
	Username string `gorm:"uniqueIndex:like_user_post"`
	Postid   uint   `gorm:"uniqueIndex:like_user_post"`
}

type comment struct {
	ID         uint      `gorm:"primaryKey"`
	Postid     uint      `gorm:"index"`
	Username   string    `gorm:"index"`
	Content    string    `gorm:"type:text"`
	Createtime time.Time `gorm:"index"`
}

type postView struct {
	ID          uint      `json:"id"`
	Username    string    `json:"username"`
	DisplayName string    `json:"DisplayName"`
	Avatar      string    `json:"Avatar"`
	Content     string    `json:"content"`
	Image       string    `json:"image"`
	Mediasize   string    `json:"mediasize"`
	Starco      int       `json:"Starco"`
	Likeco      int       `json:"Likeco"`
	Viewsco     int       `json:"Viewsco"`
	Commentco   int       `json:"Commentco"`
	Supporturl  string    `json:"supporturl"`
	Time        time.Time `json:"time"`
}

type commentView struct {
	ID       uint      `json:"id"`
	Postid   uint      `json:"post_id"`
	Username string    `json:"username"`
	Avatar   string    `json:"avatar_path"`
	Content  string    `json:"content"`
	Time     time.Time `json:"time"`
}

type gzipWriter struct {
	http.ResponseWriter
	io.Writer
}

func (g gzipWriter) Write(data []byte) (int, error) { return g.Writer.Write(data) }

func generateUserID() string {
	const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
	result, randomBytes := make([]byte, 32), make([]byte, 32)
	if _, err := rand.Read(randomBytes); err != nil {
		return ""
	}
	for i := range result {
		result[i] = chars[int(randomBytes[i])%len(chars)]
	}
	return string(result)
}

func getSessionID(r *http.Request) string {
	if cookie, err := r.Cookie("session"); err == nil {
		return cookie.Value
	}
	return ""
}

func getUserBySession(r *http.Request) (u user, ok bool) {
	session := getSessionID(r)
	if session == "" {
		return user{}, false
	}
	db.Where("userid = ?", session).First(&u)
	return u, u.ID != 0
}

func validColor(value string) bool { return hexColor.MatchString(value) }

func initUploadDirectory() string {
	path := os.Getenv("UPLOAD_PATH")
	if path == "" {
		path = "uploads"
	}
	if os.MkdirAll(path, 0755) != nil {
		return ""
	}
	index := filepath.Join(path, "index.html")
	if _, err := os.Stat(index); err != nil {
		_ = os.WriteFile(index, []byte("."), 0644)
	}
	return path
}

func initDatabase() bool {
	if uploadDir = initUploadDirectory(); uploadDir == "" {
		return false
	}
	path := os.Getenv("DB_PATH")
	if path == "" {
		path = "cuzmodata.db"
	}
	var err error
	if db, err = gorm.Open(sqlite.Open(path), &gorm.Config{Logger: logger.Discard}); err != nil {
		return false
	}
	db.Exec("PRAGMA journal_mode=WAL")
	db.Exec("PRAGMA busy_timeout=5000")
	db.Exec("PRAGMA cache_size=-2000")
	if sqlDB, err := db.DB(); err == nil {
		sqlDB.SetMaxOpenConns(6)
		sqlDB.SetMaxIdleConns(2)
		sqlDB.SetConnMaxIdleTime(60 * time.Second)
	} else {
		return false
	}
	schema := []string{
		`CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, displayname TEXT, userid TEXT UNIQUE NOT NULL, namecolor TEXT DEFAULT '#ffffff', bio TEXT, biocolor TEXT DEFAULT '#ffffff', jointext TEXT DEFAULT 'since', joincolor TEXT DEFAULT '#ffffff', avatar TEXT, banner TEXT, profilebg TEXT, tab1name TEXT DEFAULT 'posts', tab2name TEXT DEFAULT 'likes', editprofiletext TEXT DEFAULT 'edit profile', editprofilecolor TEXT DEFAULT '#ffffff', tabcolor TEXT DEFAULT '#ffffff', postcolor TEXT DEFAULT '#ffffff', cugrid TEXT, support TEXT, supportlinks TEXT, tms BOOLEAN DEFAULT FALSE, dob TEXT, createtime DATETIME, lastpost DATETIME, lastseen DATETIME)`,
		`CREATE TABLE IF NOT EXISTS posts (id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL, content TEXT, image TEXT, mediasize TEXT, starco INTEGER DEFAULT 0, likeco INTEGER DEFAULT 0, viewsco INTEGER DEFAULT 0, commentco INTEGER DEFAULT 0, supporturl TEXT, createtime DATETIME)`,
		`CREATE TABLE IF NOT EXISTS stars (id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL, postid INTEGER NOT NULL, UNIQUE(username, postid))`,
		`CREATE TABLE IF NOT EXISTS likes (id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL, postid INTEGER NOT NULL, UNIQUE(username, postid))`,
		`CREATE TABLE IF NOT EXISTS comments (id INTEGER PRIMARY KEY AUTOINCREMENT, postid INTEGER NOT NULL, username TEXT NOT NULL, content TEXT, createtime DATETIME)`,
		`CREATE TABLE IF NOT EXISTS post_views (id INTEGER PRIMARY KEY AUTOINCREMENT, post_id INTEGER NOT NULL, user_id TEXT NOT NULL, UNIQUE(post_id, user_id))`,
		`CREATE INDEX IF NOT EXISTS idx_posts_username ON posts(username)`,
		`CREATE INDEX IF NOT EXISTS idx_posts_createtime ON posts(createtime)`,
		`CREATE INDEX IF NOT EXISTS idx_likes_username ON likes(username)`,
		`CREATE INDEX IF NOT EXISTS idx_comments_postid ON comments(postid)`,
	}
	for _, stmt := range schema {
		db.Exec(stmt)
	}
	return true
}

func parseUint(value string) uint {
	number, err := strconv.ParseUint(value, 10, 64)
	if err != nil {
		return 0
	}
	return uint(number)
}

func getImageSize(path string) (int, int) {
	file, err := os.Open(path)
	if err != nil {
		return 0, 0
	}
	defer file.Close()
	if config, _, err := image.DecodeConfig(file); err == nil {
		return config.Width, config.Height
	}
	return 0, 0
}

func validateURL(value string) bool {
	if value == "" || !strings.HasPrefix(value, "https:////") {
		return false
	}
	u, err := url.Parse(value)
	return err == nil && u.Hostname() != ""
}

func isMobileUserAgent(r *http.Request) bool {
	ua := strings.ToLower(r.UserAgent())
	for _, m := range []string{"android", "iphone", "ipad", "ipod", "mobile", "windows phone", "opera mini", "blackberry"} {
		if strings.Contains(ua, m) {
			return true
		}
	}
	return false
}

func saveUploadedFile(header *multipart.FileHeader) (string, int, int) {
	if header == nil || header.Size > MaxMediaSize {
		return "", 0, 0
	}
	ext := strings.ToLower(filepath.Ext(header.Filename))
	if !map[string]bool{".jpg": true, ".jpeg": true, ".png": true, ".gif": true, ".mp4": true, ".webm": true}[ext] {
		return "", 0, 0
	}
	file, err := header.Open()
	if err != nil {
		return "", 0, 0
	}
	defer file.Close()
	buffer := make([]byte, 512)
	n, err := file.Read(buffer)
	if err != nil || n == 0 {
		return "", 0, 0
	}
	if _, err := file.Seek(0, 0); err != nil {
		return "", 0, 0
	}
	contentType := http.DetectContentType(buffer[:n])
	if !strings.HasPrefix(contentType, "image/") && !strings.HasPrefix(contentType, "video/") {
		return "", 0, 0
	}
	name := generateUserID()
	if name == "" {
		return "", 0, 0
	}
	path := filepath.Join(uploadDir, name+ext)
	dst, err := os.Create(path)
	if err != nil {
		return "", 0, 0
	}
	written, err := io.Copy(dst, io.LimitReader(file, MaxMediaSize+1))
	closeErr := dst.Close()
	if err != nil || closeErr != nil || written > MaxMediaSize {
		os.Remove(path)
		return "", 0, 0
	}
	width, height := getImageSize(path)
	if strings.HasPrefix(contentType, "image/") && (width == 0 || height == 0 || width > 10000 || height > 10000) {
		os.Remove(path)
		return "", 0, 0
	}
	return "uploads/" + filepath.Base(path), width, height
}

func saveProfileFile(header *multipart.FileHeader, prefix string) string {
	return saveProfileFileOpt(header, prefix, false)
}

func saveProfileFileOpt(header *multipart.FileHeader, prefix string, allowVideo bool) string {
	if header == nil || header.Size > MaxProfileSize {
		return ""
	}
	ext := strings.ToLower(filepath.Ext(header.Filename))
	valid := map[string]bool{".jpg": true, ".jpeg": true, ".png": true, ".gif": true}
	if allowVideo {
		valid[".mp4"] = true
		valid[".webm"] = true
	}
	if !valid[ext] {
		return ""
	}
	file, err := header.Open()
	if err != nil {
		return ""
	}
	defer file.Close()
	buffer := make([]byte, 512)
	n, err := file.Read(buffer)
	if err != nil || n == 0 {
		return ""
	}
	if _, err := file.Seek(0, 0); err != nil {
		return ""
	}
	ctype := http.DetectContentType(buffer[:n])
	if !strings.HasPrefix(ctype, "image/") && !(allowVideo && strings.HasPrefix(ctype, "video/")) {
		return ""
	}
	filename := prefix + "_" + generateUserID() + ext
	path := filepath.Join(uploadDir, filename)
	dst, err := os.Create(path)
	if err != nil {
		return ""
	}
	written, err := io.Copy(dst, io.LimitReader(file, MaxProfileSize+1))
	closeErr := dst.Close()
	if err != nil || closeErr != nil || written > MaxProfileSize {
		os.Remove(path)
		return ""
	}
	if strings.HasPrefix(ctype, "video/") {
		return "uploads/" + filepath.Base(path)
	}
	width, height := getImageSize(path)
	if width == 0 || height == 0 || width > 10000 || height > 10000 {
		os.Remove(path)
		return ""
	}
	return "uploads/" + filepath.Base(path)
}

func createAccountHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		return
	}
	r.Body = http.MaxBytesReader(w, r.Body, 1<<20)
	username := strings.TrimSpace(r.FormValue("username"))
	if len(username) < 3 || len(username) > 32 || username == "username" {
		return
	}
	userID := generateUserID()
	now := time.Now().UTC()
	newUser := user{
		Username: username, Displayname: username, Userid: userID, Namecolor: "#ffffff",
		Jointext: "since", Joincolor: "#ffffff", Tab1name: "posts", Tab2name: "likes",
		Editprofiletext: "edit profile", Editprofilecolor: "#ffffff", Tabcolor: "#ffffff",
		Postcolor: "#ffffff", Createtime: now, Lastseen: now,
	}
	db.Create(&newUser)
	http.SetCookie(w, &http.Cookie{Name: "session", Value: userID, Path: "/", MaxAge: 3000000, HttpOnly: true, Secure: true, SameSite: http.SameSiteStrictMode})
	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
	_, _ = w.Write([]byte(userID))
}

func loginHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method == http.MethodGet {
		http.ServeFile(w, r, "cfrontend/login.html")
		return
	}
	if r.Method != http.MethodPost {
		return
	}
	r.Body = http.MaxBytesReader(w, r.Body, 1<<20)
	userID := strings.TrimSpace(r.FormValue("id"))
	if len(userID) != 32 {
		return
	}
	var currentUser user
	db.Where("userid = ?", userID).First(&currentUser)
	if currentUser.ID == 0 {
		return
	}
	http.SetCookie(w, &http.Cookie{Name: "session", Value: currentUser.Userid, Path: "/", MaxAge: 3000000, HttpOnly: true, Secure: true, SameSite: http.SameSiteStrictMode})
	db.Model(&currentUser).Update("lastseen", time.Now().UTC())
}

func logoutHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		return
	}
	http.SetCookie(w, &http.Cookie{Name: "session", Value: "", Path: "/", MaxAge: -1, HttpOnly: true, Secure: true, SameSite: http.SameSiteStrictMode})
}

func deleteUploadedFile(file string) {
	if name := filepath.Base(file); file != "" && name != "." && name != string(filepath.Separator) {
		_ = os.Remove(filepath.Join(uploadDir, name))
	}
}

func deleteUserFiles(username string) {
	var currentUser user
	db.Where("username = ?", username).First(&currentUser)
	if currentUser.ID == 0 {
		return
	}
	deleteUploadedFile(currentUser.Avatar)
	deleteUploadedFile(currentUser.Banner)
	deleteUploadedFile(currentUser.Profilebg)
	var posts []post
	db.Where("username = ?", username).Find(&posts)
	for _, p := range posts {
		var media []string
		if json.Unmarshal([]byte(p.Image), &media) == nil {
			for _, path := range media {
				deleteUploadedFile(path)
			}
		}
	}
}

func deleteAccountHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		return
	}
	currentUser, ok := getUserBySession(r)
	if !ok {
		return
	}
	deleteUserFiles(currentUser.Username)
	db.Transaction(func(tx *gorm.DB) error {
		tx.Where("username = ?", currentUser.Username).Delete(&post{})
		tx.Where("username = ?", currentUser.Username).Delete(&comment{})
		tx.Where("username = ?", currentUser.Username).Delete(&star{})
		tx.Where("username = ?", currentUser.Username).Delete(&like{})
		return tx.Delete(&currentUser).Error
	})
	logoutHandler(w, r)
	_, _ = w.Write([]byte("deleted"))
}

func saveDateOfBirthHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		return
	}
	currentUser, ok := getUserBySession(r)
	if !ok {
		return
	}
	r.Body = http.MaxBytesReader(w, r.Body, 1<<20)
	dob := strings.TrimSpace(r.FormValue("dob"))
	if len(dob) <= 20 {
		_ = db.Model(&currentUser).Update("dob", dob).Error
	}
}

func createPostHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method == http.MethodGet {
		if _, ok := getUserBySession(r); !ok {
			http.Redirect(w, r, "/login", http.StatusFound)
			return
		}
		http.ServeFile(w, r, "cfrontend/createpage.html")
		return
	}
	if r.Method != http.MethodPost {
		return
	}
	currentUser, ok := getUserBySession(r)
	if !ok {
		return
	}
	r.Body = http.MaxBytesReader(w, r.Body, MaxRequestSize)
	if strings.HasPrefix(r.Header.Get("Content-Type"), "multipart/form-data") {
		if r.ParseMultipartForm(MaxUploadRAM) != nil {
			return
		}
	} else if r.ParseForm() != nil {
		return
	}
	content := strings.TrimSpace(r.FormValue("content"))
	if len(content) > 500000 {
		return
	}
	var files []*multipart.FileHeader
	if r.MultipartForm != nil {
		files = r.MultipartForm.File["media"]
	}
	if len(files) > 20 {
		return
	}
	media := make([]string, 0)
	mediaSize := make(map[string]map[string]int)
	for _, file := range files {
		path, width, height := saveUploadedFile(file)
		if path == "" {
			continue
		}
		media = append(media, path)
		if width > 0 && height > 0 {
			mediaSize[path] = map[string]int{"w": width, "h": height}
		}
	}
	if content == "" && len(media) == 0 {
		return
	}
	mediaJSON, err1 := json.Marshal(media)
	sizeJSON, err2 := json.Marshal(mediaSize)
	if err1 != nil || err2 != nil {
		return
	}
	newPost := post{Username: currentUser.Username, Content: content, Image: string(mediaJSON), Mediasize: string(sizeJSON), Createtime: time.Now().UTC()}
	if support := r.FormValue("supporturl"); validateURL(support) {
		newPost.Supporturl = support
	}
	if db.Create(&newPost).Error == nil {
		w.WriteHeader(http.StatusCreated)
	}
}

func addStarHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		return
	}
	currentUser, ok := getUserBySession(r)
	postID := parseUint(r.FormValue("post_id"))
	if !ok || postID == 0 {
		return
	}
	if db.Create(&star{Username: currentUser.Username, Postid: postID}).Error == nil {
		db.Model(&post{}).Where("id = ?", postID).Update("starco", gorm.Expr("starco + ?", 1))
	}
}

func addLikeHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		return
	}
	currentUser, ok := getUserBySession(r)
	postID := parseUint(r.FormValue("post_id"))
	if !ok || postID == 0 {
		return
	}
	if db.Create(&like{Username: currentUser.Username, Postid: postID}).Error == nil {
		db.Model(&post{}).Where("id = ?", postID).Update("likeco", gorm.Expr("likeco + ?", 1))
	}
}

func viewPostHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet && r.Method != http.MethodPost {
		return
	}
	postID := parseUint(r.FormValue("post_id"))
	if postID == 0 {
		return
	}
	currentUser, ok := getUserBySession(r)
	if !ok {
		return
	}
	result := db.Exec("INSERT OR IGNORE INTO post_views (post_id, user_id) VALUES (?, ?)", postID, currentUser.Userid)
	if result.RowsAffected > 0 {
		db.Model(&post{}).Where("id = ?", postID).Update("viewsco", gorm.Expr("viewsco + ?", 1))
	}
	var views int
	db.Raw("SELECT viewsco FROM posts WHERE id = ?", postID).Scan(&views)
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(map[string]int{"viewsco": views})
}

func createCommentHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		return
	}
	currentUser, ok := getUserBySession(r)
	if !ok {
		return
	}
	r.Body = http.MaxBytesReader(w, r.Body, 1<<20)
	postID, content := parseUint(r.FormValue("post_id")), strings.TrimSpace(r.FormValue("content"))
	if postID == 0 || content == "" || len(content) > 5000 {
		return
	}
	if db.Create(&comment{Postid: postID, Username: currentUser.Username, Content: content, Createtime: time.Now().UTC()}).Error == nil {
		db.Model(&post{}).Where("id = ?", postID).Update("commentco", gorm.Expr("commentco + ?", 1))
	}
}

const postsSelect = `posts.id, posts.username, users.displayname as display_name, users.avatar, posts.content, posts.image, posts.mediasize, posts.starco, posts.likeco, posts.viewsco, posts.commentco, posts.supporturl, posts.createtime as time`

func getPostsQuery(limit, offset int) []postView {
	if limit <= 0 || limit > 200 {
		limit = 50
	}
	if offset < 0 {
		offset = 0
	}
	posts := make([]postView, 0)
	db.Table("posts").Select(postsSelect).
		Joins("left join users on users.username = posts.username").Order("posts.createtime desc").Limit(limit).Offset(offset).Scan(&posts)
	return posts
}

func feedHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		return
	}
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(getPostsQuery(int(parseUint(r.FormValue("limit"))), int(parseUint(r.FormValue("offset")))))
}

func trendingPostsHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		return
	}
	posts := make([]postView, 0)
	db.Table("posts").Select(postsSelect).
		Joins("left join users on users.username = posts.username").Order("posts.starco desc").Limit(100).Scan(&posts)
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(posts)
}

func getCommentsHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		return
	}
	postID := parseUint(r.FormValue("post_id"))
	if postID == 0 {
		return
	}
	comments := make([]commentView, 0)
	db.Table("comments").Select(`comments.id, comments.postid, comments.username, users.avatar, comments.content, comments.createtime as time`).
		Joins("left join users on users.username = comments.username").Where("comments.postid = ?", postID).Order("comments.createtime desc").Limit(50).Scan(&comments)
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(comments)
}

func getUserProfileHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		return
	}
	username := strings.TrimSpace(r.FormValue("username"))
	if username == "" {
		return
	}
	var profile user
	if db.Where("username = ?", username).First(&profile).Error == nil {
		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(profile)
	}
}

func getMyProfileHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet && r.Method != http.MethodPost {
		return
	}
	currentUser, ok := getUserBySession(r)
	if !ok {
		w.WriteHeader(http.StatusUnauthorized)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(currentUser)
}

func getUserPostsHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		return
	}
	username := strings.TrimSpace(r.FormValue("username"))
	if username == "" {
		return
	}
	limit, offset := int(parseUint(r.FormValue("limit"))), int(parseUint(r.FormValue("offset")))
	if limit <= 0 || limit > 200 {
		limit = 100
	}
	if offset < 0 {
		offset = 0
	}
	posts := make([]postView, 0)
	db.Table("posts").Select(postsSelect).
		Joins("left join users on users.username = posts.username").Where("posts.username = ?", username).Order("posts.createtime desc").Limit(limit).Offset(offset).Scan(&posts)
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(posts)
}

func updateProfileHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		return
	}
	currentUser, ok := getUserBySession(r)
	if !ok {
		return
	}
	r.Body = http.MaxBytesReader(w, r.Body, MaxRequestSize)
	if strings.HasPrefix(r.Header.Get("Content-Type"), "multipart/form-data") {
		if r.ParseMultipartForm(MaxUploadRAM) != nil {
			return
		}
	} else if r.ParseForm() != nil {
		return
	}
	update := map[string]interface{}{}
	if value := strings.TrimSpace(r.FormValue("displayname")); value != "" && len(value) <= 100 {
		update["displayname"] = value
	}
	for _, field := range []string{"namecolor", "biocolor", "joincolor", "editprofilecolor", "tabcolor", "postcolor"} {
		if value := strings.TrimSpace(r.FormValue(field)); validColor(value) {
			update[field] = value
		}
	}
	textFields := map[string]int{"bio": 5000, "jointext": 50, "tab1name": 50, "tab2name": 50, "editprofiletext": 100, "cugrid": 5000}
	for field, limit := range textFields {
		if value := strings.TrimSpace(r.FormValue(field)); value != "" && len(value) <= limit {
			update[field] = value
		}
	}
	if _, avatarHeader, _ := r.FormFile("avatar"); avatarHeader != nil {
		if file := saveProfileFile(avatarHeader, "avatar"); file != "" {
			deleteUploadedFile(currentUser.Avatar)
			update["avatar"] = file
		}
	}
	if _, bannerHeader, _ := r.FormFile("banner"); bannerHeader != nil {
		if file := saveProfileFile(bannerHeader, "banner"); file != "" {
			deleteUploadedFile(currentUser.Banner)
			update["banner"] = file
		}
	}
	if _, backgroundHeader, _ := r.FormFile("profilebg"); backgroundHeader != nil {
		if file := saveProfileFileOpt(backgroundHeader, "profilebg", true); file != "" {
			deleteUploadedFile(currentUser.Profilebg)
			update["profilebg"] = file
		}
	}
	var links []string
	for i := 0; i < 10; i++ {
		if link := strings.TrimSpace(r.FormValue("support" + strconv.Itoa(i))); validateURL(link) {
			links = append(links, link)
		}
	}
	if len(links) > 0 {
		if data, err := json.Marshal(links); err == nil {
			update["supportlinks"] = string(data)
		}
	}
	if len(update) > 0 {
		_ = db.Model(&currentUser).Updates(update).Error
	}
}

func checkSupportHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		return
	}
	link := strings.TrimSpace(r.FormValue("url"))
	valid := validateURL(link)
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(map[string]interface{}{"valid": valid, "name": ""})
}

func signTMSHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		return
	}
	currentUser, ok := getUserBySession(r)
	if !ok {
		return
	}
	db.Model(&currentUser).Update("tms", true)
	_, _ = w.Write([]byte("yr account will not restored so save it " + currentUser.Userid))
}

func lastPostHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		return
	}
	var pv postView
	err := db.Table("posts").Select(postsSelect).
		Joins("left join users on users.username = posts.username").Order("posts.id desc").Limit(1).Scan(&pv).Error
	if err != nil || pv.ID == 0 {
		return
	}
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(pv)
}

func getUserLikedHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		return
	}
	username := strings.TrimSpace(r.FormValue("username"))
	if username == "" {
		return
	}
	limit, offset := int(parseUint(r.FormValue("limit"))), int(parseUint(r.FormValue("offset")))
	if limit <= 0 || limit > 200 {
		limit = 100
	}
	if offset < 0 {
		offset = 0
	}
	posts := make([]postView, 0)
	db.Table("likes").Select(postsSelect).
		Joins("join posts on posts.id = likes.postid").Joins("left join users on users.username = posts.username").Where("likes.username = ?", username).Order("likes.id desc").Limit(limit).Offset(offset).Scan(&posts)
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(posts)
}

func userStatsHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		return
	}
	var total, active int64
	db.Model(&user{}).Count(&total)
	db.Model(&user{}).Where("lastseen > ?", time.Now().UTC().Add(-24*time.Hour)).Count(&active)
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(map[string]interface{}{"total": total, "active_24h": active})
}

type sideUser struct {
	Username    string    `json:"username"`
	Displayname string    `json:"displayname"`
	Avatar      string    `json:"avatar"`
	Createtime  time.Time `json:"createtime"`
}

func sideCardsHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		return
	}
	var total int64
	db.Model(&user{}).Count(&total)

	newest := make([]sideUser, 0, 30)
	db.Model(&user{}).Select("username, displayname, avatar, createtime").Order("createtime desc").Limit(30).Scan(&newest)

	oldest := make([]sideUser, 0, 5)
	db.Model(&user{}).Select("username, displayname, avatar, createtime").Order("createtime asc").Limit(5).Scan(&oldest)

	var newestOne sideUser
	db.Model(&user{}).Select("username, displayname, avatar, createtime").Order("createtime desc").Limit(1).Scan(&newestOne)

	resp := map[string]interface{}{
		"total": total, "newest": newest, "oldest": oldest, "newest_one": newestOne,
	}

	if strings.TrimSpace(r.FormValue("all")) == "1" {
		all := make([]sideUser, 0)
		db.Model(&user{}).Select("username, displayname, avatar, createtime").Order("createtime desc").Scan(&all)
		resp["all"] = all
	}

	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(resp)
}

func cugridHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		return
	}
	currentUser, ok := getUserBySession(r)
	if !ok {
		return
	}
	r.Body = http.MaxBytesReader(w, r.Body, 1<<20)
	data := strings.TrimSpace(r.FormValue("layout_data"))
	if data == "" {
		_ = db.Model(&currentUser).Update("cugrid", "").Error
		return
	}
	if len(data) <= 500000 {
		_ = db.Model(&currentUser).Update("cugrid", data).Error
	}
}

func appMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("X-Content-Type-Options", "nosniff")
		w.Header().Set("X-Frame-Options", "DENY")
		w.Header().Set("Referrer-Policy", "no-referrer")
		w.Header().Set("Strict-Transport-Security", "max-age=31536000")
		w.Header().Set("Content-Security-Policy", "default-src 'self'; base-uri 'none'; form-action 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https:////fonts.googleapis.com https://cdn.jsdelivr.net; font-src 'self' https://fonts.gstatic.com https://cdn.jsdelivr.net; img-src 'self' data: blob: https://api.dicebear.com; media-src 'self'; object-src 'none'; frame-ancestors 'none'")
		if strings.HasPrefix(r.URL.Path, "/uploads/") {
			w.Header().Set("Cache-Control", "public, max-age=2592000, immutable")
		} else if strings.HasPrefix(r.URL.Path, "/cfrontend/cubg/") {
			w.Header().Set("Cache-Control", "public, max-age=2592000, immutable")
		}
		next.ServeHTTP(w, r)
	})
}

func recoverMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer func() { _ = recover() }()
		next.ServeHTTP(w, r)
	})
}

func gzipHandler(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if strings.HasPrefix(r.URL.Path, "/uploads/") || strings.HasPrefix(r.URL.Path, "/cfrontend/") || !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
			next.ServeHTTP(w, r)
			return
		}
		w.Header().Set("Content-Encoding", "gzip")
		w.Header().Add("Vary", "Accept-Encoding")
		gzipData := gzip.NewWriter(w)
		defer gzipData.Close()
		next.ServeHTTP(gzipWriter{ResponseWriter: w, Writer: gzipData}, r)
	})
}

func servePage(file string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodGet {
			return
		}
		http.ServeFile(w, r, file)
	}
}

func serveProtectedPage(file string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodGet {
			return
		}
		if _, ok := getUserBySession(r); !ok {
			http.Redirect(w, r, "/login", http.StatusFound)
			return
		}
		http.ServeFile(w, r, file)
	}
}

var sourceFiles = []struct{ Name, Path string }{
	{"Backend  (cbackend.go)", "cbackend.go"}, {"Frontend  (shared.css)", "cfrontend/shared.css"},
	{"Frontend  (shared.js)", "cfrontend/shared.js"}, {"Page  (cuorbit.html)", "cfrontend/cuorbit.html"}, {"Page  (cuorbit-phone.html)", "cfrontend/cuorbit-phone.html"},
	{"Page  (login.html)", "cfrontend/login.html"}, {"Page  (login-phone.html)", "cfrontend/login-phone.html"}, {"Page  (profile.html)", "cfrontend/profile.html"}, {"Page  (profile-phone.html)", "cfrontend/profile-phone.html"},
	{"Page  (userbase.html)", "cfrontend/userbase.html"}, {"Page  (createpage.html)", "cfrontend/createpage.html"},
	{"Dockerfile", "Dockerfile"}, {"fly.toml", "fly.toml"}, {"go.mod", "go.mod"}, {"License  (BSL 1.1)", "LICENSE"},
}

func sourceHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		return
	}
	type block struct{ Name, Code string }
	var blocks []block
	for _, f := range sourceFiles {
		if data, err := os.ReadFile(f.Path); err == nil {
			blocks = append(blocks, block{f.Name, string(data)})
		}
	}
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	w.Header().Set("X-Content-Type-Options", "nosniff")
	var b strings.Builder
	b.WriteString(`<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Cuzmo · source code</title><style>body{background:#000;color:#d4d4d4;font-family:monospace;margin:0;padding:24px;line-height:1.5}h1{color:#fff;font-size:16px;letter-spacing:2px}h2{color:#ff595e;font-size:14px;margin:36px 0 8px;border-bottom:1px solid #333;padding-bottom:4px}pre{border:1px solid #222;background:#0a0a0a;padding:14px;border-radius:6px;overflow-x:auto;white-space:pre;font-size:13px;line-height:1.5}a{color:#7aa2ff;text-decoration:none}.top{margin-bottom:18px}.k{color:#569cd6}.c{color:#6a9955}.s{color:#ce9178}.n{color:#b5cea8}.p{color:#d4d4d4}</style></head><body><div class="top"><a href="/cuorbit">&larr; back</a> &nbsp;|&nbsp; <span>source code</span></div>`)
	for _, blk := range blocks {
		b.WriteString("<h2>" + html.EscapeString(blk.Name) + "</h2><pre><code>" + highlight(html.EscapeString(blk.Code)) + "</code></pre>")
	}
	b.WriteString(`</body></html>`)
	_, _ = w.Write([]byte(b.String()))
}

func highlight(code string) string {
	var b strings.Builder
	i, n, inStr, inCom := 0, len(code), false, false
	for i < n {
		if inStr {
			b.WriteByte(code[i])
			if code[i] == '\\' && i+1 < n {
				i++
				b.WriteByte(code[i])
			} else if code[i] == '"' {
				b.WriteString("</span>")
				inStr = false
			}
			i++
			continue
		}
		if inCom {
			if strings.HasPrefix(code[i:], "*/") {
				b.WriteString("*/</span>")
				inCom = false
				i += 2
				continue
			}
			b.WriteByte(code[i])
			i++
			continue
		}
		if code[i] == '"' {
			b.WriteString("<span class=\"s\">&quot;")
			inStr = true
			i++
			continue
		}
		if strings.HasPrefix(code[i:], "////") {
			b.WriteString("<span class=\"c\">////")
			for i < n && code[i] != '\n' {
				b.WriteByte(code[i])
				i++
			}
			b.WriteString("</span>")
			continue
		}
		if strings.HasPrefix(code[i:], "/*") {
			b.WriteString("<span class=\"c\">/*")
			inCom = true
			i += 2
			continue
		}
		if isKeywordStart(code, i) {
			b.WriteString("<span class=\"k\">")
			for i < n && isIdentChar(code[i]) {
				b.WriteByte(code[i])
				i++
			}
			b.WriteString("</span>")
			continue
		}
		b.WriteByte(code[i])
		i++
	}
	return b.String()
}

func isKeywordStart(code string, i int) bool {
	if i > 0 && isIdentChar(code[i-1]) {
		return false
	}
	for _, kw := range []string{"func", "return", "if", "else", "for", "var", "const", "type", "struct", "map", "error", "nil", "true", "false", "package", "import", "go", "make", "len", "range", "switch", "case", "default", "defer", "string", "int"} {
		if strings.HasPrefix(code[i:], kw) && (i+len(kw) >= len(code) || !isIdentChar(code[i+len(kw)])) {
			return true
		}
	}
	return false
}

func isIdentChar(c byte) bool {
	return c == '_' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')
}

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU())
	for ext, t := range map[string]string{".mp4": "video/mp4", ".webm": "video/webm", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".png": "image/png", ".gif": "image/gif", ".webp": "image/webp"} {
		_ = mime.AddExtensionType(ext, t)
	}
	if !initDatabase() {
		return
	}
	mux := http.NewServeMux()
	mux.HandleFunc("/sign", createAccountHandler)
	mux.HandleFunc("/sign/tms", signTMSHandler)
	mux.HandleFunc("/login", loginHandler)
	mux.HandleFunc("/login-phone", servePage("cfrontend/login-phone.html"))
	mux.HandleFunc("/logout", logoutHandler)
	mux.HandleFunc("/create", createPostHandler)
	mux.HandleFunc("/like", addLikeHandler)
	mux.HandleFunc("/star", addStarHandler)
	mux.HandleFunc("/view", viewPostHandler)
	mux.HandleFunc("/comment", createCommentHandler)
	mux.HandleFunc("/comments", getCommentsHandler)
	mux.HandleFunc("/profile", func(w http.ResponseWriter, r *http.Request) {
		if isMobileUserAgent(r) {
			http.Redirect(w, r, "/profile-phone", http.StatusFound)
			return
		}
		servePage("cfrontend/profile.html")(w, r)
	})
	mux.HandleFunc("/profile-phone", servePage("cfrontend/profile-phone.html"))
	mux.HandleFunc("/user/profile", getUserProfileHandler)
	mux.HandleFunc("/user/me", getMyProfileHandler)
	mux.HandleFunc("/user/posts", getUserPostsHandler)
	mux.HandleFunc("/user/liked", getUserLikedHandler)
	mux.HandleFunc("/user/update", updateProfileHandler)
	mux.HandleFunc("/account/delete", deleteAccountHandler)
	mux.HandleFunc("/account/save-dob", saveDateOfBirthHandler)
	mux.HandleFunc("/orbit", feedHandler)
	mux.HandleFunc("/cunova", trendingPostsHandler)
	mux.HandleFunc("/lastpost", lastPostHandler)
	mux.HandleFunc("/support/check", checkSupportHandler)
	mux.HandleFunc("/userbase/stats", userStatsHandler)
	mux.HandleFunc("/sidecard", sideCardsHandler)
	mux.HandleFunc("/userbase", servePage("cfrontend/userbase.html"))
	mux.HandleFunc("/profile/cugrid", cugridHandler)
	mux.HandleFunc("/cugrid", serveProtectedPage("cfrontend/cugrid.html"))
	mux.HandleFunc("/cuorbit", servePage("cfrontend/cuorbit.html"))
	mux.HandleFunc("/cuorbit-phone", servePage("cfrontend/cuorbit-phone.html"))
	mux.HandleFunc("/source", sourceHandler)
	mux.HandleFunc("/rules", servePage("cfrontend/rules.html"))
	mux.HandleFunc("/thumbnail", servePage("cfrontend/thumbnail.html"))
	mux.HandleFunc("/privacy", servePage("cfrontend/privacy.html"))
	mux.HandleFunc("/favicon.ico", servePage("cfrontend/favicon.ico"))
	mux.HandleFunc("/favicon.png", servePage("cfrontend/favicon.png"))
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/" {
			http.NotFound(w, r)
			return
		}
		if isMobileUserAgent(r) {
			http.Redirect(w, r, "/cuorbit-phone", http.StatusFound)
			return
		}
		http.Redirect(w, r, "/cuorbit", http.StatusFound)
	})
	mux.Handle("/uploads/", http.StripPrefix("/uploads/", http.FileServer(http.Dir(uploadDir))))
	mux.Handle("/cfrontend/", http.StripPrefix("/cfrontend/", http.FileServer(http.Dir("cfrontend"))))
	port := os.Getenv("PORT")
	if port == "" {
		port = "42362"
	}
	server := &http.Server{
		Addr: ":" + port, Handler: gzipHandler(recoverMiddleware(appMiddleware(mux))),
		ReadTimeout: 120 * time.Second, WriteTimeout: 120 * time.Second,
	}
	_ = server.ListenAndServe()
}

Frontend (shared.css)

/* code by ai design and debug by omeo */
.post-card {
    background:rgba(255,255,255,0.06);
    backdrop-filter:blur(16px); -webkit-backdrop-filter:blur(16px);
    border:1px solid rgba(255,255,255,0.1);
    border-radius:12px; padding:28px;
    box-shadow:0 8px 32px rgba(0,0,0,0.3);
}
.post-head { display:flex; align-items:center; gap:10px; margin-bottom:10px; padding-bottom:10px; border-bottom:1px solid rgba(255,255,255,0.08); }
.post-av { width:36px; height:36px; border-radius:50%; overflow:hidden; border:1px solid rgba(255,255,255,0.1); flex-shrink:0; }
.post-av img { width:100%; height:100%; object-fit:cover; }
.post-nm { font-size:16px; color:#fff; }
.post-un { font-size:12px; color:#fff; }
.post-time { font-size:11px; color:#fff; margin-bottom:6px; }
.post-body { font-size:15px; line-height:1.6; color:#fff; word-break:break-word; margin-bottom:10px; font-family:monospace; }

.post-media { display:flex; flex-wrap:wrap; gap:8px; margin-bottom:10px; }
.post-media img, .post-media video { max-width:100%; border-radius:8px; }
.post-stats { display:flex; gap:16px; font-size:14px; color:#fff; padding-top:12px; margin-top:10px; border-top:1px solid rgba(255,255,255,0.08); }
.post-stats .spl { margin-right:auto; }
.post-stats svg { width:14px; height:14px; vertical-align:middle; }
.post-stats i { font-size:14px; vertical-align:middle; }
.post-stats span { cursor:default; display:flex; align-items:center; gap:4px; }
.post-stats .act { cursor:pointer; transition:color 0.15s; }
.post-stats .act:hover { color:#888; }
.cmt-area { border-top:1px solid rgba(255,255,255,0.06); margin-top:10px; padding-top:10px; }
.cmt-list { display:flex; flex-direction:column; gap:8px; margin-bottom:10px; max-height:200px; overflow-y:auto; }
.cmt-row { display:flex; gap:8px; align-items:flex-start; font-size:13px; }
.cmt-row .ca { width:24px; height:24px; border-radius:50%; overflow:hidden; flex-shrink:0; }
.cmt-row .ca img { width:100%; height:100%; object-fit:cover; }
.cmt-row .cb { flex:1; }
.cmt-row .cu { color:#fff; }
.cmt-row .ct { color:#fff; font-family:monospace; }
.cmt-inp { display:flex; gap:8px; }
.cmt-inp input {
    flex:1; background:rgba(255,255,255,0.06); border:1px solid rgba(255,255,255,0.1);
    border-radius:6px; padding:6px 10px; color:#fff; font-size:14px; outline:none; font-family:monospace;
}
.cmt-inp input:focus { border-color:rgba(255,255,255,0.3); }
.cmt-inp button {
    background:rgba(255,255,255,0.08); border:1px solid rgba(255,255,255,0.1);
    border-radius:6px; padding:6px 14px; color:#fff; font-size:14px; cursor:pointer; font-family:monospace;
}
.cmt-inp button:hover { background:rgba(255,255,255,0.15); }
.empty { text-align:center; color:#fff; padding:60px 20px; font-size:18px; letter-spacing:2px; opacity:0.5; }

Frontend (shared.js)

//// code by ai design and debug by omeo
//// by ai i dont use js
const colors = ['#ffcc00','#ff3366','#00ffff','#00ff00','#3399ff','#ff9900','#e066ff'];
const SVG_GEM = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M11 1h2v4h-2zm0 22h2v-4h-2zM9 5h2v4H9zm0 14h2v-4H9zm4-14h2v4h-2zm0 14h2v-4h-2zM5 9h4v2H5zm14 0h-4v2h4zM1 11h4v2H1zm22 0h-4v2h4zM5 13h4v2H5zm14 0h-4v2h4z"/></svg>';
let curUser = null;

function isMobileDevice() {
    return /Android|iPhone|iPod|iPad|Mobile/i.test(navigator.userAgent);
}

function profileURL(username) {
    return (isMobileDevice() ? '/profile-phone' : '/profile') + '?username=' + encodeURIComponent(username);
}

function esc(s) {
    if (typeof s !== 'string') return s || '';
    return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;');
}

function tpl(t, d) {
    return t.replace(/\{\{\!(\w+)\}\}/g, (_, k) => d[k] !== undefined ? d[k] : '')
            .replace(/\{\{\.(\w+)\}\}/g, (_, k) => d[k] !== undefined ? esc(d[k]) : '');
}

function getDefaultAvatar(seed) {
    const avatars = ['fr1.webp','fr2.webp','fr3.webp','fr4.webp'];
    let hash = 0;
    for (let i = 0; i < seed.length; i++) {
        hash = ((hash << 5) - hash) + seed.charCodeAt(i);
        hash |= 0;
    }
    return '/cfrontend/defaultavatar/' + avatars[Math.abs(hash) % avatars.length];
}

function av(p, seed) {
    if (p) return '<img src="/'+p+'" loading="lazy">';
    return '<img src="'+getDefaultAvatar(seed || 'default')+'" loading="lazy">';
}

function media(p) {
    if (!p.image) return '';
    try {
        let im = JSON.parse(p.image);
        if (im && im.length) return '<div class="post-media">'+im.map(u=>{let e=u.match(/\.(mp4|webm|mpeg)$/i);return e?'<video src="/'+u+'" type="video/'+e[1]+'" controls playsinline preload="metadata"></video>':'<img src="/'+u+'" loading="lazy">'}).join('')+'</div>';
    } catch(e) {
        if (typeof p.image === 'string' && p.image.startsWith('uploads/')) {
            if (p.image.match(/\.(mp4|webm|mpeg)$/i)) return '<div class="post-media"><video src="/'+p.image+'" type="video/'+p.image.match(/\.(mp4|webm|mpeg)$/i)[1]+'" controls playsinline preload="metadata"></video></div>';
            return '<div class="post-media"><img src="/'+p.image+'" loading="lazy"></div>';
        }
    }
    return '';
}

function postPlatformName(url) {
    try {
        let host = new URL(url).hostname.replace(/^www\./,'');
        let known = {'patreon.com':'Patreon','ko-fi.com':'Ko-fi','buymeacoffee.com':'Buy Me a Coffee','liberapay.com':'Liberapay','opencollective.com':'Open Collective','github.com':'GitHub Sponsors','donorbox.org':'Donorbox','tipeee.com':'Tipeee','boosty.to':'Boosty','gumroad.com':'Gumroad','supercast.com':'Supercast','memberful.com':'Memberful','paypal.me':'PayPal','venmo.com':'Venmo','cash.app':'Cash App','streamlabs.com':'Streamlabs','discord.gg':'Discord','discord.com':'Discord','twitter.com':'X','x.com':'X','instagram.com':'Instagram','tiktok.com':'TikTok','youtube.com':'YouTube','twitch.tv':'Twitch','spotify.com':'Spotify'};
        return known[host] || 'support';
    } catch(e) { return 'support'; }
}

function renderPost(p, col) {
    let sup = '';
    if (p.supporturl) {
        let nm = postPlatformName(p.supporturl);
        sup = '<span class="act" onmouseenter="this.style.color=colors[Math.floor(Math.random()*colors.length)];this.style.borderColor=colors[Math.floor(Math.random()*colors.length)]" onmouseleave="this.style.color=\'\';this.style.borderColor=\'rgba(255,255,255,0.1)\'" style="border:1px solid rgba(255,255,255,0.1);border-radius:999px;padding:1px 8px;font-size:12px;display:inline-flex;align-items:center;"><a href="'+esc(p.supporturl)+'" target="_blank" rel="noopener" style="color:inherit;text-decoration:none;">'+esc(nm)+'</a></span>';
    }
    let dt = '';
    if (p.time) {
        let d = new Date(p.time);
        let now = Date.now();
        let diff = now - d.getTime();
        if (diff < 60000) dt = 'now';
        else if (diff < 3600000) dt = Math.floor(diff/60000)+'m';
        else if (diff < 86400000) dt = Math.floor(diff/3600000)+'h';
        else if (diff < 604800000) dt = Math.floor(diff/86400000)+'d';
        else dt = d.toUTCString().split(' ').slice(1, 3).join(' ');
    }
    return tpl(postTpl, {
        id: p.id, col: col||'p', username: p.username, DisplayName: p.DisplayName,
        Content: p.content||'', time: dt,
        gem: SVG_GEM, hrt: '<i class="pixelart-icons-font-heart"></i>',
        vue: '<i class="pixelart-icons-font-eye"></i>',
        msg: '<i class="pixelart-icons-font-message"></i>',
        sc: p.Starco||0, lc: p.Likeco||0, vc: p.Viewsco||0, cmc: p.Commentco||0,
        avHtml: av(p.Avatar, p.username), medHtml: media(p),
        supportStat: sup
    });
}

async function getMe() {
    let r = await fetch('/user/me');
    if (!r.ok) return null;
    try {
        let u = await r.json();
        curUser = u.username;
        let pa = document.querySelector('.panel-avatar');
        if (pa) {
            if (u.avatar) pa.src = '/' + u.avatar;
            else pa.src = getDefaultAvatar(u.username);
        }
        return u;
    } catch (e) { return null; }
}

function loginURL() {
    return isMobileDevice() ? '/login-phone' : '/login';
}

async function actStar(el, pid) {
    if (!curUser) { window.location.href=loginURL(); return; }
    let r = await fetch('/star', {method:'POST', credentials:'include', headers:{'Content-Type':'application/x-www-form-urlencoded'}, body:'post_id='+pid});
    if (!r.ok) return;
    let c = el.querySelector('.sc');
    if (c) c.textContent = parseInt(c.textContent||'0') + 1;
}

async function actLike(el, pid) {
    if (!curUser) { window.location.href=loginURL(); return; }
    await fetch('/like', {method:'POST', credentials:'include', headers:{'Content-Type':'application/x-www-form-urlencoded'}, body:'post_id='+pid});
    let c = el.querySelector('.lc');
    if (c) c.textContent = parseInt(c.textContent||'0') + 1;
}

function toggleCmt(col, pid) {
    let a = document.getElementById('cmt-'+col+'-'+pid);
    if (!a) return;
    if (a.style.display === 'block') { a.style.display = 'none'; return; }
    a.style.display = 'block';
    fetch('/comments?post_id='+pid).then(r=>r.ok&&r.json()).then(d => {
        let cl = document.getElementById('cl-'+col+'-'+pid);
        if (cl) cl.innerHTML = d?.map(c =>
            '<div class="cmt-row"><div class="ca" style="cursor:pointer;" onclick="window.location.href=profileURL(\''+encodeURIComponent(c.username)+'\')">'+(c.avatar_path?'<img src="/'+esc(c.avatar_path)+'">':'')+'</div><div class="cb"><div class="cu" style="cursor:pointer;" onclick="window.location.href=profileURL(\''+encodeURIComponent(c.username)+'\')">@'+esc(c.username)+'</div><div class="ct">'+esc(c.content)+'</div></div></div>'
        ).join('') || '<div style="color:#555;font-size:13px;padding:4px 0;">no comments</div>';
    });
}

function postCmt(col, pid) {
    if (!curUser) { window.location.href=loginURL(); return; }
    let inp = document.getElementById('ci-'+col+'-'+pid);
    if (!inp||!inp.value.trim()) return;
    fetch('/comment', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'}, body:'post_id='+pid+'&content='+encodeURIComponent(inp.value)}).then(() => {
        inp.value = '';
        let card = document.getElementById('cmt-'+col+'-'+pid)?.closest('.post-card');
        if (card) { let cc=card.querySelector('.cmc'); if(cc) cc.textContent=parseInt(cc.textContent||'0')+1; }
        let a=document.getElementById('cmt-'+col+'-'+pid);
        if(a) a.style.display='none';
        toggleCmt(col, pid);
    });
}

function randColor(el, nc) {
    el.addEventListener('mouseenter', () => { let c=colors[Math.floor(Math.random()*colors.length)]; el.style.color=c; el.style.borderColor=c; });
    el.addEventListener('mouseleave', () => { el.style.color=nc; el.style.borderColor=nc; });
}
function openImgViewer(src) {
    let el = document.getElementById('imgViewer');
    if (!el) return;
    document.getElementById('imgViewerSrc').src = src;
    el.style.display = 'flex';
}
function closeImgViewer() {
    let el = document.getElementById('imgViewer');
    if (!el) return;
    el.style.display = 'none';
    document.getElementById('imgViewerSrc').src = '';
}

Page (cuorbit.html)

<!-- code by ai design and debug by omeo -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CuzmoProject-Cuorbit</title>
<link href="https:////fonts.googleapis.com/css2?family=VT323&display=swap" rel="stylesheet"><link rel="icon" type="image/png" href="/cfrontend/favicon.png">
<link href="/cfrontend/pixelart/pixelart-icons-font.css" rel="stylesheet">
<link href="/cfrontend/shared.css" rel="stylesheet">
<style>
* { margin:0; padding:0; box-sizing:border-box; }
body {
    background:#030305; color:#fff; font-family:'VT323',monospace;
    height:100vh; overflow:hidden;
}
body::before {
    content:''; position:fixed; top:0; left:0; width:100%; height:100%;
    background-image:url('/cfrontend/cubg/cuorbit.png');
    background-size:cover; background-position:center; background-repeat:no-repeat;
    pointer-events:none; z-index:0;
}
.scroll-area {
    position:relative; z-index:1;
    width:calc(100% - 206px); height:100vh;
    overflow-y:auto; padding:27px;
    display:flex; flex-direction:column; align-items:center;
    scrollbar-width:none; -ms-overflow-style:none;
}
.scroll-area::-webkit-scrollbar { display:none; }
.w-row {
    display:flex; gap:45px; justify-content:center;
    width:100%; max-width:1035px; margin:0 auto;
    transform:translateX(90px);
}
.w-col { flex:1; min-width:0; display:flex; flex-direction:column; gap:13px; }
#cunoCol, #orbitCol { display:flex; flex-direction:column; gap:13px; }
.w-left { max-width:495px; }
.w-right { max-width:495px; }
.w-hdr { font-size:22px; color:#fff; text-transform:uppercase; letter-spacing:3px; text-shadow:0 2px 8px rgba(0,0,0,0.5); min-height:56px; display:flex; align-items:center; flex-wrap:wrap; }
.w-hdr > * { vertical-align:middle; }




.beta-badge {
    position:fixed; top:8px; left:8px; z-index:99999;
    font-size:25px; color:#fff;
    font-family:'VT323',monospace; pointer-events:none;
    text-shadow:0 0 8px rgba(0,0,0,0.8);
}

.wk-timer {
    display:inline-block; font-size:13px; color:#fff;
    vertical-align:middle; margin-left:11px;
    letter-spacing:1px;
}

.cuo-side {
    position:fixed; top:0; right:0; width:206px; height:100vh;
    background-image:url('/cfrontend/cubg/cuzmopanel.webp');
    background-size:100% 100%; background-repeat:no-repeat;
    z-index:9999;
    box-shadow:-5px 0 15px rgba(0,0,0,0.8);
    display:flex; flex-direction:column; align-items:center; padding-top:76px;
}
.cuo-avatar {
    width:126px; height:126px; border-radius:14px; cursor:pointer;
    object-fit:cover; border:1px solid transparent; margin-bottom:40px;
}
.cuo-txt {
    font-family:'VT323',monospace; color:#fff;
    background:none; border:none; cursor:pointer;
    text-transform:uppercase; letter-spacing:2px;
    text-shadow:0 2px 8px rgba(0,0,0,0.5);
    white-space:nowrap; transition:color 0.15s;
    margin-bottom:24px;
}
#btnCreate { font-size:52px; }
#btnCugrid { font-size:46px; }
#btnUserbase { font-size:34px; margin-top:auto; }
#btnLogin { font-size:36px; margin-bottom:26px; }

.src-link { position:fixed; bottom:36px; left:12px; z-index:100000; }
.src-link a { color:#fff; font-size:16px; font-family:'VT323',monospace; text-decoration:none; transition:color 0.15s; }
.src-link a:hover { color:#ccc; }
.nd {
    position:fixed; bottom:12px; left:12px; z-index:100000;
    font-size:25px; color:rgba(255,255,255,0.5);
    font-family:'VT323',monospace; pointer-events:none;
    text-shadow:0 0 8px rgba(0,0,0,0.8);
    display:none; align-items:center; gap:7px;
}
.nd.on { display:flex; }
.nd .c { font-size:25px; color:rgba(255,255,255,0.6); }

/* ---- side cards (liquid glass) ---- */
.sc-wrap {
    position:fixed; top:76px; left:16px; z-index:50;
    width:240px; display:flex; flex-direction:column; gap:14px;
    max-height:calc(100vh - 100px); overflow-y:auto;
    scrollbar-width:none; -ms-overflow-style:none;
}
.sc-wrap::-webkit-scrollbar { display:none; }
.sc-hide-btn {
    align-self:flex-start;
    background:rgba(255,255,255,0.06); border:1px solid rgba(255,255,255,0.15);
    color:#fff; font-family:'VT323',monospace; font-size:13px;
    text-transform:uppercase; letter-spacing:1px;
    padding:5px 14px; border-radius:6px; cursor:pointer;
    -webkit-tap-highlight-color:transparent; outline:none;
}
.sc-hide-btn:hover { background:rgba(255,255,255,0.12); }
.sc-card {
    background:rgba(255,255,255,0.06);
    backdrop-filter:blur(16px); -webkit-backdrop-filter:blur(16px);
    border:1px solid rgba(255,255,255,0.1);
    border-radius:12px;
    padding:14px;
    color:#fff;
    box-shadow:0 8px 32px rgba(0,0,0,0.3);
}
.sc-card h3 {
    font-size:17px; letter-spacing:2px; text-transform:uppercase;
    font-weight:normal; margin-bottom:10px;
    text-shadow:0 2px 8px rgba(0,0,0,0.5);
}
.sc-row {
    display:flex; align-items:center; gap:9px;
    padding:4px 0; cursor:pointer;
    pointer-events:auto;
}
.sc-row:hover .sc-un { color:#ccc; }
.sc-av {
    width:30px; height:30px; border-radius:50%;
    object-fit:cover; background:rgba(255,255,255,0.08); flex-shrink:0;
}
.sc-un { font-size:15px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
#scTotal { color:#fff; opacity:0.8; font-size:17px; }
.sc-showall { cursor:pointer; font-size:14px; color:#fff; padding-top:6px; text-align:center; pointer-events:auto; }
.sc-showall:hover { color:#ccc; }
.sc-newest { display:flex; align-items:center; gap:10px; }
.sc-newest img, .sc-newest .sc-un { pointer-events:auto; }
.sc-newest .sc-un { font-size:16px; }
.sc-av-big { width:44px; height:44px; border-radius:50%; object-fit:cover; background:rgba(255,255,255,0.08); flex-shrink:0; }
#bgCredit { position:fixed; right:12px; bottom:8px; color:#fff; font-size:11px; pointer-events:none; z-index:9999999; font-family:sans-serif; }
</style>
</head>
<body>

<div class="src-link"><a href="/source">source code</a><br><a href="/rules" style="color:#fff;font-size:11px;text-decoration:none;font-family:monospace;">rules</a><br><a href="/privacy" style="color:#fff;font-size:11px;text-decoration:none;font-family:monospace;">privacy policy &amp; terms of use</a></div>
<div class="sc-wrap" id="scWrap">
    <button class="sc-hide-btn" id="scHideBtn">hide</button>
    <div class="sc-card" id="scPeople">
        <h3>people here <span id="scTotal"></span></h3>
        <div id="scPeopleList"></div>
        <div class="sc-showall" id="scShowAll">show all</div>
    </div>
    <div class="sc-card" id="scWelcome">
        <h3>welcome to cuzmo newest one &lt;3</h3>
        <div class="sc-newest" id="scNewest"></div>
    </div>
</div>
<div class="nd" id="nd"><span>no doomscroll</span><span class="c" id="ndc"></span></div>
<div class="beta-badge" style="position:fixed;top:8px;left:8px;z-index:99999;font-size:25px;color:#fff;font-family:'VT323',monospace;pointer-events:none;text-shadow:0 0 8px rgba(0,0,0,0.8);">beta 0.1v</div>
<div id="imgViewer" style="display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.9);z-index:999999;cursor:zoom-out;justify-content:center;align-items:center;" onclick="closeImgViewer()"><img id="imgViewerSrc" style="max-width:95%;max-height:95%;object-fit:contain;"><div style="position:fixed;top:16px;right:24px;font-size:32px;color:#fff;cursor:pointer;font-family:monospace;" onclick="closeImgViewer()">x</div></div>

<div class="scroll-area" id="scrollArea">
    <div class="w-row">
        <div class="w-col w-left">
            <div class="w-hdr">CUNOVA<span style="font-size:13px;letter-spacing:1px;color:#fff;margin-left:7px;white-space:nowrap;">top 500 this month</span><span class="wk-timer" id="wkTimer"></span></div>
            <div id="cunoCol"></div>
        </div>
        <div class="w-col w-right">
            <div class="w-hdr">CUORBIT<span style="font-size:14px;letter-spacing:1px;color:#fff;margin-left:8px;">Newest post</span></div>
            <div id="orbitCol"></div>
        </div>
    </div>
</div>

<div class="cuo-side">
    <img class="cuo-avatar" id="cuoAvatar" src="/cfrontend/defaultavatar/fr1.webp" onclick="window.location.href='/profile'">
    <button class="cuo-txt" id="btnCreate">CREATE</button>
    <button class="cuo-txt" id="btnCugrid">CUGRID</button>
    <button class="cuo-txt" id="btnUserbase">USERBASE</button>
    <button class="cuo-txt" id="btnLogin">LOGIN / SIGN</button>
</div>

<script src="/cfrontend/shared.js?v=7"></script>
<script>
let dActive = false;
let doomCounter = 30;

function ndLock() {
    if (dActive) return;
    dActive = true;
    let el = document.getElementById('nd');
    let s = document.getElementById('scrollArea');
    s.style.overflowY = 'hidden';
    el.classList.add('on');
    let r = 5;
    document.getElementById('ndc').textContent = r;
    let t = setInterval(() => {
        r--;
        if (r <= 0) {
            clearInterval(t);
            dActive = false;
            s.style.overflowY = 'auto';
            el.classList.remove('on');
            doomCounter = 15;
        } else {
            document.getElementById('ndc').textContent = r;
        }
    }, 1000);
}

function consumePost(pid) {
    if (dActive) return;
    doomCounter--;
    if (doomCounter <= 0) {
        doomCounter = 15;
        setTimeout(ndLock, 200);
    }
    fetch('/view?post_id='+pid).then(r => { if (!r.ok) return; return r.json(); }).then(d => {
        if (d && d.viewsco !== undefined) {
            document.querySelectorAll('.vc[data-pid="'+pid+'"]').forEach(v => {
                v.textContent = d.viewsco;
            });
        }
    });
}

function monthlyTimer() {
    let el = document.getElementById('wkTimer');
    if (!el) return;
    let now = new Date();
    let end = new Date(now.getFullYear(), now.getMonth() + 1, 1);
    let diff = end - now;
    if (diff <= 0) { el.textContent = ''; return; }
    let d = Math.floor(diff / 86400000);
    let h = Math.floor((diff % 86400000) / 3600000);
    let m = Math.floor((diff % 3600000) / 60000);
    if (d > 0) parts.push(d + 'd');
    parts.push(h + 'h', m + 'm');
    el.textContent = ' ' + parts.join(' ');
}

function sideAvatar(u) {
    return u && u.avatar ? '/' + u.avatar : getDefaultAvatar(u.username);
}

function scRow(u) {
    let nm = (u.displayname || u.username) || '?';
    return '<div class="sc-row" onclick="window.location.href=\'' + profileURL(u.username) + '\'">' +
        '<img class="sc-av" src="' + sideAvatar(u) + '" loading="lazy">' +
        '<span class="sc-un">' + esc(nm) + '</span></div>';
}

async function loadSideCards() {
    let r = await fetch('/sidecard').then(r=>r.ok?r.json():null).catch(()=>null);
    if (!r) return;
    let total = r.total || 0;
    document.getElementById('scTotal').textContent = total;

    let newest = r.newest || [];
    document.getElementById('scPeopleList').innerHTML = newest.map(scRow).join('');

    let n1 = r.newest_one;
    if (n1 && n1.username) {
        let nm = n1.displayname || n1.username;
        document.getElementById('scNewest').innerHTML =
            '<img class="sc-av-big" src="' + sideAvatar(n1) + '" loading="lazy" onclick="window.location.href=\'' + profileURL(n1.username) + '\'" style="cursor:pointer">' +
            '<span class="sc-un" style="cursor:pointer" onclick="window.location.href=\'' + profileURL(n1.username) + '\'">' + esc(nm) + '</span>';
    } else {
        document.getElementById('scNewest').innerHTML = '<span class="sc-un">no users yet</span>';
    }

    document.getElementById('scShowAll').onclick = async () => {
        let rr = await fetch('/sidecard?all=1').then(res=>res.ok?res.json():null).catch(()=>null);
        if (!rr || !rr.all || rr.all.length === 0) return;
        document.getElementById('scPeopleList').innerHTML = rr.all.map(scRow).join('');
        document.getElementById('scShowAll').style.display = 'none';
    };
}

function toggleSideCards(force) {
    let w = document.getElementById('scWrap');
    let hidden = w.style.display === 'none';
    w.style.display = force === undefined ? (hidden ? 'flex' : 'none') : (force ? 'flex' : 'none');
}

async function init() {
    try {
        let po = await fetch('/cfrontend/cuorbitwidget.html?v=3').then(r=>r.ok?r.text():'');
        if (!po) { return; }
        postTpl = po;
        let uu = await getMe();
        if (uu) {
            if (uu.avatar) document.getElementById('cuoAvatar').src = '/' + uu.avatar;
            else document.getElementById('cuoAvatar').src = getDefaultAvatar(uu.username);
        }
        document.getElementById('btnCreate').addEventListener('click', () => window.location.href = '/create');
        document.getElementById('btnCugrid').addEventListener('click', () => window.location.href = '/cugrid');
        document.getElementById('btnLogin').addEventListener('click', () => window.location.href = '/login');
        document.getElementById('btnUserbase').addEventListener('click', () => window.location.href = '/userbase');
        document.querySelectorAll('.cuo-txt').forEach(el => {
            el.addEventListener('mouseenter', () => { let c=colors[Math.floor(Math.random()*colors.length)]; el.style.color=c; });
            el.addEventListener('mouseleave', () => { el.style.color='#fff'; });
        });
        let pav = document.getElementById('cuoAvatar');
        if (pav) {
            pav.addEventListener('mouseenter', () => { pav.style.borderColor = colors[Math.floor(Math.random()*colors.length)]; });
            pav.addEventListener('mouseleave', () => { pav.style.borderColor = 'transparent'; });
        }

        let [rd, ro] = await Promise.all([
            fetch('/cunova').then(r=>r.ok?r.json():[]).catch(()=>[]),
            fetch('/orbit?offset=0&limit=50').then(r=>r.ok?r.json():[]).catch(()=>[]),
        ]);
        loadSideCards();
    if (rd.length > 0) {
        document.getElementById('cunoCol').innerHTML = rd.slice(0,50).map(p=>renderPost(p,'l')).join('');
    }
    let merged = ro.map(p => ({p, t:'r'}));
    document.getElementById('orbitCol').innerHTML = merged.map(it => renderPost(it.p, it.t)).join('');

        let observer = new IntersectionObserver(entries => {
            entries.forEach(e => {
                if (!e.isIntersecting) return;
                let vc = e.target.querySelector('.vc');
                if (!vc) return;
                let pid = vc.dataset.pid;
                if (!pid) return;
                observer.unobserve(e.target);
                consumePost(pid);
            });
        }, { root: document.getElementById('scrollArea'), threshold: 0.3 });
        document.querySelectorAll('#orbitCol .post-card').forEach(el => observer.observe(el));

        setInterval(monthlyTimer, 1000);
        monthlyTimer();

        //// live polling for new posts
        let lpId = ro.length > 0 ? Math.max(...ro.map(p=>p.id)) : 0;
        setInterval(() => {
            fetch('/lastpost').then(r=>r.ok?r.json():null).then(p => {
                if (!p || !p.id || p.id <= lpId) return;
                lpId = p.id;
                let sa = document.getElementById('scrollArea');
                if (!sa) return;
                let sy = sa.scrollTop;
                document.getElementById('orbitCol').insertAdjacentHTML('afterbegin', renderPost(p, 'r'));
                sa.scrollTop = sy;
                let obs = new IntersectionObserver(e => {
                    e.forEach(n => {
                        if (!n.isIntersecting) return;
                        let vc = n.target.querySelector('.vc');
                        if (!vc) return;
                        let pid = vc.dataset.pid;
                        if (!pid) return;
                        obs.unobserve(n.target);
                        consumePost(pid);
                    });
                }, { root: sa, threshold: 0.3 });
                let fc = document.querySelector('#orbitCol .post-card');
                if (fc) obs.observe(fc);
            }).catch(()=>{});
        }, 15000);
    } catch(e) {}
}

init();

document.getElementById('scHideBtn').addEventListener('click', () => toggleSideCards());

document.addEventListener('keydown', e => { if (e.key === 'Escape') closeImgViewer(); });
document.getElementById('scrollArea').addEventListener('click', e => {
    let t = e.target;
        if (t.tagName === 'IMG' && t.closest('.post-media')) openImgViewer(t.src);
});
</script>
<div id="bgCredit">Background: NASA/JPL — Redrawn by omeo</div>
</body>
</html>

Page (cuorbit-phone.html)

<!-- code by ai design and debug by omeo -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CuzmoProject-Cuorbit</title>
<link href="https:////fonts.googleapis.com/css2?family=VT323&display=swap" rel="stylesheet"><link rel="icon" type="image/png" href="/cfrontend/favicon.png">
<link href="/cfrontend/pixelart/pixelart-icons-font.css" rel="stylesheet">
<link href="/cfrontend/shared.css" rel="stylesheet">
<style>
* { margin:0; padding:0; box-sizing:border-box; }
body {
    background:#030305; color:#fff; font-family:'VT323',monospace;
    height:100vh; overflow:hidden;
}
body::before {
    content:''; position:fixed; top:0; left:0; width:100%; height:100%;
    background-image:url('/cfrontend/cubg/cuorbit.png');
    background-size:cover; background-position:center; background-repeat:no-repeat;
    pointer-events:none; z-index:0;
}
.scroll-area {
    position:relative; z-index:1;
    width:calc(100% - 206px); height:100vh;
    overflow-y:auto; padding:27px;
    display:flex; flex-direction:column; align-items:center;
    scrollbar-width:none; -ms-overflow-style:none;
}
.scroll-area::-webkit-scrollbar { display:none; }
.w-row {
    display:flex; gap:45px; justify-content:center;
    width:100%; max-width:1035px; margin:0 auto;
    transform:translateX(90px);
}
.w-col { flex:1; min-width:0; display:flex; flex-direction:column; gap:13px; }
#cunoCol, #orbitCol { display:flex; flex-direction:column; gap:13px; }
.w-left { max-width:495px; }
.w-right { max-width:495px; }
.w-hdr { font-size:22px; color:#fff; text-transform:uppercase; letter-spacing:3px; text-shadow:0 2px 8px rgba(0,0,0,0.5); min-height:56px; display:flex; align-items:center; flex-wrap:wrap; }
.w-hdr > * { vertical-align:middle; }




.beta-badge {
    position:fixed; top:8px; left:8px; z-index:99999;
    font-size:25px; color:#fff;
    font-family:'VT323',monospace; pointer-events:none;
    text-shadow:0 0 8px rgba(0,0,0,0.8);
}

.wk-timer {
    display:inline-block; font-size:13px; color:#fff;
    vertical-align:middle; margin-left:11px;
    letter-spacing:1px;
}

.cuo-side {
    position:fixed; top:0; right:0; width:206px; height:100vh;
    background-image:url('/cfrontend/cubg/cuzmopanel.webp');
    background-size:100% 100%; background-repeat:no-repeat;
    z-index:9999;
    box-shadow:-5px 0 15px rgba(0,0,0,0.8);
    display:flex; flex-direction:column; align-items:center; padding-top:76px;
}
.cuo-avatar {
    width:126px; height:126px; border-radius:14px; cursor:pointer;
    object-fit:cover; border:1px solid transparent; margin-bottom:40px;
}
.cuo-txt {
    font-family:'VT323',monospace; color:#fff;
    background:none; border:none; cursor:pointer;
    text-transform:uppercase; letter-spacing:2px;
    text-shadow:0 2px 8px rgba(0,0,0,0.5);
    white-space:nowrap; transition:color 0.15s;
    margin-bottom:24px;
}
#btnCreate { font-size:52px; }
#btnCugrid { font-size:46px; }
#btnUserbase { font-size:34px; }
#btnLogin { font-size:36px; margin-bottom:26px; }

.src-link { position:fixed; bottom:36px; left:12px; z-index:100000; }
.src-link a { color:#fff; font-size:11px; font-family:'VT323',monospace; text-decoration:none; transition:color 0.15s; }
.src-link a:hover { color:#ccc; }
.nd {
    position:fixed; bottom:12px; left:12px; z-index:100000;
    font-size:25px; color:rgba(255,255,255,0.5);
    font-family:'VT323',monospace; pointer-events:none;
    text-shadow:0 0 8px rgba(0,0,0,0.8);
    display:none; align-items:center; gap:7px;
}
.nd.on { display:flex; }
.nd .c { font-size:25px; color:rgba(255,255,255,0.6); }

.menu-btn {
    position:fixed; top:12px; right:12px; z-index:100001;
    width:46px; height:46px; cursor:pointer;
    background:transparent; border:none;
    padding:0;
    display:flex; flex-direction:column; align-items:center; justify-content:center; gap:9px;
    -webkit-tap-highlight-color:transparent; outline:none;
}
.menu-btn span { display:block; width:24px; height:3px; background:#fff; border-radius:2px; }
button, .cuo-txt, .cuo-avatar, .edit-btn, .menu-btn { outline:none; -webkit-tap-highlight-color:transparent; }
.cuo-side-backdrop { position:fixed; inset:0; z-index:9998; background:rgba(0,0,0,0.5); display:none; }
.cuo-side-backdrop.show { display:block; }
.cuo-side {
    left:auto; right:0; transform:translateX(100%); visibility:hidden;
    transition:transform 0.25s ease, visibility 0s linear 0.25s;
}
.cuo-side.open { transform:translateX(0); visibility:visible; transition:transform 0.25s ease; }
.scroll-area {
    width:100%; padding:70px 14px 14px;
    align-items:stretch;
}
.w-row {
    flex-direction:row; transform:none;
    gap:12px; max-width:none;
}
.w-left, .w-right { max-width:none; flex:1; }
.w-left { padding-right:10px; }
.post-card { padding:12px; }
.post-head { gap:8px; margin-bottom:6px; padding-bottom:6px; }
.post-nm { font-size:14px; }
.post-un { font-size:10px; }
.post-time { font-size:9px; margin-bottom:4px; }
.post-body { font-size:13px; margin-bottom:6px; }
.post-stats { gap:10px; font-size:12px; padding-top:8px; margin-top:6px; flex-wrap:nowrap; }
.post-stats svg { width:12px; height:12px; }
.post-stats .spl { margin-right:auto; white-space:nowrap; }
.phone-note { font-size:12px; color:#fff; text-align:right; margin-top:14px; line-height:1.5; opacity:0.75; text-shadow:0 2px 8px rgba(0,0,0,0.5); }
.beta-badge { left:12px; }
.src-link { left:12px; right:auto; }
.nd { left:12px; }
#bgCredit { position:fixed; right:12px; bottom:8px; color:#fff; font-size:11px; pointer-events:none; z-index:9999999; font-family:sans-serif; }
</style>
</head>
<body>

<div class="src-link"><a href="/source">source code</a><br><a href="/rules" style="color:#fff;font-size:11px;text-decoration:none;font-family:monospace;">rules</a><br><a href="/privacy" style="color:#fff;font-size:11px;text-decoration:none;font-family:monospace;">privacy policy &amp; terms of use</a></div>
<div class="nd" id="nd"><span>no doomscroll</span><span class="c" id="ndc"></span></div>
<div class="menu-btn" id="menuBtn"><span></span><span></span></div>
<div class="cuo-side-backdrop" id="sideBackdrop"></div>
<div class="beta-badge" style="position:fixed;top:8px;left:8px;z-index:99999;font-size:25px;color:#fff;font-family:'VT323',monospace;pointer-events:none;text-shadow:0 0 8px rgba(0,0,0,0.8);">beta 0.1v</div>
<div id="imgViewer" style="display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.9);z-index:999999;cursor:zoom-out;justify-content:center;align-items:center;" onclick="closeImgViewer()"><img id="imgViewerSrc" style="max-width:95%;max-height:95%;object-fit:contain;"><div style="position:fixed;top:16px;right:24px;font-size:32px;color:#fff;cursor:pointer;font-family:monospace;" onclick="closeImgViewer()">x</div></div>

<div class="scroll-area" id="scrollArea">
    <div class="w-row">
        <div class="w-col w-left">
            <div class="w-hdr">CUNOVA<span style="font-size:13px;letter-spacing:1px;color:#fff;margin-left:7px;white-space:nowrap;">top 500 this month</span><span class="wk-timer" id="wkTimer"></span></div>
            <div id="cunoCol"></div>
        </div>
        <div class="w-col w-right">
            <div class="w-hdr">CUORBIT<span style="font-size:14px;letter-spacing:1px;color:#fff;margin-left:8px;">Newest post</span></div>
            <div id="orbitCol"></div>
            <div class="phone-note">the phone ver is alpha u will see a huge problems i recommend use desktop site mode and rotate the screen for now</div>
        </div>
    </div>
</div>

<div class="cuo-side">
    <img class="cuo-avatar" id="cuoAvatar" src="/cfrontend/defaultavatar/fr1.webp" onclick="window.location.href='/profile-phone'">
    <button class="cuo-txt" id="btnCreate">CREATE</button>
    <button class="cuo-txt" id="btnCugrid">CUGRID</button>
    <button class="cuo-txt" id="btnUserbase">USERBASE</button>
    <button class="cuo-txt" id="btnLogin">LOGIN / SIGN</button>
</div>

<script src="/cfrontend/shared.js?v=7"></script>
<script>
let dActive = false;
let doomCounter = 30;

function ndLock() {
    if (dActive) return;
    dActive = true;
    let el = document.getElementById('nd');
    let s = document.getElementById('scrollArea');
    s.style.overflowY = 'hidden';
    el.classList.add('on');
    let r = 5;
    document.getElementById('ndc').textContent = r;
    let t = setInterval(() => {
        r--;
        if (r <= 0) {
            clearInterval(t);
            dActive = false;
            s.style.overflowY = 'auto';
            el.classList.remove('on');
            doomCounter = 15;
        } else {
            document.getElementById('ndc').textContent = r;
        }
    }, 1000);
}

function consumePost(pid) {
    if (dActive) return;
    doomCounter--;
    if (doomCounter <= 0) {
        doomCounter = 15;
        setTimeout(ndLock, 200);
    }
    fetch('/view?post_id='+pid).then(r => { if (!r.ok) return; return r.json(); }).then(d => {
        if (d && d.viewsco !== undefined) {
            document.querySelectorAll('.vc[data-pid="'+pid+'"]').forEach(v => {
                v.textContent = d.viewsco;
            });
        }
    });
}

function monthlyTimer() {
    let el = document.getElementById('wkTimer');
    if (!el) return;
    let now = new Date();
    let end = new Date(now.getFullYear(), now.getMonth() + 1, 1);
    let diff = end - now;
    if (diff <= 0) { el.textContent = ''; return; }
    let d = Math.floor(diff / 86400000);
    let h = Math.floor((diff % 86400000) / 3600000);
    let m = Math.floor((diff % 3600000) / 60000);
    let parts = [];
    if (d > 0) parts.push(d + 'd');
    parts.push(h + 'h', m + 'm');
    el.textContent = ' ' + parts.join(' ');
}

async function init() {
    try {
        let po = await fetch('/cfrontend/cuorbitwidget.html?v=3').then(r=>r.ok?r.text():'');
        if (!po) { return; }
        postTpl = po;
        let uu = await getMe();
        if (uu) {
            if (uu.avatar) document.getElementById('cuoAvatar').src = '/' + uu.avatar;
            else document.getElementById('cuoAvatar').src = getDefaultAvatar(uu.username);
        }
        document.getElementById('btnCreate').addEventListener('click', () => window.location.href = '/create');
        document.getElementById('btnCugrid').addEventListener('click', () => window.location.href = '/cugrid');
        document.getElementById('btnLogin').addEventListener('click', () => window.location.href = '/login-phone');
        document.getElementById('btnUserbase').addEventListener('click', () => window.location.href = '/userbase');
        document.querySelectorAll('.cuo-txt').forEach(el => {
            el.addEventListener('mouseenter', () => { let c=colors[Math.floor(Math.random()*colors.length)]; el.style.color=c; });
            el.addEventListener('mouseleave', () => { el.style.color='#fff'; });
        });
        let pav = document.getElementById('cuoAvatar');
        if (pav) {
            pav.addEventListener('mouseenter', () => { pav.style.borderColor = colors[Math.floor(Math.random()*colors.length)]; });
            pav.addEventListener('mouseleave', () => { pav.style.borderColor = 'transparent'; });
        }

        let [rd, ro] = await Promise.all([
            fetch('/cunova').then(r=>r.ok?r.json():[]).catch(()=>[]),
            fetch('/orbit?offset=0&limit=50').then(r=>r.ok?r.json():[]).catch(()=>[]),
        ]);
    if (rd.length > 0) {
        document.getElementById('cunoCol').innerHTML = rd.slice(0,50).map(p=>renderPost(p,'l')).join('');
    }
    let merged = ro.map(p => ({p, t:'r'}));
    document.getElementById('orbitCol').innerHTML = merged.map(it => renderPost(it.p, it.t)).join('');

        let observer = new IntersectionObserver(entries => {
            entries.forEach(e => {
                if (!e.isIntersecting) return;
                let vc = e.target.querySelector('.vc');
                if (!vc) return;
                let pid = vc.dataset.pid;
                if (!pid) return;
                observer.unobserve(e.target);
                consumePost(pid);
            });
        }, { root: document.getElementById('scrollArea'), threshold: 0.3 });
        document.querySelectorAll('#orbitCol .post-card').forEach(el => observer.observe(el));

        setInterval(monthlyTimer, 1000);
        monthlyTimer();

        //// live polling for new posts
        let lpId = ro.length > 0 ? Math.max(...ro.map(p=>p.id)) : 0;
        setInterval(() => {
            fetch('/lastpost').then(r=>r.ok?r.json():null).then(p => {
                if (!p || !p.id || p.id <= lpId) return;
                lpId = p.id;
                let sa = document.getElementById('scrollArea');
                if (!sa) return;
                let sy = sa.scrollTop;
                document.getElementById('orbitCol').insertAdjacentHTML('afterbegin', renderPost(p, 'r'));
                sa.scrollTop = sy;
                let obs = new IntersectionObserver(e => {
                    e.forEach(n => {
                        if (!n.isIntersecting) return;
                        let vc = n.target.querySelector('.vc');
                        if (!vc) return;
                        let pid = vc.dataset.pid;
                        if (!pid) return;
                        obs.unobserve(n.target);
                        consumePost(pid);
                    });
                }, { root: sa, threshold: 0.3 });
                let fc = document.querySelector('#orbitCol .post-card');
                if (fc) obs.observe(fc);
            }).catch(()=>{});
        }, 15000);
    } catch(e) {}
}

init();

const side = document.querySelector('.cuo-side');
const menuBtn = document.getElementById('menuBtn');
const sideBackdrop = document.getElementById('sideBackdrop');
function toggleSide(open) {
    if (!side) return;
    if (open === undefined) open = !side.classList.contains('open');
    side.classList.toggle('open', open);
    if (sideBackdrop) sideBackdrop.classList.toggle('show', open);
}
if (menuBtn) menuBtn.addEventListener('click', () => toggleSide());
if (sideBackdrop) sideBackdrop.addEventListener('click', () => toggleSide(false));
if (side) {
    side.addEventListener('click', e => {
        if (e.target.closest('.cuo-txt') || e.target.closest('.cuo-avatar')) toggleSide(false);
    });
}

document.addEventListener('keydown', e => { if (e.key === 'Escape') closeImgViewer(); });
document.getElementById('scrollArea').addEventListener('click', e => {
    let t = e.target;
    if (t.tagName === 'IMG' && t.closest('.post-media')) openImgViewer(t.src);
});
</script>
<div id="bgCredit">Background: NASA/JPL — Redrawn by omeo</div>
</body>
</html>

Page (login.html)

<!DOCTYPE html>
<!-- code by ai design and debug by omeo -->
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CuzmoProject</title>
<link href="https:////fonts.googleapis.com/css2?family=VT323&display=swap" rel="stylesheet"><link rel="icon" type="image/png" href="/cfrontend/favicon.png">
<style>
* { margin:0; padding:0; box-sizing:border-box; }
body {
    background:#000 url('/cfrontend/cubg/sugoi.png') center/cover no-repeat; color:#fff;
    font-family:'VT323',monospace;
    height:100vh; display:flex;
    align-items:center; justify-content:center;
    overflow:hidden;
}
.center {
    display:flex; flex-direction:column;
    align-items:center; gap:14px;
}
.avatar-top {
    display:flex; align-items:center; justify-content:center;
}
.avatar-top img {
    width:165px; height:165px; border-radius:50%;
    object-fit:cover;
    box-shadow:0 0 20px rgba(255,255,255,0.15), 0 0 60px rgba(255,255,255,0.05);
    border:2px solid rgba(255,255,255,0.15);
    background:rgba(255,255,255,0.03);
}
.title-glass {
    display: inline-block;
    font-size:65px; letter-spacing:4px; text-transform:uppercase;
    font-family:monospace;
    text-align:center;
    line-height:1;
    color: rgba(255, 255, 255, 0.45);
    -webkit-text-fill-color: rgba(255, 255, 255, 0.45);
    filter:
        drop-shadow(0 1px 2px rgba(255, 255, 255, 0.18))
        drop-shadow(0 4px 12px rgba(255, 255, 255, 0.06));
}
.glass-card {
    width:400px; height:40px;
    background:rgba(255,255,255,0.45);
    border:1px solid rgba(255,255,255,0.08);
    border-radius:18px;
    backdrop-filter:blur(12px);
    -webkit-backdrop-filter:blur(12px);
    display:flex; align-items:center;
    cursor:text;
    transition:background 0.2s, border-color 0.2s;
    position:relative;
    flex-shrink:0;
    padding:0 12px;
}
.glass-card:hover {
    background:rgba(255,255,255,0.08);
    border-color:rgba(255,255,255,0.15);
}
.glass-card .lbl {
    font-size:14px; letter-spacing:2px;
    color:#000;
    font-family:monospace;
    text-transform:uppercase;
    pointer-events:none;
    white-space:nowrap;
    margin-right:8px;
}
.glass-card input {
    flex:1; height:100%;
    background:transparent; border:none; outline:none;
    color:#000; font-family:monospace; font-size:14px;
    padding:0;
}
#policyBox {
    display:none;
    width:400px; max-height:260px; overflow-y:auto;
    background:rgba(0,0,0,0.7);
    border:1px solid rgba(255,255,255,0.08);
    border-radius:12px;
    backdrop-filter:blur(8px);
    -webkit-backdrop-filter:blur(8px);
    padding:12px 14px;
    font-family:monospace;
    font-size:11px;
    color:rgba(255,255,255,0.7);
    line-height:1.4;
}
#policyBox label {
    display:flex; align-items:center; gap:8px;
    cursor:pointer; margin-top:8px; padding-top:8px;
    border-top:1px solid rgba(255,255,255,0.06);
    font-size:12px; color:rgba(255,255,255,0.8);
}
#policyBox input[type=checkbox] {
    width:14px; height:14px; cursor:pointer;
}
#policyBox::-webkit-scrollbar { width:4px; }
#policyBox::-webkit-scrollbar-track { background:transparent; }
#policyBox::-webkit-scrollbar-thumb { background:rgba(255,255,255,0.1); border-radius:2px; }
#policyBox .pol-title {
    font-size:13px; color:rgba(255,255,255,0.6); margin-bottom:6px;
}
#policyBox code {
    color:rgba(255,255,255,0.4); font-size:10px;
}
#bgCredit { position:fixed; right:12px; bottom:8px; color:#fff; font-size:11px; pointer-events:none; z-index:9999999; font-family:sans-serif; }
</style>
</head>
<body>
<div class="center">
    <div class="title-glass">CuzmoProject</div>
    <div class="avatar-top"><img src="" id="randAvatar"></div>
    <form id="loginForm" onsubmit="return false">
    <div class="glass-card" id="loginCard">
        <div class="lbl">login</div>
        <input type="text" id="loginInput" autocomplete="off" maxlength="120" spellcheck="false" enterkeyhint="go">
    </div>
    </form>
    <form id="signForm" onsubmit="return false">
    <div class="glass-card" id="signCard">
        <div class="lbl">sign</div>
        <input type="text" id="signInput" autocomplete="off" maxlength="30" spellcheck="false" enterkeyhint="go">
    </div>
    </form>
    <div id="policyBox"></div>
</div>
<a href="/privacy" style="position:fixed;bottom:10px;left:10px;color:#fff;font-size:11px;text-decoration:none;font-family:monospace;z-index:10;">privacy policy &amp; terms of use</a>
<script>
function seededRand(seed) {
    let s = seed;
    return function() {
        s = (s * 1103515245 + 12345) & 0x7fffffff;
        return s / 0x7fffffff;
    };
}

let today = new Date();
let seed = today.getFullYear()*10000 + (today.getMonth()+1)*100 + today.getDate();
let rng = seededRand(seed);

let signPendingUser = '';

//// --- label swap on hover ---
document.getElementById('loginCard').addEventListener('mouseenter', () => {
    document.querySelector('#loginCard .lbl').textContent = 'User Id';
});
document.getElementById('loginCard').addEventListener('mouseleave', () => {
    document.querySelector('#loginCard .lbl').textContent = 'login';
});
document.getElementById('signCard').addEventListener('mouseenter', () => {
    document.querySelector('#signCard .lbl').textContent = 'Username';
});
document.getElementById('signCard').addEventListener('mouseleave', () => {
    document.querySelector('#signCard .lbl').textContent = 'sign';
});

//// --- login ---
document.getElementById('loginInput').addEventListener('input', function() {
    this.value = this.value.replace(/[^A-Za-z0-9\-_+=/]/g, '');
});
document.getElementById('loginForm').addEventListener('submit', function(e) {
    e.preventDefault();
    let inp = document.getElementById('loginInput');
    if (inp.value.length >= 31) {
        let fd = new FormData();
        fd.append('id', inp.value);
        fetch('/login', { method:'POST', body:fd }).then(r => {
            if (r.ok) window.location.href = '/cuorbit';
            else r.text().then(t => alert(t));
        });
    }
});

//// --- sign with policy checkbox ---
document.getElementById('signInput').addEventListener('keyup', function(e) {
    if (e.key === 'Enter' && this.value.trim()) {
        signPendingUser = this.value.trim();
        let fd = new FormData();
        fd.append('username', signPendingUser);
        fetch('/sign', { method:'POST', body:fd }).then(r => {
            if (r.ok) {
                document.getElementById('policyBox').style.display = 'block';
                document.getElementById('policyBox').innerHTML =
                    '<div class="pol-title">Cuzmo Policies</div>'+
                    '<b>Terms of Use</b><br><br>'+
                    'You are responsible for the content you post on Cuzmo.<br>'+
                    'Content that violates the Cuzmo Rules or applicable law may be removed.<br>'+
                    'Accounts may be restricted, suspended, or permanently deleted for serious or repeated violations.<br>'+
                    'Copyright and other legal reports can be submitted to: '+
                    '<a href="mailto:jserta@tutamail.com" style="color:rgba(255,255,255,0.6);">jserta@tutamail.com</a><br>'+
                    'Confirmed violations may result in content removal or account action.<br><br>'+
                    '<b>Privacy</b><br><br>'+
                    '6. Cuzmo collects only the data necessary to operate the service.<br>'+
                    '7. Cuzmo does not sell your personal data.<br>'+
                    '8. Cuzmo does not use advertising or personal tracking.<br>'+
                    '9. Platform statistics may be displayed using aggregated service data.<br><br>'+
                    'By creating an account, you confirm that you have read and agree to the Cuzmo Terms of Use and Privacy Policy.<br><br>'+
                     '<label><input type="checkbox" id="policyCheck"> I agree to the Cuzmo Terms of Use and Privacy Policy.</label>'+
                     '<label style="border-top:none;margin-top:0;padding-top:0;font-size:11px;color:rgba(255,255,255,0.5);"><input type="checkbox" id="cookieCheck" style="width:12px;height:12px;"> I accept essential cookies.</label>'+
                     '<div style="margin-top:6px;text-align:right;"><button onclick="doSign()" style="background:rgba(255,255,255,0.1);color:#fff;border:1px solid rgba(255,255,255,0.15);border-radius:8px;padding:4px 14px;font-family:monospace;font-size:12px;cursor:pointer;">sign</button></div>';
} else {
            r.text().then(t => alert(t));
        }
        });
        this.value = '';
    }
});

function doSign() {
    let cb = document.getElementById('policyCheck');
    if (!cb || !cb.checked) { alert('you must agree to the policies'); return; }
    let ck = document.getElementById('cookieCheck');
    if (!ck || !ck.checked) { alert('you must accept cookies'); return; }
    document.cookie = 'cookieconsent=true; path=/; max-age=' + (365*24*60*60);
    fetch('/sign/tms', { method:'POST' }).then(r => r.text()).then(t => {
        document.getElementById('policyBox').style.display = 'none';
        if (t.length > 20) alert('your ID (save this): ' + t);
        else alert(t);
    });
}

function loadAvatar() {
    let pick = document.getElementById('randAvatar');
    const defaults = ['fr1.webp','fr2.webp','fr3.webp','fr4.webp'];
    fetch('/orbit?offset=0&limit=150').then(r=>r.ok?r.json():[]).catch(()=>[]).then(posts => {
        let users = [];
        let seen = new Set();
        (posts||[]).forEach(p => {
            if (p.avatar && !seen.has(p.avatar)) {
                seen.add(p.avatar);
                users.push({avatar: p.avatar});
            }
        });
        if (users.length > 0) {
            pick.src = '/' + users[Math.floor(Math.random() * users.length)].avatar;
        } else {
            pick.src = '/cfrontend/defaultavatar/' + defaults[Math.floor(Math.random() * defaults.length)];
        }
    });
}
loadAvatar();
</script>
<div id="bgCredit">Background: NASA/JPL — Redrawn by omeo</div>
</body>
</html>

Page (login-phone.html)

<!DOCTYPE html>
<!-- code by ai design and debug by omeo -->
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CuzmoProject</title>
<link href="https:////fonts.googleapis.com/css2?family=VT323&display=swap" rel="stylesheet"><link rel="icon" type="image/png" href="/cfrontend/favicon.png">
<style>
* { margin:0; padding:0; box-sizing:border-box; }
body {
    background:#000 url('/cfrontend/cubg/sugoi.png') center/cover no-repeat; color:#fff;
    font-family:'VT323',monospace;
    height:100vh; display:flex;
    align-items:center; justify-content:center;
    overflow:hidden;
    padding:20px;
}
.center {
    display:flex; flex-direction:column;
    align-items:center; gap:16px;
    width:100%; max-width:380px;
}
.avatar-top {
    display:flex; align-items:center; justify-content:center;
}
.avatar-top img {
    width:140px; height:140px; border-radius:50%;
    object-fit:cover;
    box-shadow:0 0 20px rgba(255,255,255,0.15), 0 0 60px rgba(255,255,255,0.05);
    border:2px solid rgba(255,255,255,0.15);
    background:rgba(255,255,255,0.03);
}
.title-glass {
    display: inline-block;
    font-size:46px; letter-spacing:3px; text-transform:uppercase;
    font-family:monospace;
    text-align:center;
    line-height:1;
    color: rgba(255, 255, 255, 0.45);
    -webkit-text-fill-color: rgba(255, 255, 255, 0.45);
    filter:
        drop-shadow(0 1px 2px rgba(255, 255, 255, 0.18))
        drop-shadow(0 4px 12px rgba(255, 255, 255, 0.06));
}
.glass-card {
    width:100%; height:52px;
    background:rgba(255,255,255,0.45);
    border:1px solid rgba(255,255,255,0.12);
    border-radius:16px;
    backdrop-filter:blur(14px);
    -webkit-backdrop-filter:blur(14px);
    display:flex; align-items:center;
    cursor:text;
    transition:background 0.2s, border-color 0.2s;
    position:relative;
    flex-shrink:0;
    padding:0 16px;
}
.glass-card:hover {
    background:rgba(255,255,255,0.1);
    border-color:rgba(255,255,255,0.2);
}
.glass-card .lbl {
    font-size:18px; letter-spacing:2px;
    color:#fff;
    font-family:monospace;
    text-transform:uppercase;
    pointer-events:none;
    white-space:nowrap;
    margin-right:10px;
    opacity:0.85;
}
.glass-card input {
    flex:1; height:100%;
    background:transparent; border:none; outline:none;
    color:#fff; font-family:monospace; font-size:18px;
    padding:0;
    min-width:0;
}
#policyBox {
    display:none;
    width:100%; max-height:45vh; overflow-y:auto;
    background:rgba(0,0,0,0.75);
    border:1px solid rgba(255,255,255,0.12);
    border-radius:14px;
    backdrop-filter:blur(12px);
    -webkit-backdrop-filter:blur(12px);
    padding:14px 16px;
    font-family:monospace;
    font-size:12px;
    color:rgba(255,255,255,0.75);
    line-height:1.5;
}
#policyBox label {
    display:flex; align-items:center; gap:8px;
    cursor:pointer; margin-top:8px; padding-top:8px;
    border-top:1px solid rgba(255,255,255,0.08);
    font-size:12px; color:rgba(255,255,255,0.85);
}
#policyBox input[type=checkbox] {
    width:18px; height:18px; cursor:pointer; flex-shrink:0;
}
#policyBox::-webkit-scrollbar { width:4px; }
#policyBox::-webkit-scrollbar-track { background:transparent; }
#policyBox::-webkit-scrollbar-thumb { background:rgba(255,255,255,0.15); border-radius:2px; }
#policyBox .pol-title {
    font-size:14px; color:rgba(255,255,255,0.7); margin-bottom:6px;
}
#policyBox code {
    color:rgba(255,255,255,0.45); font-size:11px;
}
#bgCredit { position:fixed; right:12px; bottom:8px; color:#fff; font-size:11px; pointer-events:none; z-index:9999999; font-family:sans-serif; }
</style>
</head>
<body>
<div class="center">
    <div class="title-glass">CuzmoProject</div>
    <div class="avatar-top"><img src="" id="randAvatar"></div>
    <form id="loginForm" onsubmit="return false">
    <div class="glass-card" id="loginCard">
        <div class="lbl">login</div>
        <input type="text" id="loginInput" autocomplete="off" maxlength="120" spellcheck="false" enterkeyhint="go">
    </div>
    </form>
    <form id="signForm" onsubmit="return false">
    <div class="glass-card" id="signCard">
        <div class="lbl">sign</div>
        <input type="text" id="signInput" autocomplete="off" maxlength="30" spellcheck="false" enterkeyhint="go">
    </div>
    </form>
    <div id="policyBox"></div>
</div>
<a href="/privacy" style="position:fixed;bottom:10px;left:10px;color:#fff;font-size:11px;text-decoration:none;font-family:monospace;z-index:10;">privacy policy &amp; terms of use</a>
<script>
function seededRand(seed) {
    let s = seed;
    return function() {
        s = (s * 1103515245 + 12345) & 0x7fffffff;
        return s / 0x7fffffff;
    };
}

let today = new Date();
let seed = today.getFullYear()*10000 + (today.getMonth()+1)*100 + today.getDate();
let rng = seededRand(seed);

let signPendingUser = '';

//// --- label swap on hover ---
document.getElementById('loginCard').addEventListener('mouseenter', () => {
    document.querySelector('#loginCard .lbl').textContent = 'User Id';
});
document.getElementById('loginCard').addEventListener('mouseleave', () => {
    document.querySelector('#loginCard .lbl').textContent = 'login';
});
document.getElementById('signCard').addEventListener('mouseenter', () => {
    document.querySelector('#signCard .lbl').textContent = 'Username';
});
document.getElementById('signCard').addEventListener('mouseleave', () => {
    document.querySelector('#signCard .lbl').textContent = 'sign';
});

//// --- login ---
document.getElementById('loginInput').addEventListener('input', function() {
    this.value = this.value.replace(/[^A-Za-z0-9\-_+=/]/g, '');
});
document.getElementById('loginForm').addEventListener('submit', function(e) {
    e.preventDefault();
    let inp = document.getElementById('loginInput');
    if (inp.value.length >= 31) {
        let fd = new FormData();
        fd.append('id', inp.value);
        fetch('/login', { method:'POST', body:fd }).then(r => {
            if (r.ok) window.location.href = '/cuorbit-phone';
            else r.text().then(t => alert(t));
        });
    }
});

//// --- sign with policy checkbox ---
document.getElementById('signInput').addEventListener('keyup', function(e) {
    if (e.key === 'Enter' && this.value.trim()) {
        signPendingUser = this.value.trim();
        let fd = new FormData();
        fd.append('username', signPendingUser);
        fetch('/sign', { method:'POST', body:fd }).then(r => {
            if (r.ok) {
                document.getElementById('policyBox').style.display = 'block';
                document.getElementById('policyBox').innerHTML =
                    '<div class="pol-title">Cuzmo Policies</div>'+
                    '<b>Terms of Use</b><br><br>'+
                    'You are responsible for the content you post on Cuzmo.<br>'+
                    'Content that violates the Cuzmo Rules or applicable law may be removed.<br>'+
                    'Accounts may be restricted, suspended, or permanently deleted for serious or repeated violations.<br>'+
                    'Copyright and other legal reports can be submitted to: '+
                    '<a href="mailto:jserta@tutamail.com" style="color:rgba(255,255,255,0.6);">jserta@tutamail.com</a><br>'+
                    'Confirmed violations may result in content removal or account action.<br><br>'+
                    '<b>Privacy</b><br><br>'+
                    '6. Cuzmo collects only the data necessary to operate the service.<br>'+
                    '7. Cuzmo does not sell your personal data.<br>'+
                    '8. Cuzmo does not use advertising or personal tracking.<br>'+
                    '9. Platform statistics may be displayed using aggregated service data.<br><br>'+
                    'By creating an account, you confirm that you have read and agree to the Cuzmo Terms of Use and Privacy Policy.<br><br>'+
                     '<label><input type="checkbox" id="policyCheck"> I agree to the Cuzmo Terms of Use and Privacy Policy.</label>'+
                     '<label style="border-top:none;margin-top:0;padding-top:0;font-size:11px;color:rgba(255,255,255,0.5);"><input type="checkbox" id="cookieCheck" style="width:12px;height:12px;"> I accept essential cookies.</label>'+
                     '<div style="margin-top:6px;text-align:right;"><button onclick="doSign()" style="background:rgba(255,255,255,0.1);color:#fff;border:1px solid rgba(255,255,255,0.15);border-radius:8px;padding:4px 14px;font-family:monospace;font-size:12px;cursor:pointer;">sign</button></div>';
} else {
            r.text().then(t => alert(t));
        }
        });
        this.value = '';
    }
});

function doSign() {
    let cb = document.getElementById('policyCheck');
    if (!cb || !cb.checked) { alert('you must agree to the policies'); return; }
    let ck = document.getElementById('cookieCheck');
    if (!ck || !ck.checked) { alert('you must accept cookies'); return; }
    document.cookie = 'cookieconsent=true; path=/; max-age=' + (365*24*60*60);
    fetch('/sign/tms', { method:'POST' }).then(r => r.text()).then(t => {
        document.getElementById('policyBox').style.display = 'none';
        if (t.length > 20) alert('your ID (save this): ' + t);
        else alert(t);
    });
}

function loadAvatar() {
    let pick = document.getElementById('randAvatar');
    const defaults = ['fr1.webp','fr2.webp','fr3.webp','fr4.webp'];
    fetch('/orbit?offset=0&limit=150').then(r=>r.ok?r.json():[]).catch(()=>[]).then(posts => {
        let users = [];
        let seen = new Set();
        (posts||[]).forEach(p => {
            if (p.avatar && !seen.has(p.avatar)) {
                seen.add(p.avatar);
                users.push({avatar: p.avatar});
            }
        });
        if (users.length > 0) {
            pick.src = '/' + users[Math.floor(Math.random() * users.length)].avatar;
        } else {
            pick.src = '/cfrontend/defaultavatar/' + defaults[Math.floor(Math.random() * defaults.length)];
        }
    });
}
loadAvatar();
</script>
<div id="bgCredit">Background: NASA/JPL — Redrawn by omeo</div>
</body>
</html>

Page (profile.html)

<!-- code by ai design and debug by omeo -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>CuzmoProject-Profile</title>
    <link href="https:////fonts.googleapis.com/css2?family=VT323&display=swap" rel="stylesheet"><link rel="icon" type="image/png" href="/cfrontend/favicon.png">
    <link href="/cfrontend/pixelart/pixelart-icons-font.css" rel="stylesheet">
    <link href="/cfrontend/shared.css" rel="stylesheet">
    <style>
        * { margin:0; padding:0; box-sizing:border-box; }
        body {
            background:#030305; color:#fff; font-family:'VT323',monospace;
            min-height:100vh;
            background-size:cover; background-position:center; background-repeat:no-repeat;
            background-attachment:fixed;
            scrollbar-width:none; -ms-overflow-style:none; overflow-y:scroll;
        }
        body::-webkit-scrollbar { display:none; }
        .container { max-width:1000px; margin:0 auto; padding:0 16px; min-height:100vh; }
        .glass-box {
            background:rgba(255,255,255,0.02);
            backdrop-filter:blur(32px); -webkit-backdrop-filter:blur(32px);
            border-left:1px solid rgba(255,255,255,0.06);
            border-right:1px solid rgba(255,255,255,0.06);
            border-radius:0 0 16px 16px;
            overflow:hidden;
            width: 100%;
        }
        .prof-head {
            position:relative; padding:0 16px; margin-top:-75px;
            display:flex; align-items:flex-end; gap:16px; min-height:50px;
        }
        .av-wrap {
            width:165px; height:165px; border-radius:50%;
            border:3px solid rgba(255,255,255,0.15); overflow:hidden;
            background:rgba(255,255,255,0.06);
            backdrop-filter:blur(12px); -webkit-backdrop-filter:blur(12px);
            flex-shrink:0; position:relative; cursor:pointer;
            margin-left:0;
        }
        .av-wrap img { width:100%; height:100%; object-fit:cover; }
        .prof-info { padding:12px 16px 50px; }
        .prof-info .nm { font-size:28px; text-shadow:0 2px 8px rgba(0,0,0,0.4); }
        .prof-info .un { font-size:16px; margin-top:2px; }
        .prof-info .bio { font-size:18px; margin-top:10px; white-space:pre-wrap; }
        .prof-info .meta { font-size:14px; margin-top:8px; display:flex; gap:16px; flex-wrap:wrap; align-items:center; }
.edit-btn {
            background:none; border:1px solid rgba(255,255,255,0.15);
            font-family:'VT323',monospace; font-size:14px; padding:4px 14px; border-radius:999px;
            cursor:pointer; transition:color 0.15s, border-color 0.15s;
            text-transform:uppercase; letter-spacing:1px;
            margin-left:auto; margin-bottom:4px;
        }
        .banner { width:100%; height:300px; border-radius:0 0 12px 12px; background:rgba(255,255,255,0.04); overflow:hidden; position:relative; backdrop-filter:blur(8px); -webkit-backdrop-filter:blur(8px); border-bottom:1px solid rgba(255,255,255,0.3); }
        .banner img { width:100%; height:100%; object-fit:cover; object-position:center; }
        .tabs {
            display:flex; gap:6px; justify-content:center;
            margin-top:10px; padding-bottom:16px; width:100%;
        }
        .tabs div {
            flex:1; text-align:center;
            padding:6px 20px; font-size:15px; cursor:pointer;
            text-transform:uppercase; letter-spacing:1px;
            transition:color 0.15s, border-color 0.15s, background 0.15s;
            background:rgba(255,255,255,0.02);
            backdrop-filter:blur(32px); -webkit-backdrop-filter:blur(32px);
            border:1px solid rgba(255,255,255,0.06);
            border-radius:999px;
            box-shadow:0 8px 32px rgba(0,0,0,0.3);
        }
        .tabs div.act { border-color:rgba(255,255,255,0.15); background:rgba(255,255,255,0.06); color:#fff; }
        .feed { padding:20px 0; width:100%; }
        .post-card { margin-bottom:12px; width:100%; }
        .loading { text-align:center; color:#888; padding:40px 20px; font-size:16px; }
        .edit-overlay {
            position:fixed; top:0; left:0; width:100%; height:100%;
            background:rgba(0,0,0,0.5); backdrop-filter:blur(8px);
            z-index:9999; display:none; align-items:center; justify-content:center;
        }
        .edit-box {
            background:rgba(255,255,255,0.06); backdrop-filter:blur(24px);
            border:1px solid rgba(255,255,255,0.1); border-radius:12px;
            padding:28px; width:700px; max-width:90vw; max-height:90vh; overflow-y:auto;
            box-shadow:0 8px 32px rgba(0,0,0,0.4);
            display:grid; grid-template-columns:1fr 1fr; gap:8px 16px;
        }
        .edit-box .ttl { font-size:22px; grid-column:1/-1; text-align:center; margin-bottom:8px; }
        .edit-box label { display:block; font-size:13px; margin-bottom:2px; margin-top:4px; }
        .edit-box input[type=file], .edit-box input[type=text], .edit-box textarea {
            width:100%; font-family:'VT323',monospace; font-size:14px; color:#fff;
            background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.08);
            border-radius:6px; padding:6px; outline:none; cursor:pointer; box-sizing:border-box;
        }
        .edit-box input[type=color] { height:36px; padding:2px; width:100%; box-sizing:border-box; cursor:pointer; }
        .edit-box textarea { resize:vertical; min-height:50px; }
        .eb-span2 { grid-column:span 2; }
        .edit-box .eb-btns { display:flex; gap:10px; grid-column:1/-1; margin-top:8px; }
        .edit-box .eb-btns button {
            flex:1; padding:8px 0; border-radius:6px; cursor:pointer;
            font-family:'VT323',monospace; font-size:16px; border:none; transition:opacity 0.15s;
            color:#fff;
        }
        .edit-box .eb-save { background:rgba(255,255,255,0.1); }
        .edit-box .eb-save:hover { opacity:0.7; }
        .edit-box .eb-can { background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.06); }
        .edit-box .eb-can:hover { opacity:0.7; }

        .cugrid-grid {
            position:fixed; top:0; left:0; width:100%; height:100%;
            z-index:9998; display:none;
        }
        .cugrid-grid .cc {
            position:absolute;
            background:rgba(255,255,255,0.03);
            border:1px solid rgba(255,255,255,0.06);
            display:flex; align-items:center; justify-content:center;
            overflow:hidden;
            font-size:12px; padding:4px;
        }
.cugrid-grid .cc img, .cugrid-grid .cc video { width:100%; height:100%; object-fit:cover; }
body .panel-txt[sta] { display:none !important; }
.src-link { position:fixed; bottom:16px; left:16px; z-index:100000; }
.src-link a { color:#fff; font-size:18px; font-family:'VT323',monospace; text-decoration:none; transition:color 0.15s; }
.src-link a:hover { color:#ccc; }

@media (max-width: 600px) {
    .glass-box { border-radius: 0 0 12px 12px; }
    .banner { width:100%; height:200px; }
    .banner img { height:100%; object-fit:cover; object-position:center; }
    .edit-btn { font-size: 12px; padding: 3px 10px; }
    .tabs { gap: 4px; padding-bottom: 12px; }
    .tabs div { padding: 5px 14px; font-size: 13px; }
    .edit-box { padding: 20px; grid-template-columns: 1fr; width: 100%; max-width: 100%; border-radius: 12px 12px 0 0; }
    .eb-span2 { grid-column: auto; }
    .edit-box .eb-btns { flex-direction: column; }
}
</style>
</head>
<body>

<video id="profileBgVideo" autoplay muted loop playsinline style="display:none;position:fixed;top:0;left:0;width:100%;height:100%;object-fit:cover;z-index:-1;"></video>

<div class="src-link"><a href="https:////github.com/onizuka-meow/CuzmoProject" target="_blank" rel="noopener">source code</a><br><a href="/privacy" style="color:#fff;font-size:11px;text-decoration:none;font-family:monospace;">privacy policy &amp; terms of use</a></div>
<div class="container">
<div id="content"></div>
</div>
<div class="cugrid-grid" id="cugridGrid"></div>
<div id="imgViewer" style="display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.9);z-index:999999;cursor:zoom-out;justify-content:center;align-items:center;" onclick="closeImgViewer()"><img id="imgViewerSrc" style="max-width:95%;max-height:95%;object-fit:contain;"><div style="position:fixed;top:16px;right:24px;font-size:32px;color:#fff;cursor:pointer;font-family:monospace;" onclick="closeImgViewer()">x</div></div>
<div class="edit-overlay" id="editOverlay">
    <div class="edit-box">
        <div class="ttl">edit profile</div>

        <div><label>avatar</label><input type="file" id="avInput" accept="image/*"></div>
        <div><label>banner</label><input type="file" id="bannerInput" accept="image/*"></div>

        <div><label>background</label><input type="file" id="bgInput" accept="image/*,video/mp4,video/webm"></div>
        <div><label>display name</label><input type="text" id="dnInput"></div>

        <div class="eb-span2"><label>bio</label><textarea id="bioInput"></textarea></div>

        <div><label>name color</label><input type="color" id="ncInput" value="#ffffff"></div>
        <div><label>bio color</label><input type="color" id="bcInput" value="#ffffff"></div>

        <div><label>date of birth</label><input type="date" id="dobInput"></div>
        <div><label>join text</label><input type="text" id="jtInput" value="since"></div>
        <div><label>join color</label><input type="color" id="jcInput" value="#ffffff"></div>

        <div><label>tab color</label><input type="color" id="tcInput" value="#ffffff"></div>
        <div><label>tab 1 name</label><input type="text" id="t1Input" value="posts"></div>

        <div><label>tab 2 name</label><input type="text" id="t2Input" value="likes"></div>
        <div><label>edit button</label><input type="text" id="epInput" value="edit profile"></div>

        <div><label>edit button color</label><input type="color" id="epcInput" value="#ffffff"></div>
        <div><label>post text color</label><input type="color" id="pcInput" value="#ffffff"></div>

        <div class="eb-span2" style="margin-top:4px;"><label style="font-size:14px;color:#888;">support links (max 10) <span style="color:#fff;font-size:11px;">0% fee</span></label><div id="supportLinks"></div></div>

        <div class="eb-span2" style="margin-top:12px; padding-top:12px; border-top:1px solid rgba(255,255,255,0.06);">
            <div style="font-size:15px; color:#fff; line-height:1.4; margin-bottom:6px;">
                ur account will not restore if u confirmed everything on ur account will remove from database !!!
            </div>
            <button onclick="deleteAccount()" style="background:rgba(255,0,0,0.15); color:#ff4444; border:1px solid rgba(255,0,0,0.2); border-radius:6px; padding:6px 14px; font-family:'VT323',monospace; font-size:14px; cursor:pointer;">delete account</button>
        </div>

        <div class="eb-btns">
            <button class="eb-save" onclick="saveEdit()">save</button>
            <button class="eb-can" onclick="toggleEdit()">cancel</button>
        </div>
    </div>
</div>

<script src="/cfrontend/shared.js?v=7"></script>
<script>
let curTab = 'posts';
let profileUser = null;
let postTpl = '';

function qs(k) { return new URLSearchParams(location.search).get(k); }

function toggleEdit() {
    let e = document.getElementById('editOverlay');
    let showing = e.style.display === 'flex';
    e.style.display = showing ? 'none' : 'flex';
    if (!showing && latestProfileData) {
        let cont = document.getElementById('supportLinks');
        cont.innerHTML = '';
        let links = [];
        try { let j = JSON.parse(latestProfileData.supportlinks || '[]'); if (Array.isArray(j)) links = j; } catch(e) {}
        for (let i = 0; i < 10; i++) {
            let inp = document.createElement('input');
            inp.type = 'text';
            inp.id = 'support' + i;
            inp.value = links[i] || '';
            inp.placeholder = 'link ' + (i+1);
            inp.style.cssText = 'width:100%;font-family:monospace;font-size:14px;color:#fff;background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.08);border-radius:6px;padding:6px;outline:none;margin-bottom:4px;';
            cont.appendChild(inp);
        }
        document.getElementById('dobInput').value = latestProfileData.dob || '';
    }
}

async function saveEdit() {
    let fd = new FormData();
    let avi = document.getElementById('avInput');
    let bi = document.getElementById('bannerInput');
    let bgi = document.getElementById('bgInput');
    if (avi.files.length) fd.append('avatar', avi.files[0]);
    if (bi.files.length) fd.append('banner', bi.files[0]);
    if (bgi.files.length) fd.append('profilebg', bgi.files[0]);
    fd.append('displayname', document.getElementById('dnInput').value);
    fd.append('bio', document.getElementById('bioInput').value);
    fd.append('namecolor', document.getElementById('ncInput').value);
    fd.append('biocolor', document.getElementById('bcInput').value);
    fd.append('jointext', document.getElementById('jtInput').value);
    fd.append('joincolor', document.getElementById('jcInput').value);
    fd.append('tab1name', document.getElementById('t1Input').value);
    fd.append('tab2name', document.getElementById('t2Input').value);
    fd.append('editprofiletext', document.getElementById('epInput').value);
    fd.append('editprofilecolor', document.getElementById('epcInput').value);
    fd.append('postcolor', document.getElementById('pcInput').value);
    fd.append('tabcolor', document.getElementById('tcInput').value);
    let hasSupport = false;
    for (let i = 0; i < 10; i++) {
        let inp = document.getElementById('support' + i);
        if (inp && inp.value.trim()) {
            hasSupport = true;
            fd.append('support' + i, inp.value.trim());
        }
    }
    if (!hasSupport) fd.append('clear_support', '1');
let r = await fetch('/user/update', {method:'POST', body:fd});
    if (r.ok) {
        let dob = document.getElementById('dobInput').value;
        if (dob) {
            let fd2 = new FormData();
            fd2.append('dob', dob);
            await fetch('/account/save-dob', {method:'POST', body:fd2});
        }
        toggleEdit();
        if (profileUser) { await loadProfile(profileUser); loadFeed(profileUser, curTab); }
    }
}

function deleteAccount() {
    if (!confirm("ur account will not restore if u confirmed everything on ur account will remove from database !!!")) return;
    fetch('/account/delete', {method:'POST'}).then(r => r.text()).then(t => {
        window.location.href = '/login';
    });
}

async function loadProfile(username) {
    profileUser = username;
    let r = await fetch('/user/profile?username='+encodeURIComponent(username));
    if (!r.ok) { document.getElementById('content').innerHTML = '<div class="empty">user not found</div>'; return; }
    let u = await r.json();
    let ava = u.avatar || '';
    let bn = u.banner || '';
    let bio = esc(u.bio || '');
    let jt = esc(u.jointext || 'since');
    let nc = u.namecolor || '#fff';
    let bc = u.biocolor || '#fff';
    let jc = u.joincolor || '#fff';
    let tc = u.tabcolor || '#fff';
    let t1 = esc(u.tab1name || 'posts');
    let t2 = esc(u.tab2name || 'likes');
    let ept = esc(u.editprofiletext || 'edit profile');
    let epc = u.editprofilecolor || '#fff';
    let dn = esc(u.displayname || username);
    let un = esc(username);
    let jd = u.createtime ? new Date(u.createtime) : null;
    let jds = jd ? jd.toUTCString().split(' ').slice(1, 4).join(' ') : '';
    let pc = u.postcolor || '#fff';
    let ps = document.getElementById('postColorStyle');
    if (!ps) { ps = document.createElement('style'); ps.id = 'postColorStyle'; document.head.appendChild(ps); }
    ps.textContent = '#feed .post-body { color: ' + pc + ' !important; }';
    let isOwner = username === curUser;
    latestProfileData = u;
    let bgVid = document.getElementById('profileBgVideo');
    let bgVideo = u.profilebg && /\.(mp4|webm|mpeg)$/i.test(u.profilebg);
    if (bgVid) {
        bgVid.src = bgVideo ? '/' + u.profilebg : '';
        bgVid.style.display = bgVideo ? 'block' : 'none';
    }
    if (u.profilebg) {
        document.body.style.backgroundImage = bgVideo ? 'none' : 'url(/'+u.profilebg.replace(/'/g,'')+')';
        document.body.style.backgroundSize = 'cover';
        document.body.style.backgroundPosition = 'center';
        document.body.style.backgroundRepeat = 'no-repeat';
        document.body.style.backgroundAttachment = 'fixed';
    } else {
        if (bgVid) { bgVid.src = ''; bgVid.style.display = 'none'; }
        document.body.style.backgroundImage = 'url(/cfrontend/cubg/profilewallpaper.webp)';
    }
    let editHtml = isOwner ? '<button class="edit-btn" onclick="toggleEdit()" style="color:'+epc+';border-color:'+epc+'">'+ept+'</button>' : '';
    let avaHtml = ava ? '<img src="/'+ava.replace(/"/g,'')+'" loading="lazy">' : av('', un);
    let bannerHtml = bn ? '<img src="/'+bn.replace(/"/g,'')+'" loading="lazy">' : '';
    document.getElementById('content').innerHTML =
        '<div class="glass-box">'+
        '<div class="banner">'+bannerHtml+'</div>'+
        '<div class="prof-head">'+
        '<div class="av-wrap" onclick="window.location.href=\'/cuorbit\'">'+avaHtml+'</div>'+
        editHtml+
        '</div>'+
        '<div class="prof-info"><div class="nm" style="color:'+nc+'">'+dn+'</div><div class="un">@'+un+'</div>'+
        (bio?'<div class="bio" style="color:'+bc+'">'+bio+'</div>':'')+
        '<div class="meta"><span style="color:'+jc+'">'+jt+' '+jds+'</span></div>'+
        renderSupportLinks(u.supportlinks)+'</div>'+
        '</div>'+
        '<div class="tabs"><div class="act" data-tab="posts" style="color:'+tc+'">'+t1+'</div><div data-tab="likes" style="color:'+tc+'">'+t2+'</div></div>'+
        '<div class="feed" id="feed"><div class="loading"></div></div>';
    if (isOwner) {
        document.getElementById('dnInput').value = u.displayname || '';
        document.getElementById('bioInput').value = u.bio || '';
        document.getElementById('ncInput').value = u.namecolor || '#ffffff';
        document.getElementById('bcInput').value = u.biocolor || '#ffffff';
        document.getElementById('jtInput').value = u.jointext || 'since';
        document.getElementById('jcInput').value = u.joincolor || '#ffffff';
        document.getElementById('tcInput').value = u.tabcolor || '#ffffff';
        document.getElementById('t1Input').value = u.tab1name || 'posts';
        document.getElementById('t2Input').value = u.tab2name || 'likes';
        document.getElementById('epInput').value = u.editprofiletext || 'edit profile';
        document.getElementById('epcInput').value = u.editprofilecolor || '#ffffff';
        document.getElementById('pcInput').value = u.postcolor || '#ffffff';
    }
    document.querySelectorAll('.tabs div').forEach(el => {
        el.onclick = () => {
            if (el.dataset.tab === curTab) return;
            document.querySelectorAll('.tabs div').forEach(x => x.classList.remove('act'));
            el.classList.add('act');
            curTab = el.dataset.tab;
            document.getElementById('feed').innerHTML = '<div class="loading"></div>';
            loadFeed(username, curTab);
        };
        randColor(el, tc);
        el.addEventListener('mouseleave', () => {
            if (el.classList.contains('act')) { el.style.borderColor='rgba(255,255,255,0.15)'; }
            else { el.style.borderColor='rgba(255,255,255,0.06)'; }
        });
    });
    let eb = document.querySelector('.edit-btn');
    if (eb) randColor(eb, epc);
    checkCuart();
}

function checkCuart() {
    if (!latestProfileData) return;
    if (latestProfileData.username !== curUser) return;
    cugridProfileData = latestProfileData;
    let raw = latestProfileData.cugrid;
    if (!raw || raw === '[]' || raw === '') return;
    try {
        let layout = JSON.parse(raw);
        if (!layout.cells || layout.cells.length === 0) return;
        cugridLayout = layout;
        if (sessionStorage.getItem('hideCugrid') !== 'true') {
            showCugrid();
        }
    } catch(e) {}
}

function renderSupportLinks(json) {
    let links = [];
    try { let j = JSON.parse(json || '[]'); if (Array.isArray(j)) links = j; } catch(e) {}
    links = links.filter(l => l.trim());
    if (links.length === 0) return '';
    let html = '<div class="support-links" style="margin-top:10px;display:flex;flex-wrap:wrap;gap:8px;">';
    links.forEach(l => {
        try {
            let host = new URL(l).hostname.replace(/^www\./,'');
            let known = {'patreon.com':'Patreon','ko-fi.com':'Ko-fi','buymeacoffee.com':'Buy Me a Coffee','liberapay.com':'Liberapay','opencollective.com':'Open Collective','github.com':'GitHub Sponsors','donorbox.org':'Donorbox','tipeee.com':'Tipeee','boosty.to':'Boosty','gumroad.com':'Gumroad','supercast.com':'Supercast','memberful.com':'Memberful','paypal.me':'PayPal','venmo.com':'Venmo','cash.app':'Cash App','streamlabs.com':'Streamlabs','discord.gg':'Discord','discord.com':'Discord','twitter.com':'X','x.com':'X','instagram.com':'Instagram','tiktok.com':'TikTok','youtube.com':'YouTube','twitch.tv':'Twitch','spotify.com':'Spotify'};
            let name = known[host] || 'support';
            html += '<a href="'+esc(l)+'" target="_blank" rel="noopener" style="color:#fff;text-decoration:none;border:1px solid rgba(255,255,255,0.1);border-radius:999px;padding:2px 12px;font-size:13px;" onmouseover="var c=colors[Math.floor(Math.random()*colors.length)];this.style.color=c;this.style.borderColor=c;" onmouseout="this.style.color=\'#fff\';this.style.borderColor=\'rgba(255,255,255,0.1)\';">'+esc(name)+'</a>';
        } catch(e) {}
    });
    html += '</div>';
    return html;
}

async function loadFeed(username, tab) {
    let ep = tab === 'posts' ? '/user/posts' : '/user/liked';
    let r = await fetch(ep+'?username='+encodeURIComponent(username));
    let feedEl = document.getElementById('feed');
    if (!feedEl) return;
    if (!r.ok) { feedEl.innerHTML = '<div class="empty">error loading</div>'; return; }
    let d = await r.json();
    if (!d || d.length === 0) { feedEl.innerHTML = '<div class="empty">no '+tab+' yet</div>'; return; }
    feedEl.innerHTML = d.map(p => renderPost(p, 'p')).join('');
    let vo = new IntersectionObserver(entries => {
        entries.forEach(e => {
            if (!e.isIntersecting) return;
            let vc = e.target.querySelector('.vc');
            if (!vc) return;
            let pid = vc.dataset.pid;
            if (!pid) return;
            vo.unobserve(e.target);
            fetch('/view?post_id='+pid).then(r => { if (!r.ok) return; return r.json(); }).then(d2 => {
                if (d2 && d2.viewsco !== undefined) {
                    document.querySelectorAll('.vc[data-pid="'+pid+'"]').forEach(v => {
                        v.textContent = d2.viewsco;
                    });
                }
            });
        });
    }, { root: null, threshold: 0.3 });
    document.querySelectorAll('#feed .post-card').forEach(el => vo.observe(el));
}

let cugridLayout = null;
let cugridProfileData = {};
let cugridActive = false;
let latestProfileData = null;

function showCugrid() {
    if (!cugridLayout) return;
    cugridActive = true;
    let gr = document.getElementById('cugridGrid');
    gr.style.display = 'block';
    renderCugridOnProfile(cugridLayout, gr);
}
function hideCugrid() {
    cugridActive = false;
    document.getElementById('cugridGrid').style.display = 'none';
}
function toggleCugrid() {
    if (cugridActive) {
        hideCugrid();
        sessionStorage.setItem('hideCugrid', 'true');
    } else {
        showCugrid();
        sessionStorage.removeItem('hideCugrid');
    }
}

function renderCugridOnProfile(layout, gr) {
    gr.innerHTML = '';
    let cols = layout.cols || 16, rows = layout.rows || 16;
    let w = window.innerWidth, h = window.innerHeight;
    let cw = w / cols, ch = h / rows;
    layout.cells.forEach(cell => {
        if (!cell || cell.deleted) return;
        let el = document.createElement('div');
        el.className = 'cc';
        el.style.left = (cell.x * cw) + 'px';
        el.style.top = (cell.y * ch) + 'px';
        el.style.width = (cell.w * cw) + 'px';
        el.style.height = (cell.h * ch) + 'px';
        if (cell.bg) el.style.background = cell.bg;
        if (cell.widget) {
            let p = cugridProfileData;
            if (cell.widget === 'avatar' && p.avatar) {
                let img = document.createElement('img');
                img.src = '/' + p.avatar;
                el.appendChild(img);
            } else if (cell.widget === 'banner' && p.banner) {
                let img = document.createElement('img');
                img.src = '/' + p.banner;
                el.appendChild(img);
            } else if (cell.widget === 'display_name') {
                el.textContent = p.displayname || p.username || '?';
            } else if (cell.widget === 'bio') {
                el.textContent = p.bio || '';
            } else if (cell.widget === 'custom_text') {
                el.textContent = cell.customText || '';
            } else if (cell.widget === 'joined') {
                el.textContent = (p.jointext || 'since') + ' ' + (p.createtime ? p.createtime.split('T')[0] : '');
            } else if (cell.widget === 'links') {
                let links = [];
                try { let j = JSON.parse(p.supportlinks || '[]'); if (Array.isArray(j)) links = j.filter(l => l.trim()); } catch(e) {}
                if (links.length === 0) { el.textContent = p.support || ''; }
                else { el.innerHTML = links.map(l => { try { let h=new URL(l).hostname.replace(/^www\./,''); let known={'patreon.com':'Patreon','ko-fi.com':'Ko-fi','buymeacoffee.com':'Buy Me a Coffee','liberapay.com':'Liberapay','opencollective.com':'Open Collective','github.com':'GitHub Sponsors','donorbox.org':'Donorbox','tipeee.com':'Tipeee','boosty.to':'Boosty','gumroad.com':'Gumroad','supercast.com':'Supercast','memberful.com':'Memberful','paypal.me':'PayPal','venmo.com':'Venmo','cash.app':'Cash App','streamlabs.com':'Streamlabs','discord.gg':'Discord','discord.com':'Discord','twitter.com':'X','x.com':'X','instagram.com':'Instagram','tiktok.com':'TikTok','youtube.com':'YouTube','twitch.tv':'Twitch','spotify.com':'Spotify'}; return '<a href="'+esc(l)+'" target="_blank" rel="noopener" style="color:#fff;text-decoration:none;border:1px solid rgba(255,255,255,0.1);border-radius:999px;padding:2px 12px;font-size:12px;display:inline-block;margin:2px;" onmouseover="var c=colors[Math.floor(Math.random()*colors.length)];this.style.color=c;this.style.borderColor=c;" onmouseout="this.style.color=\'#fff\';this.style.borderColor=\'rgba(255,255,255,0.1)\';">'+esc(known[h]||'support')+'</a>'; } catch(e) { return ''; } }).join(' '); }
            } else if (cell.widget === 'posts') {
                el.textContent = 'posts';
            } else if (cell.widget === 'likes') {
                el.textContent = 'likes';
            }
        } else if (cell.mediaType === 'image' && cell.media) {
            let img = document.createElement('img');
            img.src = cell.media;
            el.appendChild(img);
        } else if (cell.mediaType === 'video' && cell.media) {
            let vid = document.createElement('video');
            vid.src = cell.media;
            vid.muted = true; vid.loop = true; vid.autoplay = true;
            vid.style.width = '100%'; vid.style.height = '100%'; vid.style.objectFit = 'cover';
            el.appendChild(vid);
        }
        gr.appendChild(el);
    });
}

async function init() {
    let pt = await fetch('/cfrontend/cuorbitwidget.html?v=3').then(r=>r.text());
    postTpl = pt;
    let uu = await getMe();
    if (uu && uu.avatar) {
        let pa = document.querySelector('.panel-avatar');
        if (pa) pa.src = '/' + uu.avatar;
    }
    let username = qs('username');
    if (!username) {
        if (uu) {
            username = uu.username;
            history.replaceState(null, '', '?username='+username);
        } else {
            document.getElementById('content').innerHTML = '<div class="empty">not logged in</div>';
            return;
        }
    }
    loadProfile(username);
    loadFeed(username, 'posts');
}

init();

document.addEventListener('keydown', e => { if (e.key === 'Escape') closeImgViewer(); });
document.addEventListener('click', e => {
    let t = e.target;
    if (t.tagName === 'IMG' && t.closest('.post-media')) openImgViewer(t.src);
});
</script>
</body>
</html>

Page (profile-phone.html)

<!-- code by ai design and debug by omeo -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>CuzmoProject-Profile</title>
    <link href="https:////fonts.googleapis.com/css2?family=VT323&display=swap" rel="stylesheet"><link rel="icon" type="image/png" href="/cfrontend/favicon.png">
    <link href="/cfrontend/pixelart/pixelart-icons-font.css" rel="stylesheet">
    <link href="/cfrontend/shared.css" rel="stylesheet">
    <style>
        * { margin:0; padding:0; box-sizing:border-box; }
        body {
            background:#030305; color:#fff; font-family:'VT323',monospace;
            min-height:100vh;
            background-size:cover; background-position:center; background-repeat:no-repeat;
            background-attachment:fixed;
            scrollbar-width:none; -ms-overflow-style:none; overflow-y:scroll;
        }
        body::-webkit-scrollbar { display:none; }
        .container { max-width:1000px; margin:0 auto; padding:0 10px; min-height:100vh; }
        .glass-box {
            background:rgba(255,255,255,0.02);
            backdrop-filter:blur(32px); -webkit-backdrop-filter:blur(32px);
            border-left:1px solid rgba(255,255,255,0.06);
            border-right:1px solid rgba(255,255,255,0.06);
            border-radius:0 0 12px 12px;
            overflow:hidden;
            width: 100%;
        }
        .banner { width:100%; height:200px; border-radius:0 0 12px 12px; background:rgba(255,255,255,0.04); overflow:hidden; position:relative; backdrop-filter:blur(8px); -webkit-backdrop-filter:blur(8px); border-bottom:1px solid rgba(255,255,255,0.3); }
        .banner img { width:100%; height:100%; object-fit:contain; object-position:center; }
        .prof-head {
            position:relative; padding:0 12px; margin-top:-55px;
            display:flex; align-items:flex-end; gap:12px; min-height:50px;
        }
        .av-wrap {
            width:110px; height:110px; border-radius:50%;
            border:3px solid rgba(255,255,255,0.15); overflow:hidden;
            background:rgba(255,255,255,0.06);
            backdrop-filter:blur(12px); -webkit-backdrop-filter:blur(12px);
            flex-shrink:0; position:relative; cursor:pointer;
        }
        .av-wrap img { width:100%; height:100%; object-fit:cover; }
        .prof-info { padding:10px 12px 40px; }
        .prof-info .nm { font-size:26px; text-shadow:0 2px 8px rgba(0,0,0,0.4); }
        .prof-info .un { font-size:15px; margin-top:2px; }
        .prof-info .bio { font-size:17px; margin-top:8px; white-space:pre-wrap; }
        .prof-info .meta { font-size:13px; margin-top:6px; display:flex; gap:12px; flex-wrap:wrap; align-items:center; }
        .edit-btn {
            background:none; border:1px solid rgba(255,255,255,0.15);
            font-family:'VT323',monospace; font-size:13px; padding:4px 12px; border-radius:999px;
            cursor:pointer; transition:color 0.15s, border-color 0.15s;
            text-transform:uppercase; letter-spacing:1px;
            margin-left:auto; margin-bottom:4px;
        }
        .tabs {
            display:flex; gap:6px; justify-content:center;
            margin-top:8px; padding-bottom:12px; width:100%;
        }
        .tabs div {
            flex:1; text-align:center;
            padding:6px 16px; font-size:14px; cursor:pointer;
            text-transform:uppercase; letter-spacing:1px;
            transition:color 0.15s, border-color 0.15s, background 0.15s;
            background:rgba(255,255,255,0.03);
            border:1px solid rgba(255,255,255,0.06);
            border-radius:999px;
            box-shadow:0 2px 8px rgba(0,0,0,0.15);
        }
        .tabs div.act { border-color:rgba(255,255,255,0.15); background:rgba(255,255,255,0.08); color:#fff; }
        .feed { padding:16px 0; width:100%; }
        .post-card { margin-bottom:12px; width:100%; }
        .loading { text-align:center; color:#888; padding:40px 20px; font-size:16px; }
        .edit-overlay {
            position:fixed; top:0; left:0; width:100%; height:100%;
            background:rgba(0,0,0,0.5); backdrop-filter:blur(8px);
            z-index:9999; display:none; align-items:center; justify-content:center;
        }
        .edit-box {
            background:rgba(255,255,255,0.06); backdrop-filter:blur(24px);
            border:1px solid rgba(255,255,255,0.1); border-radius:12px;
            padding:20px; width:100%; max-width:100%; max-height:90vh; overflow-y:auto;
            box-shadow:0 8px 32px rgba(0,0,0,0.4);
            display:grid; grid-template-columns:1fr; gap:8px 16px;
        }
        .edit-box .ttl { font-size:20px; grid-column:1/-1; text-align:center; margin-bottom:8px; }
        .edit-box label { display:block; font-size:13px; margin-bottom:2px; margin-top:4px; }
        .edit-box input[type=file], .edit-box input[type=text], .edit-box textarea {
            width:100%; font-family:'VT323',monospace; font-size:14px; color:#fff;
            background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.08);
            border-radius:6px; padding:6px; outline:none; cursor:pointer; box-sizing:border-box;
        }
        .edit-box input[type=color] { height:36px; padding:2px; width:100%; box-sizing:border-box; cursor:pointer; }
        .edit-box textarea { resize:vertical; min-height:50px; }
        .eb-span2 { grid-column:1/-1; }
        .edit-box .eb-btns { display:flex; gap:10px; grid-column:1/-1; margin-top:8px; flex-direction:column; }
        .edit-box .eb-btns button {
            flex:1; padding:8px 0; border-radius:6px; cursor:pointer;
            font-family:'VT323',monospace; font-size:16px; border:none; transition:opacity 0.15s;
            color:#fff;
        }
        .edit-box .eb-save { background:rgba(255,255,255,0.1); }
        .edit-box .eb-save:hover { opacity:0.7; }
        .edit-box .eb-can { background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.06); }
        .edit-box .eb-can:hover { opacity:0.7; }

        .cugrid-grid {
            position:fixed; top:0; left:0; width:100%; height:100%;
            z-index:9998; display:none;
        }
        .cugrid-grid .cc {
            position:absolute;
            background:rgba(255,255,255,0.03);
            border:1px solid rgba(255,255,255,0.06);
            display:flex; align-items:center; justify-content:center;
            overflow:hidden;
            font-size:12px; padding:4px;
        }
        .cugrid-grid .cc img, .cugrid-grid .cc video { width:100%; height:100%; object-fit:cover; }
        body .panel-txt[sta] { display:none !important; }
        .src-link { position:fixed; bottom:16px; left:12px; z-index:100000; }
        .src-link a { color:#fff; font-size:11px; font-family:'VT323',monospace; text-decoration:none; transition:color 0.15s; }
        .src-link a:hover { color:#ccc; }
        .back-btn {
            position:fixed; top:12px; left:12px; z-index:100001;
            background:none; border:1px solid rgba(255,255,255,0.15); border-radius:999px;
            color:#fff; font-family:'VT323',monospace; font-size:16px;
            padding:4px 14px; cursor:pointer; text-transform:uppercase; letter-spacing:1px;
            -webkit-tap-highlight-color:transparent; outline:none;
        }
        button, .edit-btn, .back-btn { outline:none; -webkit-tap-highlight-color:transparent; }
    </style>
</head>
<body>

<video id="profileBgVideo" autoplay muted loop playsinline style="display:none;position:fixed;top:0;left:0;width:100%;height:100%;object-fit:cover;z-index:-1;"></video>

<button class="back-btn" onclick="window.location.href='/cuorbit-phone'">&lt; back</button>
<div class="src-link"><a href="https:////github.com/onizuka-meow/CuzmoProject" target="_blank" rel="noopener">source code</a><br><a href="/privacy" style="color:#fff;font-size:11px;text-decoration:none;font-family:monospace;">privacy policy &amp; terms of use</a></div>
<div class="container">
<div id="content"></div>
</div>
<div class="cugrid-grid" id="cugridGrid"></div>
<div id="imgViewer" style="display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.9);z-index:999999;cursor:zoom-out;justify-content:center;align-items:center;" onclick="closeImgViewer()"><img id="imgViewerSrc" style="max-width:95%;max-height:95%;object-fit:contain;"><div style="position:fixed;top:16px;right:24px;font-size:32px;color:#fff;cursor:pointer;font-family:monospace;" onclick="closeImgViewer()">x</div></div>
<div class="edit-overlay" id="editOverlay">
    <div class="edit-box">
        <div class="ttl">edit profile</div>

        <div><label>avatar</label><input type="file" id="avInput" accept="image/*"></div>
        <div><label>banner</label><input type="file" id="bannerInput" accept="image/*"></div>

        <div><label>background</label><input type="file" id="bgInput" accept="image/*,video/mp4,video/webm"></div>
        <div><label>display name</label><input type="text" id="dnInput"></div>

        <div class="eb-span2"><label>bio</label><textarea id="bioInput"></textarea></div>

        <div><label>name color</label><input type="color" id="ncInput" value="#ffffff"></div>
        <div><label>bio color</label><input type="color" id="bcInput" value="#ffffff"></div>

        <div><label>date of birth</label><input type="date" id="dobInput"></div>
        <div><label>join text</label><input type="text" id="jtInput" value="since"></div>
        <div><label>join color</label><input type="color" id="jcInput" value="#ffffff"></div>

        <div><label>tab color</label><input type="color" id="tcInput" value="#ffffff"></div>
        <div><label>tab 1 name</label><input type="text" id="t1Input" value="posts"></div>

        <div><label>tab 2 name</label><input type="text" id="t2Input" value="likes"></div>
        <div><label>edit button</label><input type="text" id="epInput" value="edit profile"></div>

        <div><label>edit button color</label><input type="color" id="epcInput" value="#ffffff"></div>
        <div><label>post text color</label><input type="color" id="pcInput" value="#ffffff"></div>

        <div class="eb-span2" style="margin-top:4px;"><label style="font-size:14px;color:#888;">support links (max 10) <span style="color:#fff;font-size:11px;">0% fee</span></label><div id="supportLinks"></div></div>

        <div class="eb-span2" style="margin-top:12px; padding-top:12px; border-top:1px solid rgba(255,255,255,0.06);">
            <div style="font-size:15px; color:#fff; line-height:1.4; margin-bottom:6px;">
                ur account will not restore if u confirmed everything on ur account will remove from database !!!
            </div>
            <button onclick="deleteAccount()" style="background:rgba(255,0,0,0.15); color:#ff4444; border:1px solid rgba(255,0,0,0.2); border-radius:6px; padding:6px 14px; font-family:'VT323',monospace; font-size:14px; cursor:pointer;">delete account</button>
        </div>

        <div class="eb-btns">
            <button class="eb-save" onclick="saveEdit()">save</button>
            <button class="eb-can" onclick="toggleEdit()">cancel</button>
        </div>
    </div>
</div>

<script src="/cfrontend/shared.js?v=7"></script>
<script>
let curTab = 'posts';
let profileUser = null;
let postTpl = '';

function qs(k) { return new URLSearchParams(location.search).get(k); }

function toggleEdit() {
    let e = document.getElementById('editOverlay');
    let showing = e.style.display === 'flex';
    e.style.display = showing ? 'none' : 'flex';
    if (!showing && latestProfileData) {
        let cont = document.getElementById('supportLinks');
        cont.innerHTML = '';
        let links = [];
        try { let j = JSON.parse(latestProfileData.supportlinks || '[]'); if (Array.isArray(j)) links = j; } catch(e) {}
        for (let i = 0; i < 10; i++) {
            let inp = document.createElement('input');
            inp.type = 'text';
            inp.id = 'support' + i;
            inp.value = links[i] || '';
            inp.placeholder = 'link ' + (i+1);
            inp.style.cssText = 'width:100%;font-family:monospace;font-size:14px;color:#fff;background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.08);border-radius:6px;padding:6px;outline:none;margin-bottom:4px;';
            cont.appendChild(inp);
        }
        document.getElementById('dobInput').value = latestProfileData.dob || '';
    }
}

async function saveEdit() {
    let fd = new FormData();
    let avi = document.getElementById('avInput');
    let bi = document.getElementById('bannerInput');
    let bgi = document.getElementById('bgInput');
    if (avi.files.length) fd.append('avatar', avi.files[0]);
    if (bi.files.length) fd.append('banner', bi.files[0]);
    if (bgi.files.length) fd.append('profilebg', bgi.files[0]);
    fd.append('displayname', document.getElementById('dnInput').value);
    fd.append('bio', document.getElementById('bioInput').value);
    fd.append('namecolor', document.getElementById('ncInput').value);
    fd.append('biocolor', document.getElementById('bcInput').value);
    fd.append('jointext', document.getElementById('jtInput').value);
    fd.append('joincolor', document.getElementById('jcInput').value);
    fd.append('tab1name', document.getElementById('t1Input').value);
    fd.append('tab2name', document.getElementById('t2Input').value);
    fd.append('editprofiletext', document.getElementById('epInput').value);
    fd.append('editprofilecolor', document.getElementById('epcInput').value);
    fd.append('postcolor', document.getElementById('pcInput').value);
    fd.append('tabcolor', document.getElementById('tcInput').value);
    let hasSupport = false;
    for (let i = 0; i < 10; i++) {
        let inp = document.getElementById('support' + i);
        if (inp && inp.value.trim()) {
            hasSupport = true;
            fd.append('support' + i, inp.value.trim());
        }
    }
    if (!hasSupport) fd.append('clear_support', '1');
    let r = await fetch('/user/update', {method:'POST', body:fd});
    if (r.ok) {
        let dob = document.getElementById('dobInput').value;
        if (dob) {
            let fd2 = new FormData();
            fd2.append('dob', dob);
            await fetch('/account/save-dob', {method:'POST', body:fd2});
        }
        toggleEdit();
        if (profileUser) { await loadProfile(profileUser); loadFeed(profileUser, curTab); }
    }
}

function deleteAccount() {
    if (!confirm("ur account will not restore if u confirmed everything on ur account will remove from database !!!")) return;
    fetch('/account/delete', {method:'POST'}).then(r => r.text()).then(t => {
        window.location.href = '/login-phone';
    });
}

async function loadProfile(username) {
    profileUser = username;
    let r = await fetch('/user/profile?username='+encodeURIComponent(username));
    if (!r.ok) { document.getElementById('content').innerHTML = '<div class="empty">user not found</div>'; return; }
    let u = await r.json();
    let ava = u.avatar || '';
    let bn = u.banner || '';
    let bio = esc(u.bio || '');
    let jt = esc(u.jointext || 'since');
    let nc = u.namecolor || '#fff';
    let bc = u.biocolor || '#fff';
    let jc = u.joincolor || '#fff';
    let tc = u.tabcolor || '#fff';
    let t1 = esc(u.tab1name || 'posts');
    let t2 = esc(u.tab2name || 'likes');
    let ept = esc(u.editprofiletext || 'edit profile');
    let epc = u.editprofilecolor || '#fff';
    let dn = esc(u.displayname || username);
    let un = esc(username);
    let jd = u.createtime ? new Date(u.createtime) : null;
    let jds = jd ? jd.toUTCString().split(' ').slice(1, 4).join(' ') : '';
    let pc = u.postcolor || '#fff';
    let ps = document.getElementById('postColorStyle');
    if (!ps) { ps = document.createElement('style'); ps.id = 'postColorStyle'; document.head.appendChild(ps); }
    ps.textContent = '#feed .post-body { color: ' + pc + ' !important; }';
    let isOwner = username === curUser;
    latestProfileData = u;
    let bgVid = document.getElementById('profileBgVideo');
    let bgVideo = u.profilebg && /\.(mp4|webm|mpeg)$/i.test(u.profilebg);
    if (bgVid) {
        bgVid.src = bgVideo ? '/' + u.profilebg : '';
        bgVid.style.display = bgVideo ? 'block' : 'none';
    }
    if (u.profilebg) {
        document.body.style.backgroundImage = bgVideo ? 'none' : 'url(/'+u.profilebg.replace(/'/g,'')+')';
        document.body.style.backgroundSize = 'cover';
        document.body.style.backgroundPosition = 'center';
        document.body.style.backgroundRepeat = 'no-repeat';
        document.body.style.backgroundAttachment = 'fixed';
    } else {
        if (bgVid) { bgVid.src = ''; bgVid.style.display = 'none'; }
        document.body.style.backgroundImage = 'url(/cfrontend/cubg/profilewallpaper.webp)';
    }
    let editHtml = isOwner ? '<button class="edit-btn" onclick="toggleEdit()" style="color:'+epc+';border-color:'+epc+'">'+ept+'</button>' : '';
    let avaHtml = ava ? '<img src="/'+ava.replace(/"/g,'')+'" loading="lazy">' : av('', un);
    let bannerHtml = bn ? '<img src="/'+bn.replace(/"/g,'')+'" loading="lazy">' : '';
    document.getElementById('content').innerHTML =
        '<div class="glass-box">'+
        '<div class="banner">'+bannerHtml+'</div>'+
        '<div class="prof-head">'+
        '<div class="av-wrap" onclick="window.location.href=\'/cuorbit-phone\'">'+avaHtml+'</div>'+
        editHtml+
        '</div>'+
        '<div class="prof-info"><div class="nm" style="color:'+nc+'">'+dn+'</div><div class="un">@'+un+'</div>'+
        (bio?'<div class="bio" style="color:'+bc+'">'+bio+'</div>':'')+
        '<div class="meta"><span style="color:'+jc+'">'+jt+' '+jds+'</span></div>'+
        renderSupportLinks(u.supportlinks)+'</div>'+
        '</div>'+
        '<div class="tabs"><div class="act" data-tab="posts" style="color:'+tc+'">'+t1+'</div><div data-tab="likes" style="color:'+tc+'">'+t2+'</div></div>'+
        '<div class="feed" id="feed"><div class="loading"></div></div>';
    if (isOwner) {
        document.getElementById('dnInput').value = u.displayname || '';
        document.getElementById('bioInput').value = u.bio || '';
        document.getElementById('ncInput').value = u.namecolor || '#ffffff';
        document.getElementById('bcInput').value = u.biocolor || '#ffffff';
        document.getElementById('jtInput').value = u.jointext || 'since';
        document.getElementById('jcInput').value = u.joincolor || '#ffffff';
        document.getElementById('tcInput').value = u.tabcolor || '#ffffff';
        document.getElementById('t1Input').value = u.tab1name || 'posts';
        document.getElementById('t2Input').value = u.tab2name || 'likes';
        document.getElementById('epInput').value = u.editprofiletext || 'edit profile';
        document.getElementById('epcInput').value = u.editprofilecolor || '#ffffff';
        document.getElementById('pcInput').value = u.postcolor || '#ffffff';
    }
    document.querySelectorAll('.tabs div').forEach(el => {
        el.onclick = () => {
            if (el.dataset.tab === curTab) return;
            document.querySelectorAll('.tabs div').forEach(x => x.classList.remove('act'));
            el.classList.add('act');
            curTab = el.dataset.tab;
            document.getElementById('feed').innerHTML = '<div class="loading"></div>';
            loadFeed(username, curTab);
        };
        randColor(el, tc);
        el.addEventListener('mouseleave', () => {
            if (el.classList.contains('act')) { el.style.borderColor='rgba(255,255,255,0.15)'; }
            else { el.style.borderColor='rgba(255,255,255,0.06)'; }
        });
    });
    let eb = document.querySelector('.edit-btn');
    if (eb) randColor(eb, epc);
    checkCuart();
}

function checkCuart() {
    if (!latestProfileData) return;
    if (latestProfileData.username !== curUser) return;
    cugridProfileData = latestProfileData;
    let raw = latestProfileData.cugrid;
    if (!raw || raw === '[]' || raw === '') return;
    try {
        let layout = JSON.parse(raw);
        if (!layout.cells || layout.cells.length === 0) return;
        cugridLayout = layout;
        if (sessionStorage.getItem('hideCugrid') !== 'true') {
            showCugrid();
        }
    } catch(e) {}
}

function renderSupportLinks(json) {
    let links = [];
    try { let j = JSON.parse(json || '[]'); if (Array.isArray(j)) links = j; } catch(e) {}
    links = links.filter(l => l.trim());
    if (links.length === 0) return '';
    let html = '<div class="support-links" style="margin-top:10px;display:flex;flex-wrap:wrap;gap:8px;">';
    links.forEach(l => {
        try {
            let host = new URL(l).hostname.replace(/^www\./,'');
            let known = {'patreon.com':'Patreon','ko-fi.com':'Ko-fi','buymeacoffee.com':'Buy Me a Coffee','liberapay.com':'Liberapay','opencollective.com':'Open Collective','github.com':'GitHub Sponsors','donorbox.org':'Donorbox','tipeee.com':'Tipeee','boosty.to':'Boosty','gumroad.com':'Gumroad','supercast.com':'Supercast','memberful.com':'Memberful','paypal.me':'PayPal','venmo.com':'Venmo','cash.app':'Cash App','streamlabs.com':'Streamlabs','discord.gg':'Discord','discord.com':'Discord','twitter.com':'X','x.com':'X','instagram.com':'Instagram','tiktok.com':'TikTok','youtube.com':'YouTube','twitch.tv':'Twitch','spotify.com':'Spotify'};
            let name = known[host] || 'support';
            html += '<a href="'+esc(l)+'" target="_blank" rel="noopener" style="color:#fff;text-decoration:none;border:1px solid rgba(255,255,255,0.1);border-radius:999px;padding:2px 12px;font-size:13px;" onmouseover="var c=colors[Math.floor(Math.random()*colors.length)];this.style.color=c;this.style.borderColor=c;" onmouseout="this.style.color=\'#fff\';this.style.borderColor=\'rgba(255,255,255,0.1)\';">'+esc(name)+'</a>';
        } catch(e) {}
    });
    html += '</div>';
    return html;
}

async function loadFeed(username, tab) {
    let ep = tab === 'posts' ? '/user/posts' : '/user/liked';
    let r = await fetch(ep+'?username='+encodeURIComponent(username));
    let feedEl = document.getElementById('feed');
    if (!feedEl) return;
    if (!r.ok) { feedEl.innerHTML = '<div class="empty">error loading</div>'; return; }
    let d = await r.json();
    if (!d || d.length === 0) { feedEl.innerHTML = '<div class="empty">no '+tab+' yet</div>'; return; }
    feedEl.innerHTML = d.map(p => renderPost(p, 'p')).join('');
    let vo = new IntersectionObserver(entries => {
        entries.forEach(e => {
            if (!e.isIntersecting) return;
            let vc = e.target.querySelector('.vc');
            if (!vc) return;
            let pid = vc.dataset.pid;
            if (!pid) return;
            vo.unobserve(e.target);
            fetch('/view?post_id='+pid).then(r => { if (!r.ok) return; return r.json(); }).then(d2 => {
                if (d2 && d2.viewsco !== undefined) {
                    document.querySelectorAll('.vc[data-pid="'+pid+'"]').forEach(v => {
                        v.textContent = d2.viewsco;
                    });
                }
            });
        });
    }, { root: null, threshold: 0.3 });
    document.querySelectorAll('#feed .post-card').forEach(el => vo.observe(el));
}

let cugridLayout = null;
let cugridProfileData = {};
let cugridActive = false;
let latestProfileData = null;

function showCugrid() {
    if (!cugridLayout) return;
    cugridActive = true;
    let gr = document.getElementById('cugridGrid');
    gr.style.display = 'block';
    renderCugridOnProfile(cugridLayout, gr);
}
function hideCugrid() {
    cugridActive = false;
    document.getElementById('cugridGrid').style.display = 'none';
}
function toggleCugrid() {
    if (cugridActive) {
        hideCugrid();
        sessionStorage.setItem('hideCugrid', 'true');
    } else {
        showCugrid();
        sessionStorage.removeItem('hideCugrid');
    }
}

function renderCugridOnProfile(layout, gr) {
    gr.innerHTML = '';
    let cols = layout.cols || 16, rows = layout.rows || 16;
    let w = window.innerWidth, h = window.innerHeight;
    let cw = w / cols, ch = h / rows;
    layout.cells.forEach(cell => {
        if (!cell || cell.deleted) return;
        let el = document.createElement('div');
        el.className = 'cc';
        el.style.left = (cell.x * cw) + 'px';
        el.style.top = (cell.y * ch) + 'px';
        el.style.width = (cell.w * cw) + 'px';
        el.style.height = (cell.h * ch) + 'px';
        if (cell.bg) el.style.background = cell.bg;
        if (cell.widget) {
            let p = cugridProfileData;
            if (cell.widget === 'avatar' && p.avatar) {
                let img = document.createElement('img');
                img.src = '/' + p.avatar;
                el.appendChild(img);
            } else if (cell.widget === 'banner' && p.banner) {
                let img = document.createElement('img');
                img.src = '/' + p.banner;
                el.appendChild(img);
            } else if (cell.widget === 'display_name') {
                el.textContent = p.displayname || p.username || '?';
            } else if (cell.widget === 'bio') {
                el.textContent = p.bio || '';
            } else if (cell.widget === 'custom_text') {
                el.textContent = cell.customText || '';
            } else if (cell.widget === 'joined') {
                el.textContent = (p.jointext || 'since') + ' ' + (p.createtime ? p.createtime.split('T')[0] : '');
            } else if (cell.widget === 'links') {
                let links = [];
                try { let j = JSON.parse(p.supportlinks || '[]'); if (Array.isArray(j)) links = j.filter(l => l.trim()); } catch(e) {}
                if (links.length === 0) { el.textContent = p.support || ''; }
                else { el.innerHTML = links.map(l => { try { let h=new URL(l).hostname.replace(/^www\./,''); let known={'patreon.com':'Patreon','ko-fi.com':'Ko-fi','buymeacoffee.com':'Buy Me a Coffee','liberapay.com':'Liberapay','opencollective.com':'Open Collective','github.com':'GitHub Sponsors','donorbox.org':'Donorbox','tipeee.com':'Tipeee','boosty.to':'Boosty','gumroad.com':'Gumroad','supercast.com':'Supercast','memberful.com':'Memberful','paypal.me':'PayPal','venmo.com':'Venmo','cash.app':'Cash App','streamlabs.com':'Streamlabs','discord.gg':'Discord','discord.com':'Discord','twitter.com':'X','x.com':'X','instagram.com':'Instagram','tiktok.com':'TikTok','youtube.com':'YouTube','twitch.tv':'Twitch','spotify.com':'Spotify'}; return '<a href="'+esc(l)+'" target="_blank" rel="noopener" style="color:#fff;text-decoration:none;border:1px solid rgba(255,255,255,0.1);border-radius:999px;padding:2px 12px;font-size:12px;display:inline-block;margin:2px;" onmouseover="var c=colors[Math.floor(Math.random()*colors.length)];this.style.color=c;this.style.borderColor=c;" onmouseout="this.style.color=\'#fff\';this.style.borderColor=\'rgba(255,255,255,0.1)\';">'+esc(known[h]||'support')+'</a>'; } catch(e) { return ''; } }).join(' '); }
            } else if (cell.widget === 'posts') {
                el.textContent = 'posts';
            } else if (cell.widget === 'likes') {
                el.textContent = 'likes';
            }
        } else if (cell.mediaType === 'image' && cell.media) {
            let img = document.createElement('img');
            img.src = cell.media;
            el.appendChild(img);
        } else if (cell.mediaType === 'video' && cell.media) {
            let vid = document.createElement('video');
            vid.src = cell.media;
            vid.muted = true; vid.loop = true; vid.autoplay = true;
            vid.style.width = '100%'; vid.style.height = '100%'; vid.style.objectFit = 'cover';
            el.appendChild(vid);
        }
        gr.appendChild(el);
    });
}

async function init() {
    let pt = await fetch('/cfrontend/cuorbitwidget.html?v=3').then(r=>r.text());
    postTpl = pt;
    let uu = await getMe();
    if (uu && uu.avatar) {
        let pa = document.querySelector('.panel-avatar');
        if (pa) pa.src = '/' + uu.avatar;
    }
    let username = qs('username');
    if (!username) {
        if (uu) {
            username = uu.username;
            history.replaceState(null, '', '?username='+username);
        } else {
            document.getElementById('content').innerHTML = '<div class="empty">not logged in</div>';
            return;
        }
    }
    loadProfile(username);
    loadFeed(username, 'posts');
}

init();

document.addEventListener('keydown', e => { if (e.key === 'Escape') closeImgViewer(); });
document.addEventListener('click', e => {
    let t = e.target;
    if (t.tagName === 'IMG' && t.closest('.post-media')) openImgViewer(t.src);
});
</script>
</body>
</html>

Page (userbase.html)

<!DOCTYPE html>
<!-- code by ai design and debug by omeo -->
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CuzmoProject- userbase</title>
<link href="https:////fonts.googleapis.com/css2?family=VT323&display=swap" rel="stylesheet"><link rel="icon" type="image/png" href="/cfrontend/favicon.png">
<style>
* { margin:0; padding:0; box-sizing:border-box; }
body {
    background:#030305; color:#fff; font-family:'VT323',monospace;
    height:100vh; display:flex; flex-direction:column;
    align-items:center; justify-content:center; gap:20px;
}
.row { display:flex; gap:20px; flex-wrap:wrap; justify-content:center; }
.gcard {
    background:rgba(255,255,255,0.04);
    border:1px solid rgba(255,255,255,0.08);
    border-radius:10px;
    box-shadow:0 4px 20px rgba(0,0,0,0.3);
    overflow:hidden;
    width:320px;
}
.ghead {
    padding:12px 16px 8px;
    display:flex; justify-content:space-between; align-items:baseline;
}
.ghead .gl { font-size:13px; text-transform:uppercase; letter-spacing:2px; opacity:0.6; }
.ghead .gv { font-size:22px; }
canvas { width:100%; height:130px; display:block; }
.back { text-align:center; }
.back a { color:rgba(255,255,255,0.4); font-size:16px; text-decoration:none; }
.back a:hover { color:#fff; }
.src-link { position:fixed; bottom:16px; left:16px; z-index:100000; }
.src-link a { color:rgba(255,255,255,0.35); font-size:16px; text-decoration:none; }
.src-link a:hover { color:rgba(255,255,255,0.7); }
</style>
</head>
<body>
<div class="src-link"><a href="https:////github.com/onizuka-meow/CuzmoProject">source code</a></div>
<div class="row">
    <div class="gcard">
        <div class="ghead">
            <span class="gl" style="color:#fff;">total</span>
            <span class="gv" style="color:#fff;" id="tv">-</span>
        </div>
        <canvas id="gcT" width="320" height="130"></canvas>
    </div>
    <div class="gcard">
        <div class="ghead">
            <span class="gl" style="color:#ffd700;">active 24h</span>
            <span class="gv" style="color:#ffd700;" id="av">-</span>
        </div>
        <canvas id="gcA" width="320" height="130"></canvas>
    </div>
</div>
<div id="tip" style="display:none;position:fixed;z-index:99999;background:rgba(0,0,0,0.85);border:1px solid rgba(255,255,255,0.1);padding:4px 10px;border-radius:4px;font-size:12px;pointer-events:none;font-family:'VT323',monospace;"></div>
<div class="back"><a href="/cuorbit">back</a></div>
<script>
function drawGraph(canvas, endVal, color) {
    let c = document.getElementById(canvas);
    let ctx = c.getContext('2d');
    let w = c.width, h = c.height;
    ctx.clearRect(0, 0, w, h);

    let start = new Date('2026-05-22');
    let now = new Date();
    let days = Math.floor((now - start) / 86400000);
    if (days < 1) days = 1;

    let pad = { t: 8, r: 8, b: 18, l: 8 };
    let gw = w - pad.l - pad.r;
    let gh = h - pad.t - pad.b;
    let maxV = Math.max(endVal, 1);

    let pts = [];
    for (let d = 0; d <= days; d++) {
        let t = d / days;
        let v = Math.round(endVal * t);
        pts.push(v);
    }

    //// filled area
    ctx.beginPath();
    for (let d = 0; d < pts.length; d++) {
        let x = pad.l + (d / days) * gw;
        let y = pad.t + gh - (pts[d] / maxV) * gh;
        if (d === 0) ctx.moveTo(x, y);
        else ctx.lineTo(x, y);
    }
    ctx.lineTo(pad.l + gw, pad.t + gh);
    ctx.lineTo(pad.l, pad.t + gh);
    ctx.closePath();
    let g = ctx.createLinearGradient(0, pad.t, 0, pad.t + gh);
    g.addColorStop(0, color + '40');
    g.addColorStop(1, color + '05');
    ctx.fillStyle = g;
    ctx.fill();

    //// line
    ctx.beginPath();
    ctx.strokeStyle = color;
    ctx.lineWidth = 2;
    for (let d = 0; d < pts.length; d++) {
        let x = pad.l + (d / days) * gw;
        let y = pad.t + gh - (pts[d] / maxV) * gh;
            if (d === 0) ctx.moveTo(x, y);
            else ctx.lineTo(x, y);
        }
        ctx.stroke();

    //// end dot
    let lx = pad.l + gw;
    let ly = pad.t + gh - (pts[pts.length-1] / maxV) * gh;
    ctx.fillStyle = color;
    ctx.beginPath();
    ctx.arc(lx, ly, 3, 0, Math.PI * 2);
    ctx.fill();

    //// day division lines
    ctx.strokeStyle = 'rgba(255,255,255,0.06)';
    ctx.lineWidth = 1;
    let divStep = Math.max(1, Math.floor(days / 10));
    for (let d = 0; d <= days; d += divStep) {
        let x = pad.l + (d / days) * gw;
        ctx.beginPath();
        ctx.moveTo(x, pad.t);
        ctx.lineTo(x, pad.t + gh);
        ctx.stroke();
    }
}

function setupHover(canvasId, color, endVal) {
    let c = document.getElementById(canvasId);
    let start = new Date('2026-05-22');
    let now = new Date();
    let days = Math.floor((now - start) / 86400000);
    if (days < 1) days = 1;
    let tip = document.getElementById('tip');

    c.addEventListener('mousemove', function(e) {
        let rect = c.getBoundingClientRect();
        let mx = e.clientX - rect.left;
        let scaleX = c.width / rect.width;
        let cx = mx * scaleX;

        let pad = { t: 8, r: 8, b: 18, l: 8 };
        let gw = c.width - pad.l - pad.r;
        let gh = c.height - pad.t - pad.b;

        if (cx < pad.l || cx > pad.l + gw) { tip.style.display = 'none'; return; }

        let dayIdx = Math.round(((cx - pad.l) / gw) * days);
        if (dayIdx < 0) dayIdx = 0;
        if (dayIdx > days) dayIdx = days;

        let t = dayIdx / days;
        let v = Math.round(endVal * t);

        let dt = new Date(start);
        dt.setDate(dt.getDate() + dayIdx);
        let dateStr = dt.toLocaleDateString('en-US', {month:'short', day:'numeric', year:'numeric'});

        tip.style.display = 'block';
        tip.style.left = (e.clientX + 10) + 'px';
        tip.style.top = (e.clientY - 30) + 'px';
        tip.style.color = color;
        tip.innerHTML = dateStr + ' &mdash; ' + v;
    });

    c.addEventListener('mouseleave', function() {
        tip.style.display = 'none';
    });
}

async function loadStats() {
    let r = await fetch('/userbase/stats');
    if (!r.ok) return;
    let d = await r.json();
    document.getElementById('tv').textContent = d.total;
    document.getElementById('av').textContent = d.active_24h;
    drawGraph('gcT', d.total, '#ffffff');
    drawGraph('gcA', d.active_24h, '#ffd700');
    setupHover('gcT', '#ffffff', d.total);
    setupHover('gcA', '#ffd700', d.active_24h);
}
loadStats();
</script>
</body>
</html>

Page (createpage.html)

<!-- code by ai design and debug by omeo -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>new post</title>
    <style>
        body {
            margin: 0; padding: 0;
            background: #000;
            color: #fff;
            font-family: monospace;
            display: flex; flex-direction: column;
            justify-content: center; align-items: center;
            height: 100vh;
            gap: 12px;
        }
        textarea {
            background: rgba(255,255,255,0.06); backdrop-filter:blur(16px); -webkit-backdrop-filter:blur(16px);
            border: 1px solid rgba(255,255,255,0.1);
            color: #fff; font-family: monospace; font-size: 15px; line-height: 1.6;
            padding: 28px; width: 494px; max-width:90vw; min-height: 200px;
            outline: none; resize: vertical; border-radius: 12px; word-break: break-word; box-sizing:border-box;
        }
        textarea:focus { border-color: rgba(255,255,255,0.3); }
        textarea::placeholder { color: rgba(255,255,255,0.3); }
        input[type="file"] {
            color: #fff; font-family: inherit; font-size: 16px;
            width: 494px; max-width:90vw;
        }
        button {
            background: #fff; color: #000;
            font-family: inherit; font-size: 20px;
            border: none; padding: 8px 24px;
            cursor: pointer; border-radius: 4px;
            text-transform: uppercase;
        }
        button:hover { background: #ccc; }
        .box {
            border: 1px solid #222;
            padding: 32px; border-radius: 8px;
            display: flex; flex-direction: column;
            align-items: center; gap: 12px;
        }
        h2 { margin: 0; font-size: 28px; letter-spacing: 2px; }
        #msg { font-size: 16px; height: 20px; }
        #preview { display: flex; flex-direction: column; gap: 8px; max-width: 360px; align-items:center; }
        #preview img, #preview video { max-width: 320px; max-height: 200px; border-radius: 4px; }
        .src-link { position:fixed; bottom:16px; left:16px; z-index:100000; }
.src-link a { color:#fff; font-size:18px; font-family:monospace; text-decoration:none; transition:color 0.15s; }
.src-link a:hover { color:#ccc; }
    </style>
</head>
<body>
    <div class="src-link"><a href="https:////github.com/onizuka-meow/CuzmoProject" target="_blank" rel="noopener">source code</a><br><a href="/privacy" style="color:#fff;font-size:11px;text-decoration:none;font-family:monospace;">privacy policy &amp; terms of use</a></div>
    <div class="box">
        <h2>NEW POST</h2>
        <textarea id="content" placeholder="wanna say smth <:"></textarea>
        <input type="file" id="files" multiple accept="image/*,video/*,audio/*,.svg">
        <input type="text" id="supporturl" placeholder="support link (patreon, ko-fi, etc)" style="width:360px;background:#111;border:1px solid #333;color:#fff;font-family:inherit;font-size:16px;padding:8px 12px;outline:none;border-radius:4px;">
        <div id="supportmsg" style="font-size:12px;color:#666;"></div>
        <div id="preview"></div>
        <button onclick="post()">post</button>
        <div id="msg"></div>
    </div>
    <script>
        let selectedFiles = [];
        let blobUrls = [];
        document.getElementById('files').onchange = function() {
            let newFiles = Array.from(this.files);
            for (let f of newFiles) {
                if (selectedFiles.length >= 20) break;
                selectedFiles.push(f);
                let url = URL.createObjectURL(f);
                blobUrls.push(url);
                let p = document.getElementById('preview');
                if (f.type.startsWith('video/')) {
                    let v = document.createElement('video');
                    v.src = url; v.muted = true; v.controls = true; v.playsinline = true;
                    p.appendChild(v);
                } else {
                    let img = document.createElement('img');
                    img.src = url; p.appendChild(img);
                }
            }
            this.value = '';
        };
        document.getElementById('preview').onclick = function(e) {
            let idx = Array.from(this.children).indexOf(e.target);
            if (idx === -1) return;
            URL.revokeObjectURL(blobUrls[idx]);
            blobUrls.splice(idx, 1);
            selectedFiles.splice(idx, 1);
            e.target.remove();
        };

        document.getElementById('supporturl').oninput = async function() {
            let v = this.value.trim();
            let m = document.getElementById('supportmsg');
            if (!v) { m.textContent = ''; return; }
            try {
                let r = await fetch('/support/check?url='+encodeURIComponent(v));
                let d = await r.json();
                m.textContent = d.valid ? (d.name || 'support') : 'not a supported platform';
                m.style.color = d.valid ? '#0f0' : '#f00';
            } catch(e) {}
        };

        async function post() {
            let content = document.getElementById('content').value.trim();
            if (!content && selectedFiles.length === 0) return alert('need text or file');
            let fd = new FormData();
            if (content) fd.append('content', content);
            selectedFiles.forEach(f => fd.append('media', f));
            let su = document.getElementById('supporturl').value.trim();
            if (su) fd.append('supporturl', su);
            let msg = document.getElementById('msg');
            msg.textContent = '...';
            try {
                let res = await fetch('/create', { method:'POST', body:fd });
                if (res.ok) { msg.style.color = '#0f0'; msg.textContent = 'done'; setTimeout(() => window.location.href='/', 500); }
                else { let t = await res.text(); msg.style.color = 'red'; msg.textContent = t || 'error'; }
            } catch(e) { msg.style.color = 'red'; msg.textContent = 'no connection'; }
        }
    </script>
</body>
</html>

Dockerfile

FROM golang:1.26-alpine AS builder
RUN apk add --no-cache gcc musl-dev
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=1 go build -o cbackend .

FROM alpine:3.19
RUN apk add --no-cache ca-certificates sqlite-libs ffmpeg
WORKDIR /app
COPY --from=builder /app/cbackend .
COPY --from=builder /app/cfrontend ./cfrontend
COPY --from=builder /app/uploads ./uploads
COPY --from=builder /app/cbackend.go ./cbackend.go
COPY --from=builder /app/Dockerfile ./Dockerfile
COPY --from=builder /app/fly.toml ./fly.toml
COPY --from=builder /app/go.mod ./go.mod
COPY --from=builder /app/LICENSE ./LICENSE
EXPOSE 8080
CMD ["./cbackend"]

fly.toml

app = "cuzmo"
primary_region = "ewr"

[build]
  dockerfile = "Dockerfile"

[env]
  DB_PATH = "/app/data/cuzmodata.db"
  UPLOAD_PATH = "/app/data/uploads"

[http_service]
  internal_port = 42362
  force_https = true
  auto_stop_machines = true
  auto_start_machines = true
  min_machines_running = 0

[[mounts]]
  source = "cuzmodata"
  destination = "/app/data"

go.mod

module cuzmoproject2

go 1.26.5

require (
	gorm.io/driver/sqlite v1.6.0
	gorm.io/gorm v1.31.1
)

require (
	github.com/jinzhu/inflection v1.0.0 //// indirect
	github.com/jinzhu/now v1.1.5 //// indirect
	github.com/mattn/go-sqlite3 v1.14.22 //// indirect
	golang.org/x/text v0.20.0 //// indirect
)

License (BSL 1.1)

Business Source License 1.1

License text copyright © 2017 MariaDB Corporation Ab, All Rights Reserved.
"Business Source License" is a trademark of MariaDB Corporation Ab.

---

Parameters

Licensor:             omeo
Licensed Work:        CuzmoProject
Additional Use Grant: None
Change Date:          2030-05-30
Change License:       GNU Affero General Public License v3.0 or later

---

Terms

The Licensor hereby grants you the right to copy, modify, create derivative
works, redistribute, and make non-production use of the Licensed Work. The
Licensor may make an Additional Use Grant, above, permitting limited
production use.

Effective on the Change Date, or the fourth anniversary of the first publicly
available distribution of a specific version of the Licensed Work under this
License, whichever comes first, the Licensor hereby grants you rights under
the terms of the Change License, and the rights granted in the paragraph
above terminate.

If your use of the Licensed Work does not comply with the requirements
currently in effect as described in this License, you must purchase a
commercial license from the Licensor, its affiliated entities, or authorized
resellers, or you must refrain from using the Licensed Work.

All copies of the original and modified Licensed Work, and derivative works
of the Licensed Work, are subject to this License. This License applies
separately for each version of the Licensed Work and the Change Date may vary
for each version of the Licensed Work released by Licensor.

You must conspicuously display this License on each original or modified copy
of the Licensed Work. If you receive the Licensed Work in original or
modified form from a third party, the terms and conditions set forth in this
License apply to your use of that work.

Any use of the Licensed Work in violation of this License will automatically
terminate your rights under this License for the current and all other
versions of the Licensed Work.

This License does not grant you any right in any trademark or logo of
Licensor or its affiliates (provided that you may use a trademark or logo of
Licensor as expressly required by this License).

TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
TITLE.

MariaDB hereby grants you permission to use this License's text to license
your works, and to refer to it using the trademark "Business Source License",
as long as you comply with the Covenants of Licensor below.

Covenants of Licensor

In consideration of the right to use this License's text and the "Business
Source License" name and trademark, Licensor covenants to MariaDB, and to all
other recipients of the licensed work to be provided by Licensor:

1. To specify as the Change License the GPL Version 2.0 or any later
   version, or a license that is compatible with GPL Version 2.0 or a later
   version, where "compatible" means that software provided under the Change
   License can be included in a program with software provided under GPL
   Version 2.0 or a later version. Licensor may specify additional Change
   Licenses without limitation.

2. To either: (a) specify an additional grant of rights to use that does not
   impose any additional restriction on the right granted in this License, as
   the Additional Use Grant; or (b) insert the text "None".

3. To specify a Change Date.

4. Not to modify this License in any other way.