initial commit
This commit is contained in:
167
fluxer_marketing/src/fluxer_marketing.gleam
Normal file
167
fluxer_marketing/src/fluxer_marketing.gleam
Normal file
@@ -0,0 +1,167 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import fluxer_marketing/config
|
||||
import fluxer_marketing/geoip
|
||||
import fluxer_marketing/i18n
|
||||
import fluxer_marketing/locale.{type Locale, get_locale_from_code}
|
||||
import fluxer_marketing/metrics
|
||||
import fluxer_marketing/middleware/cache_middleware
|
||||
import fluxer_marketing/router
|
||||
import fluxer_marketing/visionary_slots
|
||||
import fluxer_marketing/web
|
||||
import gleam/erlang/atom.{type Atom}
|
||||
import gleam/erlang/process
|
||||
import gleam/http/request
|
||||
import gleam/list
|
||||
import gleam/result
|
||||
import gleam/string
|
||||
import mist
|
||||
import wisp
|
||||
import wisp/wisp_mist
|
||||
|
||||
pub fn main() {
|
||||
wisp.configure_logger()
|
||||
|
||||
let assert Ok(cfg) = config.load_config()
|
||||
|
||||
let i18n_db = i18n.setup_database()
|
||||
let slots_cache =
|
||||
visionary_slots.start(visionary_slots.Settings(
|
||||
api_host: cfg.api_host,
|
||||
rpc_secret: cfg.gateway_rpc_secret,
|
||||
))
|
||||
|
||||
let assert Ok(_) =
|
||||
wisp_mist.handler(
|
||||
handle_request(_, i18n_db, cfg, slots_cache),
|
||||
cfg.secret_key_base,
|
||||
)
|
||||
|> mist.new
|
||||
|> mist.bind("0.0.0.0")
|
||||
|> mist.port(cfg.port)
|
||||
|> mist.start
|
||||
|
||||
process.sleep_forever()
|
||||
}
|
||||
|
||||
fn handle_request(
|
||||
req: wisp.Request,
|
||||
i18n_db,
|
||||
cfg: config.Config,
|
||||
slots_cache: visionary_slots.Cache,
|
||||
) -> wisp.Response {
|
||||
let locale = get_request_locale(req)
|
||||
|
||||
let base_url = cfg.marketing_endpoint <> cfg.base_path
|
||||
|
||||
let country_code = geoip.country_code(req, cfg.geoip_host)
|
||||
|
||||
let user_agent = case request.get_header(req, "user-agent") {
|
||||
Ok(ua) -> ua
|
||||
Error(_) -> ""
|
||||
}
|
||||
|
||||
let platform = web.detect_platform(user_agent)
|
||||
let architecture = web.detect_architecture(user_agent, platform)
|
||||
|
||||
let ctx =
|
||||
web.Context(
|
||||
locale: locale,
|
||||
i18n_db: i18n_db,
|
||||
static_directory: "priv/static",
|
||||
base_url: base_url,
|
||||
country_code: country_code,
|
||||
api_endpoint: cfg.api_endpoint,
|
||||
app_endpoint: cfg.app_endpoint,
|
||||
cdn_endpoint: cfg.cdn_endpoint,
|
||||
asset_version: cfg.build_timestamp,
|
||||
base_path: cfg.base_path,
|
||||
platform: platform,
|
||||
architecture: architecture,
|
||||
geoip_host: cfg.geoip_host,
|
||||
release_channel: cfg.release_channel,
|
||||
visionary_slots: visionary_slots.current(slots_cache),
|
||||
metrics_endpoint: cfg.metrics_endpoint,
|
||||
)
|
||||
|
||||
use <- wisp.log_request(req)
|
||||
|
||||
let start = monotonic_milliseconds()
|
||||
|
||||
let response = case wisp.path_segments(req) {
|
||||
["static", ..] -> {
|
||||
use <- wisp.serve_static(
|
||||
req,
|
||||
under: "/static",
|
||||
from: ctx.static_directory,
|
||||
)
|
||||
router.handle_request(req, ctx)
|
||||
}
|
||||
_ -> router.handle_request(req, ctx)
|
||||
}
|
||||
|
||||
let duration = monotonic_milliseconds() - start
|
||||
|
||||
metrics.track_request(ctx, req, response.status, duration)
|
||||
|
||||
response |> cache_middleware.add_cache_headers
|
||||
}
|
||||
|
||||
fn monotonic_milliseconds() -> Int {
|
||||
do_monotonic_time(atom.create("millisecond"))
|
||||
}
|
||||
|
||||
@external(erlang, "erlang", "monotonic_time")
|
||||
fn do_monotonic_time(unit: Atom) -> Int
|
||||
|
||||
fn get_request_locale(req: wisp.Request) -> Locale {
|
||||
case wisp.get_cookie(req, "locale", wisp.PlainText) {
|
||||
Ok(locale_code) ->
|
||||
get_locale_from_code(locale_code) |> result.unwrap(locale.EnUS)
|
||||
Error(_) -> detect_browser_locale(req)
|
||||
}
|
||||
}
|
||||
|
||||
fn detect_browser_locale(req: wisp.Request) -> Locale {
|
||||
case request.get_header(req, "accept-language") {
|
||||
Ok(header) -> parse_accept_language(header)
|
||||
Error(_) -> locale.EnUS
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_accept_language(header: String) -> Locale {
|
||||
header
|
||||
|> string.split(",")
|
||||
|> list.map(string.trim)
|
||||
|> list.map(fn(lang) {
|
||||
case string.split(lang, ";") {
|
||||
[code, ..] -> string.trim(code) |> string.lowercase
|
||||
_ -> ""
|
||||
}
|
||||
})
|
||||
|> list.filter(fn(code) { code != "" })
|
||||
|> list.find_map(fn(code) {
|
||||
let clean_code = case string.split(code, "-") {
|
||||
[lang, region] -> lang <> "-" <> string.uppercase(region)
|
||||
[lang] -> lang
|
||||
_ -> code
|
||||
}
|
||||
get_locale_from_code(clean_code)
|
||||
})
|
||||
|> result.unwrap(locale.EnUS)
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import fluxer_marketing/i18n
|
||||
import fluxer_marketing/web.{type Context}
|
||||
import gleam/list
|
||||
import kielet.{gettext as g_}
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/html
|
||||
|
||||
pub fn render(ctx: Context) -> Element(a) {
|
||||
let i18n_ctx = i18n.get_context(ctx.i18n_db, ctx.locale)
|
||||
|
||||
let coming_features = [
|
||||
g_(i18n_ctx, "Federation"),
|
||||
g_(i18n_ctx, "Opt-in E2EE messaging"),
|
||||
|
||||
g_(i18n_ctx, "Slash commands"),
|
||||
g_(i18n_ctx, "Message components"),
|
||||
g_(i18n_ctx, "DM folders"),
|
||||
|
||||
g_(i18n_ctx, "Threads and forums"),
|
||||
g_(i18n_ctx, "Community templates"),
|
||||
g_(i18n_ctx, "Publish forums to the web"),
|
||||
g_(i18n_ctx, "RSS/Atom feeds for forums"),
|
||||
g_(i18n_ctx, "Polls & events"),
|
||||
g_(i18n_ctx, "Stage channels"),
|
||||
g_(i18n_ctx, "Event tickets"),
|
||||
g_(i18n_ctx, "Discovery"),
|
||||
|
||||
g_(i18n_ctx, "Public profile URLs"),
|
||||
g_(i18n_ctx, "Profile connections"),
|
||||
g_(i18n_ctx, "Activity sharing"),
|
||||
|
||||
g_(i18n_ctx, "Creator monetization"),
|
||||
g_(i18n_ctx, "Theme marketplace"),
|
||||
|
||||
g_(i18n_ctx, "Global voice regions"),
|
||||
g_(i18n_ctx, "Better noise cancellation"),
|
||||
g_(i18n_ctx, "E2EE calls"),
|
||||
g_(i18n_ctx, "Call pop-out"),
|
||||
g_(i18n_ctx, "Soundboard"),
|
||||
g_(i18n_ctx, "Streamer mode"),
|
||||
]
|
||||
|
||||
html.section(
|
||||
[
|
||||
attribute.class(
|
||||
"bg-gradient-to-b from-gray-50 to-white px-6 py-24 md:py-40",
|
||||
),
|
||||
],
|
||||
[
|
||||
html.div([attribute.class("mx-auto max-w-7xl")], [
|
||||
html.div([attribute.class("mb-20 md:mb-24 text-center")], [
|
||||
html.h2(
|
||||
[
|
||||
attribute.class(
|
||||
"display mb-8 md:mb-10 text-black text-4xl md:text-5xl lg:text-6xl",
|
||||
),
|
||||
],
|
||||
[
|
||||
html.text(g_(i18n_ctx, "What's coming next")),
|
||||
],
|
||||
),
|
||||
html.p(
|
||||
[
|
||||
attribute.class(
|
||||
"lead mx-auto max-w-3xl text-gray-700 text-xl md:text-2xl",
|
||||
),
|
||||
],
|
||||
[
|
||||
html.text(g_(
|
||||
i18n_ctx,
|
||||
"The future is being built right now. These features are coming soon.",
|
||||
)),
|
||||
],
|
||||
),
|
||||
]),
|
||||
html.div(
|
||||
[
|
||||
attribute.class("max-w-6xl mx-auto"),
|
||||
],
|
||||
[
|
||||
html.div(
|
||||
[
|
||||
attribute.id("coming-features-list"),
|
||||
attribute.class("coming-features-masonry"),
|
||||
],
|
||||
coming_features
|
||||
|> list.map(fn(feature) {
|
||||
html.span(
|
||||
[
|
||||
attribute.class(
|
||||
"feature-pill inline-block rounded-xl sm:rounded-2xl bg-white px-4 py-2.5 sm:px-6 sm:py-3.5 md:px-7 md:py-4.5 text-sm sm:text-base md:text-lg font-semibold text-gray-900 shadow-md border border-gray-200 whitespace-nowrap",
|
||||
),
|
||||
],
|
||||
[html.text(feature)],
|
||||
)
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import fluxer_marketing/components/community_type
|
||||
import fluxer_marketing/i18n
|
||||
import fluxer_marketing/web.{type Context}
|
||||
import kielet.{gettext as g_}
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/html
|
||||
|
||||
pub fn render(ctx: Context) -> Element(a) {
|
||||
let i18n_ctx = i18n.get_context(ctx.i18n_db, ctx.locale)
|
||||
|
||||
html.section(
|
||||
[
|
||||
attribute.class(
|
||||
"bg-gradient-to-b from-white to-gray-50 px-6 py-24 md:py-40",
|
||||
),
|
||||
],
|
||||
[
|
||||
html.div([attribute.class("mx-auto max-w-7xl text-center")], [
|
||||
html.h2(
|
||||
[
|
||||
attribute.class(
|
||||
"display mb-16 md:mb-20 text-black text-4xl md:text-5xl lg:text-6xl",
|
||||
),
|
||||
],
|
||||
[
|
||||
html.text(g_(i18n_ctx, "Built for communities of all kinds.")),
|
||||
],
|
||||
),
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"mb-16 md:mb-20 grid grid-cols-5 gap-2 sm:gap-4 md:gap-6 max-w-6xl mx-auto",
|
||||
),
|
||||
],
|
||||
[
|
||||
community_type.render(
|
||||
"game-controller",
|
||||
"blue",
|
||||
g_(i18n_ctx, "Gaming"),
|
||||
),
|
||||
community_type.render(
|
||||
"video-camera",
|
||||
"purple",
|
||||
g_(i18n_ctx, "Creators"),
|
||||
),
|
||||
community_type.render(
|
||||
"graduation-cap",
|
||||
"green",
|
||||
g_(i18n_ctx, "Education"),
|
||||
),
|
||||
community_type.render(
|
||||
"users-three",
|
||||
"orange",
|
||||
g_(i18n_ctx, "Hobbyists"),
|
||||
),
|
||||
community_type.render("code", "red", g_(i18n_ctx, "Developers")),
|
||||
],
|
||||
),
|
||||
html.p(
|
||||
[
|
||||
attribute.class(
|
||||
"lead lead-soft mx-auto max-w-4xl text-gray-700 leading-relaxed",
|
||||
),
|
||||
],
|
||||
[
|
||||
html.text(g_(
|
||||
i18n_ctx,
|
||||
"A chat platform that answers to you, not investors. It's ad-free, open source, community-funded, and never sells your data or nags you with upgrade pop-ups.",
|
||||
)),
|
||||
],
|
||||
),
|
||||
html.div(
|
||||
[
|
||||
attribute.class("my-6 mx-auto flex flex-col items-center gap-3"),
|
||||
attribute.attribute("aria-hidden", "true"),
|
||||
],
|
||||
[],
|
||||
),
|
||||
html.p(
|
||||
[
|
||||
attribute.class(
|
||||
"lead lead-soft mx-auto max-w-4xl text-gray-700 leading-relaxed",
|
||||
),
|
||||
],
|
||||
[
|
||||
html.text(g_(
|
||||
i18n_ctx,
|
||||
"Over time, we'd love to explore optional monetization tools that help creators and communities earn, with a small, transparent fee that keeps the app sustainable.",
|
||||
)),
|
||||
],
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import fluxer_marketing/icons
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/html
|
||||
|
||||
pub fn render(icon: String, color: String, label: String) -> Element(a) {
|
||||
let bg_class = case color {
|
||||
"blue" -> "bg-gradient-to-br from-blue-400 to-blue-600"
|
||||
"purple" -> "bg-gradient-to-br from-purple-400 to-purple-600"
|
||||
"green" -> "bg-gradient-to-br from-emerald-400 to-emerald-600"
|
||||
"orange" -> "bg-gradient-to-br from-orange-400 to-orange-600"
|
||||
"red" -> "bg-gradient-to-br from-rose-400 to-rose-600"
|
||||
_ -> "bg-gradient-to-br from-gray-400 to-gray-600"
|
||||
}
|
||||
|
||||
let icon_element = case icon {
|
||||
"game-controller" -> icons.game_controller
|
||||
"video-camera" -> icons.video_camera
|
||||
"graduation-cap" -> icons.graduation_cap
|
||||
"users-three" -> icons.users_three
|
||||
"code" -> icons.code_icon
|
||||
_ -> fn(_) { html.div([], []) }
|
||||
}
|
||||
|
||||
html.div([], [
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"lg:hidden w-full aspect-square flex items-center justify-center rounded-xl p-3 sm:p-4 "
|
||||
<> bg_class,
|
||||
),
|
||||
],
|
||||
[
|
||||
icon_element([
|
||||
attribute.class(
|
||||
"w-3/5 h-3/5 sm:w-1/2 sm:h-1/2 text-white flex-shrink-0",
|
||||
),
|
||||
]),
|
||||
],
|
||||
),
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"hidden lg:flex w-full aspect-square flex-col items-center justify-center rounded-2xl p-6 xl:p-8 "
|
||||
<> bg_class,
|
||||
),
|
||||
],
|
||||
[
|
||||
icon_element([
|
||||
attribute.class("w-2/5 h-2/5 text-white flex-shrink-0"),
|
||||
]),
|
||||
html.p(
|
||||
[
|
||||
attribute.class(
|
||||
"subtitle text-white font-bold text-center mt-4 xl:mt-5 whitespace-nowrap overflow-hidden text-ellipsis",
|
||||
),
|
||||
],
|
||||
[html.text(label)],
|
||||
),
|
||||
],
|
||||
),
|
||||
])
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import fluxer_marketing/components/feature_card
|
||||
import fluxer_marketing/i18n
|
||||
import fluxer_marketing/web.{type Context}
|
||||
import kielet.{gettext as g_}
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/html
|
||||
|
||||
pub fn render(ctx: Context) -> Element(a) {
|
||||
let i18n_ctx = i18n.get_context(ctx.i18n_db, ctx.locale)
|
||||
|
||||
html.section(
|
||||
[
|
||||
attribute.class(
|
||||
"bg-gradient-to-b from-[#4641D9] to-[#3832B8] px-6 py-20 md:py-32",
|
||||
),
|
||||
],
|
||||
[
|
||||
html.div([attribute.class("mx-auto max-w-7xl")], [
|
||||
html.div([attribute.class("mb-16 md:mb-20 text-center")], [
|
||||
html.h2(
|
||||
[
|
||||
attribute.class(
|
||||
"display mb-6 md:mb-8 text-white text-4xl md:text-5xl lg:text-6xl",
|
||||
),
|
||||
],
|
||||
[
|
||||
html.text(g_(i18n_ctx, "What's available today")),
|
||||
],
|
||||
),
|
||||
html.p(
|
||||
[
|
||||
attribute.class("lead mx-auto max-w-3xl text-white/90"),
|
||||
],
|
||||
[
|
||||
html.text(g_(
|
||||
i18n_ctx,
|
||||
"All the basics you expect, plus a few things you don't.",
|
||||
)),
|
||||
],
|
||||
),
|
||||
]),
|
||||
html.div(
|
||||
[attribute.class("grid gap-8 md:gap-10 grid-cols-1 lg:grid-cols-2")],
|
||||
[
|
||||
feature_card.render(
|
||||
ctx,
|
||||
"chats",
|
||||
g_(i18n_ctx, "Messaging"),
|
||||
g_(
|
||||
i18n_ctx,
|
||||
"DM your friends, chat with groups, or build communities with channels.",
|
||||
),
|
||||
[
|
||||
g_(i18n_ctx, "Full Markdown support in messages"),
|
||||
g_(i18n_ctx, "Private DMs and group chats"),
|
||||
g_(i18n_ctx, "Organized channels for communities"),
|
||||
g_(i18n_ctx, "Share files and preview links"),
|
||||
],
|
||||
"dark",
|
||||
),
|
||||
feature_card.render(
|
||||
ctx,
|
||||
"microphone",
|
||||
g_(i18n_ctx, "Voice & video"),
|
||||
g_(
|
||||
i18n_ctx,
|
||||
"Hop in a call with friends or share your screen to work together.",
|
||||
),
|
||||
[
|
||||
g_(i18n_ctx, "Join from multiple devices at once"),
|
||||
g_(i18n_ctx, "Screen sharing built in"),
|
||||
g_(i18n_ctx, "Noise suppression and echo cancellation"),
|
||||
g_(i18n_ctx, "Mute, deafen, and camera controls"),
|
||||
],
|
||||
"dark",
|
||||
),
|
||||
feature_card.render(
|
||||
ctx,
|
||||
"gear",
|
||||
g_(i18n_ctx, "Moderation tools"),
|
||||
g_(
|
||||
i18n_ctx,
|
||||
"Keep your community running smoothly with roles, permissions, and logs.",
|
||||
),
|
||||
[
|
||||
g_(i18n_ctx, "Granular roles and permissions"),
|
||||
g_(i18n_ctx, "Moderation actions and tools"),
|
||||
g_(i18n_ctx, "Audit logs for transparency"),
|
||||
g_(i18n_ctx, "Webhooks and bot support"),
|
||||
],
|
||||
"dark",
|
||||
),
|
||||
feature_card.render(
|
||||
ctx,
|
||||
"magnifying-glass",
|
||||
g_(i18n_ctx, "Search & quick switcher"),
|
||||
g_(
|
||||
i18n_ctx,
|
||||
"Find old messages or jump between communities and channels in seconds.",
|
||||
),
|
||||
[
|
||||
g_(i18n_ctx, "Search through message history"),
|
||||
g_(i18n_ctx, "Filter by users, dates, and more"),
|
||||
g_(i18n_ctx, "Quick switcher with keyboard shortcuts"),
|
||||
g_(i18n_ctx, "Manage friends and block users"),
|
||||
],
|
||||
"dark",
|
||||
),
|
||||
feature_card.render(
|
||||
ctx,
|
||||
"palette",
|
||||
g_(i18n_ctx, "Customization"),
|
||||
g_(
|
||||
i18n_ctx,
|
||||
"Add custom emojis, save media for later, and style the app with custom CSS.",
|
||||
),
|
||||
[
|
||||
g_(i18n_ctx, "Upload custom emojis and stickers"),
|
||||
g_(i18n_ctx, "Save images, videos, GIFs, and audio"),
|
||||
g_(i18n_ctx, "Custom CSS themes"),
|
||||
g_(i18n_ctx, "Compact mode and display options"),
|
||||
],
|
||||
"dark",
|
||||
),
|
||||
feature_card.render(
|
||||
ctx,
|
||||
"server",
|
||||
g_(i18n_ctx, "Self-hosting"),
|
||||
g_(
|
||||
i18n_ctx,
|
||||
"Run the Fluxer backend on your own hardware and connect with our apps.",
|
||||
),
|
||||
[
|
||||
g_(i18n_ctx, "Fully open source (AGPLv3)"),
|
||||
g_(i18n_ctx, "Host your own instance"),
|
||||
g_(i18n_ctx, "Use our desktop client (mobile coming soon)"),
|
||||
g_(i18n_ctx, "Switch between multiple instances"),
|
||||
],
|
||||
"dark",
|
||||
),
|
||||
],
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import fluxer_marketing/icons
|
||||
import fluxer_marketing/web.{type Context}
|
||||
import gleam/list
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/html
|
||||
|
||||
pub fn render(
|
||||
_ctx: Context,
|
||||
icon: String,
|
||||
title: String,
|
||||
description: String,
|
||||
features: List(String),
|
||||
theme: String,
|
||||
) -> Element(a) {
|
||||
let card_bg = case theme {
|
||||
"light" -> "bg-white"
|
||||
"dark" -> "bg-white/95 backdrop-blur-sm"
|
||||
_ -> "bg-white/95 backdrop-blur-sm"
|
||||
}
|
||||
|
||||
let text_color = case theme {
|
||||
"light" -> "text-gray-900"
|
||||
"dark" -> "text-gray-900"
|
||||
_ -> "text-gray-900"
|
||||
}
|
||||
|
||||
let description_color = case theme {
|
||||
"light" -> "text-gray-600"
|
||||
"dark" -> "text-gray-700"
|
||||
_ -> "text-gray-700"
|
||||
}
|
||||
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"flex h-full flex-col rounded-2xl "
|
||||
<> card_bg
|
||||
<> " p-8 md:p-10 shadow-md border border-gray-100",
|
||||
),
|
||||
],
|
||||
[
|
||||
html.div([attribute.class("mb-6")], [
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"inline-flex items-center justify-center w-16 h-16 md:w-20 md:h-20 rounded-2xl bg-gradient-to-br from-[#4641D9]/10 to-[#4641D9]/5 mb-5",
|
||||
),
|
||||
],
|
||||
[
|
||||
case icon {
|
||||
"chats" ->
|
||||
icons.chats([
|
||||
attribute.class("h-8 w-8 md:h-10 md:w-10 text-[#4641D9]"),
|
||||
])
|
||||
"microphone" ->
|
||||
icons.microphone([
|
||||
attribute.class("h-8 w-8 md:h-10 md:w-10 text-[#4641D9]"),
|
||||
])
|
||||
"palette" ->
|
||||
icons.palette([
|
||||
attribute.class("h-8 w-8 md:h-10 md:w-10 text-[#4641D9]"),
|
||||
])
|
||||
"magnifying-glass" ->
|
||||
icons.magnifying_glass([
|
||||
attribute.class("h-8 w-8 md:h-10 md:w-10 text-[#4641D9]"),
|
||||
])
|
||||
"devices" ->
|
||||
icons.devices([
|
||||
attribute.class("h-8 w-8 md:h-10 md:w-10 text-[#4641D9]"),
|
||||
])
|
||||
"gear" ->
|
||||
icons.gear([
|
||||
attribute.class("h-8 w-8 md:h-10 md:w-10 text-[#4641D9]"),
|
||||
])
|
||||
"heart" ->
|
||||
icons.heart([
|
||||
attribute.class("h-8 w-8 md:h-10 md:w-10 text-[#4641D9]"),
|
||||
])
|
||||
"lightning" ->
|
||||
icons.lightning([
|
||||
attribute.class("h-8 w-8 md:h-10 md:w-10 text-[#4641D9]"),
|
||||
])
|
||||
"globe" ->
|
||||
icons.globe([
|
||||
attribute.class("h-8 w-8 md:h-10 md:w-10 text-[#4641D9]"),
|
||||
])
|
||||
"server" ->
|
||||
icons.globe([
|
||||
attribute.class("h-8 w-8 md:h-10 md:w-10 text-[#4641D9]"),
|
||||
])
|
||||
"shopping-cart" ->
|
||||
icons.shopping_cart([
|
||||
attribute.class("h-8 w-8 md:h-10 md:w-10 text-[#4641D9]"),
|
||||
])
|
||||
"newspaper" ->
|
||||
icons.newspaper([
|
||||
attribute.class("h-8 w-8 md:h-10 md:w-10 text-[#4641D9]"),
|
||||
])
|
||||
"brain" ->
|
||||
icons.brain([
|
||||
attribute.class("h-8 w-8 md:h-10 md:w-10 text-[#4641D9]"),
|
||||
])
|
||||
_ -> html.div([], [])
|
||||
},
|
||||
],
|
||||
),
|
||||
html.h3([attribute.class("title " <> text_color <> " mb-3")], [
|
||||
html.text(title),
|
||||
]),
|
||||
html.p([attribute.class("body-lg " <> description_color)], [
|
||||
html.text(description),
|
||||
]),
|
||||
]),
|
||||
html.div([attribute.class("flex-1 mt-2")], [
|
||||
html.ul(
|
||||
[attribute.class("space-y-3")],
|
||||
features
|
||||
|> list.map(fn(feature) {
|
||||
html.li([attribute.class("flex items-start gap-3")], [
|
||||
html.span(
|
||||
[
|
||||
attribute.class(
|
||||
"mt-[.7em] h-1.5 w-1.5 rounded-full bg-[#4641D9] shrink-0",
|
||||
),
|
||||
],
|
||||
[],
|
||||
),
|
||||
html.span([attribute.class("body-lg " <> text_color)], [
|
||||
html.text(feature),
|
||||
]),
|
||||
])
|
||||
}),
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/html
|
||||
|
||||
pub type Status {
|
||||
Live
|
||||
ComingSoon
|
||||
}
|
||||
|
||||
pub type Theme {
|
||||
Light
|
||||
Dark
|
||||
}
|
||||
|
||||
pub fn render(text: String, _status: Status) -> Element(a) {
|
||||
html.span(
|
||||
[
|
||||
attribute.class(
|
||||
"inline-block rounded-lg bg-white px-3 py-2 sm:px-4 sm:py-3 text-sm sm:text-base font-medium text-[#4641D9] shadow-sm border border-white/20",
|
||||
),
|
||||
],
|
||||
[html.text(text)],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn render_with_theme(
|
||||
text: String,
|
||||
_status: Status,
|
||||
theme: Theme,
|
||||
) -> Element(a) {
|
||||
let pill_class = case theme {
|
||||
Light ->
|
||||
"inline-block rounded-lg bg-[#4641D9] px-3 py-2 sm:px-4 sm:py-3 text-sm sm:text-base font-medium text-white shadow-sm border border-gray-200"
|
||||
Dark ->
|
||||
"inline-block rounded-lg bg-white px-3 py-2 sm:px-4 sm:py-3 text-sm sm:text-base font-medium text-[#4641D9] shadow-sm border border-white/20"
|
||||
}
|
||||
|
||||
html.span([attribute.class(pill_class)], [html.text(text)])
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import fluxer_marketing/components/feature_pill
|
||||
import fluxer_marketing/i18n
|
||||
import fluxer_marketing/web.{type Context}
|
||||
import gleam/list
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/html
|
||||
|
||||
pub fn render(
|
||||
ctx: Context,
|
||||
title: String,
|
||||
description: String,
|
||||
features: List(#(String, feature_pill.Status)),
|
||||
theme: feature_pill.Theme,
|
||||
) -> Element(a) {
|
||||
let _i18n_ctx = i18n.get_context(ctx.i18n_db, ctx.locale)
|
||||
|
||||
let #(bg_class, text_class, desc_class) = case theme {
|
||||
feature_pill.Light -> #("bg-white", "text-black", "text-gray-600")
|
||||
feature_pill.Dark -> #("bg-[#4641D9]", "text-white", "text-white/80")
|
||||
}
|
||||
|
||||
html.section([attribute.class(bg_class <> " px-6 py-20 md:py-32")], [
|
||||
html.div([attribute.class("mx-auto max-w-7xl")], [
|
||||
html.div([attribute.class("mb-16 text-center")], [
|
||||
html.h2(
|
||||
[
|
||||
attribute.class(
|
||||
"mb-6 text-3xl font-bold " <> text_class <> " md:text-4xl",
|
||||
),
|
||||
],
|
||||
[html.text(title)],
|
||||
),
|
||||
html.p(
|
||||
[
|
||||
attribute.class(
|
||||
"mx-auto max-w-3xl text-lg " <> desc_class <> " md:text-xl",
|
||||
),
|
||||
],
|
||||
[html.text(description)],
|
||||
),
|
||||
]),
|
||||
html.div(
|
||||
[attribute.class("flex flex-wrap justify-center gap-3")],
|
||||
features
|
||||
|> list.map(fn(feature) {
|
||||
let #(text, status) = feature
|
||||
feature_pill.render_with_theme(text, status, theme)
|
||||
}),
|
||||
),
|
||||
]),
|
||||
])
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import fluxer_marketing/i18n
|
||||
import fluxer_marketing/web.{type Context}
|
||||
import kielet.{gettext as g_}
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/html
|
||||
|
||||
pub fn render(ctx: Context) -> Element(a) {
|
||||
let i18n_ctx = i18n.get_context(ctx.i18n_db, ctx.locale)
|
||||
|
||||
html.section(
|
||||
[
|
||||
attribute.class("bg-gradient-to-b from-white to-gray-50"),
|
||||
],
|
||||
[
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"rounded-t-3xl bg-gradient-to-b from-[#4641D9] to-[#3832B8] text-white",
|
||||
),
|
||||
],
|
||||
[
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"px-4 py-24 md:px-8 md:py-32 lg:py-40 text-center",
|
||||
),
|
||||
],
|
||||
[
|
||||
html.h2(
|
||||
[
|
||||
attribute.class(
|
||||
"display mb-8 md:mb-10 text-white text-5xl md:text-7xl lg:text-8xl font-bold",
|
||||
),
|
||||
],
|
||||
[
|
||||
html.text(g_(
|
||||
i18n_ctx,
|
||||
"We need your support to make this work.",
|
||||
)),
|
||||
],
|
||||
),
|
||||
html.p(
|
||||
[
|
||||
attribute.class(
|
||||
"lead mb-12 md:mb-14 text-white/90 text-xl md:text-2xl",
|
||||
),
|
||||
],
|
||||
[
|
||||
html.text(g_(
|
||||
i18n_ctx,
|
||||
"Create an account and help us build something good.",
|
||||
)),
|
||||
],
|
||||
),
|
||||
html.a(
|
||||
[
|
||||
attribute.href(ctx.app_endpoint <> "/register"),
|
||||
attribute.class(
|
||||
"label inline-block rounded-xl bg-white px-10 py-5 md:px-12 md:py-6 text-lg md:text-xl text-[#4641D9] transition-colors hover:bg-opacity-90 shadow-lg",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Join Fluxer"))],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
}
|
||||
345
fluxer_marketing/src/fluxer_marketing/components/footer.gleam
Normal file
345
fluxer_marketing/src/fluxer_marketing/components/footer.gleam
Normal file
@@ -0,0 +1,345 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import fluxer_marketing/help_center
|
||||
import fluxer_marketing/i18n
|
||||
import fluxer_marketing/icons
|
||||
import fluxer_marketing/web.{type Context, href}
|
||||
import kielet.{gettext as g_}
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/html
|
||||
|
||||
pub fn render(ctx: Context) -> Element(a) {
|
||||
let i18n_ctx = i18n.get_context(ctx.i18n_db, ctx.locale)
|
||||
let help_data = help_center.load_help_articles(ctx.locale)
|
||||
let bug_article_href =
|
||||
help_center.article_href(ctx.locale, help_data, "1447264362996695040")
|
||||
|
||||
html.footer(
|
||||
[
|
||||
attribute.class(
|
||||
"bg-gradient-to-b from-[#4641D9] to-[#3832B8] px-4 py-20 md:py-24 text-white md:px-8",
|
||||
),
|
||||
],
|
||||
[
|
||||
html.div([attribute.class("mx-auto max-w-7xl")], [
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"mb-12 md:mb-16 rounded-2xl bg-white/10 backdrop-blur-sm border border-white/20 px-6 py-6 md:px-10 md:py-10",
|
||||
),
|
||||
],
|
||||
[
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"flex flex-col md:flex-row items-center md:items-center justify-between gap-6 md:gap-8 text-center md:text-left",
|
||||
),
|
||||
],
|
||||
[
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"flex flex-col md:flex-row items-center md:items-center gap-3 md:gap-4",
|
||||
),
|
||||
],
|
||||
[
|
||||
icons.heart([attribute.class("h-7 w-7 text-white")]),
|
||||
html.p([attribute.class("body-lg text-white/90")], [
|
||||
html.text(g_(
|
||||
i18n_ctx,
|
||||
"Help support an independent communication platform. Your donation funds the platform's infrastructure and development.",
|
||||
)),
|
||||
]),
|
||||
],
|
||||
),
|
||||
html.a(
|
||||
[
|
||||
href(ctx, "/donate"),
|
||||
attribute.class(
|
||||
"inline-flex items-center justify-center rounded-xl bg-white px-8 py-4 text-base md:text-lg font-semibold text-[#4641D9] transition-colors hover:bg-white/90 shadow-lg whitespace-nowrap",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Donate to Fluxer"))],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
html.div(
|
||||
[attribute.class("grid grid-cols-1 gap-12 md:gap-16 md:grid-cols-4")],
|
||||
[
|
||||
html.div([attribute.class("md:col-span-1")], [
|
||||
icons.fluxer_logo_wordmark([attribute.class("h-10 md:h-12")]),
|
||||
]),
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"grid grid-cols-1 gap-8 md:gap-12 sm:grid-cols-3 md:col-span-3",
|
||||
),
|
||||
],
|
||||
[
|
||||
html.div([], [
|
||||
html.h3([attribute.class("title mb-4 md:mb-6 text-white")], [
|
||||
html.text(g_(i18n_ctx, "Fluxer")),
|
||||
]),
|
||||
html.ul([attribute.class("space-y-3")], [
|
||||
html.li([], [
|
||||
html.a(
|
||||
[
|
||||
href(ctx, "/plutonium"),
|
||||
attribute.class(
|
||||
"body-lg text-white/90 hover:text-white hover:underline transition-colors",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Pricing"))],
|
||||
),
|
||||
]),
|
||||
html.li([], [
|
||||
html.a(
|
||||
[
|
||||
href(ctx, "/partners"),
|
||||
attribute.class(
|
||||
"body-lg text-white/90 hover:text-white hover:underline transition-colors",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Partners"))],
|
||||
),
|
||||
]),
|
||||
html.li([], [
|
||||
html.a(
|
||||
[
|
||||
href(ctx, "/download"),
|
||||
attribute.class(
|
||||
"body-lg text-white/90 hover:text-white hover:underline transition-colors",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Download"))],
|
||||
),
|
||||
]),
|
||||
html.li([], [
|
||||
html.a(
|
||||
[
|
||||
attribute.href("https://github.com/fluxerapp/fluxer"),
|
||||
attribute.class(
|
||||
"body-lg text-white/90 hover:text-white hover:underline transition-colors",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Source Code"))],
|
||||
),
|
||||
]),
|
||||
html.li([], [
|
||||
html.div([attribute.class("flex items-center gap-2")], [
|
||||
html.a(
|
||||
[
|
||||
attribute.href(
|
||||
"https://bsky.app/profile/fluxer.app",
|
||||
),
|
||||
attribute.class(
|
||||
"body-lg text-white/90 hover:text-white hover:underline transition-colors",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Bluesky"))],
|
||||
),
|
||||
html.a(
|
||||
[
|
||||
attribute.href(
|
||||
"https://bsky.app/profile/fluxer.app/rss",
|
||||
),
|
||||
attribute.title("RSS Feed"),
|
||||
attribute.target("_blank"),
|
||||
attribute.rel("noopener noreferrer"),
|
||||
attribute.class(
|
||||
"text-white/90 hover:text-white transition-colors",
|
||||
),
|
||||
],
|
||||
[
|
||||
icons.rss([
|
||||
attribute.class("h-[1em] w-[1em]"),
|
||||
]),
|
||||
],
|
||||
),
|
||||
]),
|
||||
]),
|
||||
html.li([], [
|
||||
html.a(
|
||||
[
|
||||
href(ctx, "/help"),
|
||||
attribute.class(
|
||||
"body-lg text-white/90 hover:text-white hover:underline transition-colors",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Help Center"))],
|
||||
),
|
||||
]),
|
||||
html.li([], [
|
||||
html.a(
|
||||
[
|
||||
href(ctx, "/press"),
|
||||
attribute.class(
|
||||
"body-lg text-white/90 hover:text-white hover:underline transition-colors",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Press"))],
|
||||
),
|
||||
]),
|
||||
html.li([], [
|
||||
html.a(
|
||||
[
|
||||
attribute.href("https://docs.fluxer.app"),
|
||||
attribute.class(
|
||||
"body-lg text-white/90 hover:text-white hover:underline transition-colors",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Developer Docs"))],
|
||||
),
|
||||
]),
|
||||
html.li([], [
|
||||
html.a(
|
||||
[
|
||||
href(ctx, "/careers"),
|
||||
attribute.class(
|
||||
"body-lg text-white/90 hover:text-white hover:underline transition-colors",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Careers"))],
|
||||
),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
html.div([], [
|
||||
html.h3([attribute.class("title mb-4 md:mb-6 text-white")], [
|
||||
html.text(g_(i18n_ctx, "Policies")),
|
||||
]),
|
||||
html.ul([attribute.class("space-y-3")], [
|
||||
html.li([], [
|
||||
html.a(
|
||||
[
|
||||
href(ctx, "/terms"),
|
||||
attribute.class(
|
||||
"body-lg text-white/90 hover:text-white hover:underline transition-colors",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Terms of Service"))],
|
||||
),
|
||||
]),
|
||||
html.li([], [
|
||||
html.a(
|
||||
[
|
||||
href(ctx, "/privacy"),
|
||||
attribute.class(
|
||||
"body-lg text-white/90 hover:text-white hover:underline transition-colors",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Privacy Policy"))],
|
||||
),
|
||||
]),
|
||||
html.li([], [
|
||||
html.a(
|
||||
[
|
||||
href(ctx, "/guidelines"),
|
||||
attribute.class(
|
||||
"body-lg text-white/90 hover:text-white hover:underline transition-colors",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Community Guidelines"))],
|
||||
),
|
||||
]),
|
||||
html.li([], [
|
||||
html.a(
|
||||
[
|
||||
href(ctx, "/company-information"),
|
||||
attribute.class(
|
||||
"body-lg text-white/90 hover:text-white hover:underline transition-colors",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Company Information"))],
|
||||
),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
html.div([], [
|
||||
html.h3([attribute.class("title mb-4 md:mb-6 text-white")], [
|
||||
html.text(g_(i18n_ctx, "Connect")),
|
||||
]),
|
||||
html.ul([attribute.class("space-y-3")], [
|
||||
html.li([], [
|
||||
html.a(
|
||||
[
|
||||
attribute.href("mailto:press@fluxer.app"),
|
||||
attribute.class(
|
||||
"body-lg text-white/90 hover:text-white hover:underline transition-colors",
|
||||
),
|
||||
],
|
||||
[html.text("press@fluxer.app")],
|
||||
),
|
||||
]),
|
||||
html.li([], [
|
||||
html.a(
|
||||
[
|
||||
attribute.href("mailto:support@fluxer.app"),
|
||||
attribute.class(
|
||||
"body-lg text-white/90 hover:text-white hover:underline transition-colors",
|
||||
),
|
||||
],
|
||||
[html.text("support@fluxer.app")],
|
||||
),
|
||||
]),
|
||||
html.li([], [
|
||||
html.a(
|
||||
[
|
||||
href(ctx, bug_article_href),
|
||||
attribute.class(
|
||||
"body-lg text-white/90 hover:text-white hover:underline transition-colors",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Report a bug"))],
|
||||
),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
html.div([attribute.class("mt-12 border-t border-white/20 pt-8")], [
|
||||
html.div([attribute.class("flex flex-col gap-2")], [
|
||||
html.p([attribute.class("body-sm text-white/80")], [
|
||||
html.text(g_(
|
||||
i18n_ctx,
|
||||
"© Fluxer Platform AB (Swedish limited liability company: 559537-3993)",
|
||||
)),
|
||||
]),
|
||||
html.p([attribute.class("body-sm text-white/80")], [
|
||||
html.text(g_(i18n_ctx, "IP geolocation data by ")),
|
||||
html.a(
|
||||
[
|
||||
attribute.href("https://ipinfo.io"),
|
||||
attribute.target("_blank"),
|
||||
attribute.rel("noopener noreferrer"),
|
||||
attribute.class("hover:underline"),
|
||||
],
|
||||
[html.text("IPinfo")],
|
||||
),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import fluxer_marketing/components/support_card
|
||||
import fluxer_marketing/help_center
|
||||
import fluxer_marketing/i18n
|
||||
import fluxer_marketing/icons
|
||||
import fluxer_marketing/web.{type Context}
|
||||
import kielet.{gettext as g_}
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/html
|
||||
|
||||
pub fn render(ctx: Context) -> Element(a) {
|
||||
let i18n_ctx = i18n.get_context(ctx.i18n_db, ctx.locale)
|
||||
let help_data = help_center.load_help_articles(ctx.locale)
|
||||
let bug_article_href =
|
||||
help_center.article_href(ctx.locale, help_data, "1447264362996695040")
|
||||
|
||||
html.section(
|
||||
[
|
||||
attribute.class(
|
||||
"bg-gradient-to-b from-[#4641D9] to-[#3832B8] px-6 py-24 md:py-40",
|
||||
),
|
||||
attribute.id("get-involved"),
|
||||
attribute.style("scroll-margin-top", "8rem"),
|
||||
],
|
||||
[
|
||||
html.div([attribute.class("mx-auto max-w-7xl")], [
|
||||
html.div([attribute.class("mb-20 md:mb-24 text-center")], [
|
||||
html.h2(
|
||||
[
|
||||
attribute.class(
|
||||
"display mb-8 md:mb-10 text-white text-4xl md:text-5xl lg:text-6xl",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Get involved"))],
|
||||
),
|
||||
html.p(
|
||||
[
|
||||
attribute.class(
|
||||
"lead mx-auto max-w-3xl text-white/90 text-xl md:text-2xl",
|
||||
),
|
||||
],
|
||||
[
|
||||
html.text(g_(
|
||||
i18n_ctx,
|
||||
"We're building a complete platform with all the features you'd expect. But we can't do it without your help!",
|
||||
)),
|
||||
],
|
||||
),
|
||||
]),
|
||||
html.div(
|
||||
[
|
||||
attribute.class("grid gap-10 md:gap-12 grid-cols-1 md:grid-cols-2"),
|
||||
],
|
||||
[
|
||||
support_card.render(
|
||||
ctx,
|
||||
"rocket-launch",
|
||||
g_(i18n_ctx, "Join and spread the word"),
|
||||
g_(
|
||||
i18n_ctx,
|
||||
"We're limiting registrations during beta. Once you're in, you can give friends a code to skip the queue.",
|
||||
),
|
||||
g_(i18n_ctx, "Register now"),
|
||||
ctx.app_endpoint <> "/register",
|
||||
),
|
||||
support_card.render(
|
||||
ctx,
|
||||
"chat-centered-text",
|
||||
g_(i18n_ctx, "Join Fluxer HQ"),
|
||||
g_(
|
||||
i18n_ctx,
|
||||
"Get updates, see upcoming features, discuss suggestions, and chat with the team.",
|
||||
),
|
||||
g_(i18n_ctx, "Join Fluxer HQ"),
|
||||
"https://fluxer.gg/fluxer-hq",
|
||||
),
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"flex h-full flex-col rounded-3xl bg-white/95 backdrop-blur-sm p-10 md:p-12 shadow-lg border border-white/50",
|
||||
),
|
||||
],
|
||||
[
|
||||
html.div([attribute.class("mb-8 text-center")], [
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"inline-flex items-center justify-center w-24 h-24 md:w-28 md:h-28 rounded-3xl bg-[#4641D9] mb-6",
|
||||
),
|
||||
],
|
||||
[
|
||||
icons.bluesky([
|
||||
attribute.class("h-12 w-12 md:h-14 md:w-14 text-white"),
|
||||
]),
|
||||
],
|
||||
),
|
||||
html.h3(
|
||||
[
|
||||
attribute.class(
|
||||
"title text-gray-900 mb-4 text-xl md:text-2xl",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Follow us on Bluesky"))],
|
||||
),
|
||||
html.p(
|
||||
[attribute.class("body-lg text-gray-700 leading-relaxed")],
|
||||
[
|
||||
html.text(g_(
|
||||
i18n_ctx,
|
||||
"Stay updated on news, service status, and what's happening. You can also follow ",
|
||||
)),
|
||||
html.a(
|
||||
[
|
||||
attribute.href(
|
||||
"https://bsky.app/profile/fluxer.app/rss",
|
||||
),
|
||||
attribute.class(
|
||||
"underline hover:text-gray-900 transition-colors",
|
||||
),
|
||||
attribute.target("_blank"),
|
||||
attribute.rel("noopener noreferrer"),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "our RSS feed"))],
|
||||
),
|
||||
html.text("."),
|
||||
],
|
||||
),
|
||||
]),
|
||||
html.div(
|
||||
[attribute.class("mt-auto flex flex-col items-center")],
|
||||
[
|
||||
html.a(
|
||||
[
|
||||
attribute.href("https://bsky.app/profile/fluxer.app"),
|
||||
attribute.class(
|
||||
"label inline-block rounded-xl bg-[#4641D9] px-8 py-4 text-base md:text-lg text-white transition-colors hover:bg-opacity-90 shadow-md w-full text-center",
|
||||
),
|
||||
attribute.target("_blank"),
|
||||
attribute.rel("noopener noreferrer"),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Follow @fluxer.app"))],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
support_card.render(
|
||||
ctx,
|
||||
"bug",
|
||||
g_(i18n_ctx, "Report bugs"),
|
||||
g_(
|
||||
i18n_ctx,
|
||||
"Approved reports grant access to Fluxer Testers, where you can earn points for Plutonium codes and the Bug Hunter badge.",
|
||||
),
|
||||
g_(i18n_ctx, "Read the Guide"),
|
||||
bug_article_href,
|
||||
),
|
||||
support_card.render(
|
||||
ctx,
|
||||
"code",
|
||||
g_(i18n_ctx, "Contribute code"),
|
||||
g_(
|
||||
i18n_ctx,
|
||||
"Fluxer is open source (AGPLv3). Contribute directly on GitHub by opening pull requests.",
|
||||
),
|
||||
g_(i18n_ctx, "View repository"),
|
||||
"https://github.com/fluxerapp/fluxer",
|
||||
),
|
||||
support_card.render(
|
||||
ctx,
|
||||
"shield-check",
|
||||
g_(i18n_ctx, "Found a security issue?"),
|
||||
g_(
|
||||
i18n_ctx,
|
||||
"We appreciate responsible disclosure to security@fluxer.app. We offer Plutonium codes and Bug Hunter badges based on severity.",
|
||||
),
|
||||
"security@fluxer.app",
|
||||
"mailto:security@fluxer.app",
|
||||
),
|
||||
],
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/html
|
||||
|
||||
pub fn render() -> Element(a) {
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"h-px bg-gradient-to-r from-transparent via-gray-300 to-transparent",
|
||||
),
|
||||
],
|
||||
[],
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import fluxer_marketing/i18n
|
||||
import fluxer_marketing/web.{type Context}
|
||||
import kielet.{gettext as g_}
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/html
|
||||
|
||||
fn hn_logo() -> Element(a) {
|
||||
element.element(
|
||||
"svg",
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "4 4 188 188"),
|
||||
attribute.attribute("width", "20"),
|
||||
attribute.attribute("height", "20"),
|
||||
attribute.class("block flex-shrink-0 rounded-[3px]"),
|
||||
],
|
||||
[
|
||||
element.element(
|
||||
"path",
|
||||
[
|
||||
attribute.attribute("d", "m4 4h188v188h-188z"),
|
||||
attribute.attribute("fill", "#f60"),
|
||||
],
|
||||
[],
|
||||
),
|
||||
element.element(
|
||||
"path",
|
||||
[
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"m73.2521756 45.01 22.7478244 47.39130083 22.7478244-47.39130083h19.56569631l-34.32352071 64.48661468v41.49338532h-15.98v-41.49338532l-34.32352071-64.48661468z",
|
||||
),
|
||||
attribute.attribute("fill", "#fff"),
|
||||
],
|
||||
[],
|
||||
),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn render(ctx: Context) -> Element(a) {
|
||||
let i18n_ctx = i18n.get_context(ctx.i18n_db, ctx.locale)
|
||||
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"mt-8 inline-flex items-center gap-3 rounded-full bg-white/10 backdrop-blur-sm border border-white/20 px-4 py-2.5 md:px-5",
|
||||
),
|
||||
],
|
||||
[
|
||||
hn_logo(),
|
||||
html.p([attribute.class("text-sm md:text-base text-white/90")], [
|
||||
html.span([attribute.class("font-medium")], [
|
||||
html.text(g_(i18n_ctx, "From HN?")),
|
||||
html.text(" "),
|
||||
]),
|
||||
html.text(g_(i18n_ctx, "Try it without an email at")),
|
||||
html.text(" "),
|
||||
html.a(
|
||||
[
|
||||
attribute.href("https://fluxer.gg/fluxer-hq"),
|
||||
attribute.class(
|
||||
"font-semibold text-white underline underline-offset-2 hover:no-underline",
|
||||
),
|
||||
],
|
||||
[html.text("fluxer.gg/fluxer-hq")],
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
243
fluxer_marketing/src/fluxer_marketing/components/hero.gleam
Normal file
243
fluxer_marketing/src/fluxer_marketing/components/hero.gleam
Normal file
@@ -0,0 +1,243 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import fluxer_marketing/components/hackernews_banner
|
||||
import fluxer_marketing/components/platform_download_button
|
||||
import fluxer_marketing/i18n
|
||||
import fluxer_marketing/locale
|
||||
import fluxer_marketing/web.{type Context}
|
||||
import kielet.{gettext as g_}
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/html
|
||||
|
||||
pub fn render(ctx: Context) -> Element(a) {
|
||||
let i18n_ctx = i18n.get_context(ctx.i18n_db, ctx.locale)
|
||||
|
||||
html.main(
|
||||
[
|
||||
attribute.class(
|
||||
"flex flex-col items-center justify-center px-6 pb-16 pt-52 md:pb-20 md:pt-64 lg:pb-24",
|
||||
),
|
||||
],
|
||||
[
|
||||
html.div([attribute.class("max-w-4xl space-y-6 text-center")], [
|
||||
case ctx.locale {
|
||||
locale.Ja ->
|
||||
html.div([attribute.class("mb-2 flex justify-center")], [
|
||||
html.span([attribute.class("text-3xl font-bold text-white")], [
|
||||
html.text("Fluxer(フラクサー)"),
|
||||
]),
|
||||
])
|
||||
_ -> element.none()
|
||||
},
|
||||
html.div([attribute.class("mb-4 flex justify-center")], [
|
||||
html.span(
|
||||
[
|
||||
attribute.class(
|
||||
"rounded-full bg-white/20 px-4 py-2 backdrop-blur-sm",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Public Beta"))],
|
||||
),
|
||||
]),
|
||||
html.h1([attribute.class("hero")], [
|
||||
html.text(g_(i18n_ctx, "Chat that puts you first")),
|
||||
]),
|
||||
html.p([attribute.class("lead text-white/90")], [
|
||||
html.text(g_(
|
||||
i18n_ctx,
|
||||
"Built for friends, groups, and communities. Text, voice, and video. Open source and community-funded.",
|
||||
)),
|
||||
]),
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"flex flex-col items-center justify-center gap-4 pt-4 sm:flex-row sm:items-stretch",
|
||||
),
|
||||
],
|
||||
[
|
||||
platform_download_button.render_with_overlay(ctx),
|
||||
html.a(
|
||||
[
|
||||
attribute.href(ctx.app_endpoint <> "/channels/@me"),
|
||||
attribute.class(
|
||||
"hidden sm:inline-flex items-center justify-center gap-3 rounded-2xl bg-white/10 backdrop-blur-sm border-2 border-white/30 px-8 md:px-10 text-lg md:text-xl font-semibold text-white transition-colors hover:bg-white/20 shadow-lg",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Open in Browser"))],
|
||||
),
|
||||
],
|
||||
),
|
||||
hackernews_banner.render(ctx),
|
||||
]),
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"mt-16 flex w-full max-w-7xl items-end justify-center gap-4 px-6 md:mt-24 md:gap-8",
|
||||
),
|
||||
],
|
||||
[
|
||||
html.div(
|
||||
[attribute.class("hidden w-full md:block md:w-3/4 lg:w-2/3")],
|
||||
[
|
||||
element.element("picture", [], [
|
||||
element.element(
|
||||
"source",
|
||||
[
|
||||
attribute.type_("image/avif"),
|
||||
attribute.attribute(
|
||||
"srcset",
|
||||
ctx.cdn_endpoint
|
||||
<> "/marketing/screenshots/desktop-480w.avif?v=4 480w, "
|
||||
<> ctx.cdn_endpoint
|
||||
<> "/marketing/screenshots/desktop-768w.avif?v=4 768w, "
|
||||
<> ctx.cdn_endpoint
|
||||
<> "/marketing/screenshots/desktop-1024w.avif?v=4 1024w, "
|
||||
<> ctx.cdn_endpoint
|
||||
<> "/marketing/screenshots/desktop-1920w.avif?v=4 1920w, "
|
||||
<> ctx.cdn_endpoint
|
||||
<> "/marketing/screenshots/desktop-2560w.avif?v=4 2560w",
|
||||
),
|
||||
attribute.attribute(
|
||||
"sizes",
|
||||
"(max-width: 768px) 100vw, 75vw",
|
||||
),
|
||||
],
|
||||
[],
|
||||
),
|
||||
element.element(
|
||||
"source",
|
||||
[
|
||||
attribute.type_("image/webp"),
|
||||
attribute.attribute(
|
||||
"srcset",
|
||||
ctx.cdn_endpoint
|
||||
<> "/marketing/screenshots/desktop-480w.webp?v=4 480w, "
|
||||
<> ctx.cdn_endpoint
|
||||
<> "/marketing/screenshots/desktop-768w.webp?v=4 768w, "
|
||||
<> ctx.cdn_endpoint
|
||||
<> "/marketing/screenshots/desktop-1024w.webp?v=4 1024w, "
|
||||
<> ctx.cdn_endpoint
|
||||
<> "/marketing/screenshots/desktop-1920w.webp?v=4 1920w, "
|
||||
<> ctx.cdn_endpoint
|
||||
<> "/marketing/screenshots/desktop-2560w.webp?v=4 2560w",
|
||||
),
|
||||
attribute.attribute(
|
||||
"sizes",
|
||||
"(max-width: 768px) 100vw, 75vw",
|
||||
),
|
||||
],
|
||||
[],
|
||||
),
|
||||
html.img([
|
||||
attribute.src(
|
||||
ctx.cdn_endpoint
|
||||
<> "/marketing/screenshots/desktop-1920w.png?v=4",
|
||||
),
|
||||
attribute.attribute(
|
||||
"srcset",
|
||||
ctx.cdn_endpoint
|
||||
<> "/marketing/screenshots/desktop-480w.png?v=4 480w, "
|
||||
<> ctx.cdn_endpoint
|
||||
<> "/marketing/screenshots/desktop-768w.png?v=4 768w, "
|
||||
<> ctx.cdn_endpoint
|
||||
<> "/marketing/screenshots/desktop-1024w.png?v=4 1024w, "
|
||||
<> ctx.cdn_endpoint
|
||||
<> "/marketing/screenshots/desktop-1920w.png?v=4 1920w, "
|
||||
<> ctx.cdn_endpoint
|
||||
<> "/marketing/screenshots/desktop-2560w.png?v=4 2560w",
|
||||
),
|
||||
attribute.attribute("sizes", "(max-width: 768px) 100vw, 75vw"),
|
||||
attribute.alt("Fluxer desktop interface"),
|
||||
attribute.class(
|
||||
"aspect-video w-full rounded-lg border-2 border-white/50",
|
||||
),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
),
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"w-full max-w-[320px] md:w-1/4 md:max-w-none lg:w-1/5",
|
||||
),
|
||||
],
|
||||
[
|
||||
element.element("picture", [], [
|
||||
element.element(
|
||||
"source",
|
||||
[
|
||||
attribute.type_("image/avif"),
|
||||
attribute.attribute(
|
||||
"srcset",
|
||||
ctx.cdn_endpoint
|
||||
<> "/marketing/screenshots/mobile-480w.avif?v=4 480w, "
|
||||
<> ctx.cdn_endpoint
|
||||
<> "/marketing/screenshots/mobile-768w.avif?v=4 768w",
|
||||
),
|
||||
attribute.attribute(
|
||||
"sizes",
|
||||
"(max-width: 768px) 320px, 25vw",
|
||||
),
|
||||
],
|
||||
[],
|
||||
),
|
||||
element.element(
|
||||
"source",
|
||||
[
|
||||
attribute.type_("image/webp"),
|
||||
attribute.attribute(
|
||||
"srcset",
|
||||
ctx.cdn_endpoint
|
||||
<> "/marketing/screenshots/mobile-480w.webp?v=4 480w, "
|
||||
<> ctx.cdn_endpoint
|
||||
<> "/marketing/screenshots/mobile-768w.webp?v=4 768w",
|
||||
),
|
||||
attribute.attribute(
|
||||
"sizes",
|
||||
"(max-width: 768px) 320px, 25vw",
|
||||
),
|
||||
],
|
||||
[],
|
||||
),
|
||||
html.img([
|
||||
attribute.src(
|
||||
ctx.cdn_endpoint
|
||||
<> "/marketing/screenshots/mobile-768w.png?v=4",
|
||||
),
|
||||
attribute.attribute(
|
||||
"srcset",
|
||||
ctx.cdn_endpoint
|
||||
<> "/marketing/screenshots/mobile-480w.png?v=4 480w, "
|
||||
<> ctx.cdn_endpoint
|
||||
<> "/marketing/screenshots/mobile-768w.png?v=4 768w",
|
||||
),
|
||||
attribute.attribute("sizes", "(max-width: 768px) 320px, 25vw"),
|
||||
attribute.alt("Fluxer mobile interface"),
|
||||
attribute.class(
|
||||
"aspect-[9/19] w-full rounded-3xl border-2 border-white/50",
|
||||
),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/html
|
||||
|
||||
pub type HeroConfig(a) {
|
||||
HeroConfig(
|
||||
icon: Element(a),
|
||||
title: String,
|
||||
description: String,
|
||||
extra_content: Element(a),
|
||||
custom_padding: String,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn default_padding() -> String {
|
||||
"px-6 pt-48 md:pt-60 pb-16 md:pb-20 lg:pb-24 text-white"
|
||||
}
|
||||
|
||||
pub fn render(config: HeroConfig(a)) -> Element(a) {
|
||||
html.section([attribute.class(config.custom_padding)], [
|
||||
html.div([attribute.class("mx-auto max-w-5xl text-center")], [
|
||||
html.div([attribute.class("mb-8 flex justify-center")], [
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"inline-flex items-center justify-center w-28 h-28 md:w-36 md:h-36 rounded-3xl bg-white/10 backdrop-blur-sm",
|
||||
),
|
||||
],
|
||||
[config.icon],
|
||||
),
|
||||
]),
|
||||
html.h1(
|
||||
[
|
||||
attribute.class(
|
||||
"hero mb-8 md:mb-10 text-5xl md:text-6xl lg:text-7xl font-bold",
|
||||
),
|
||||
],
|
||||
[html.text(config.title)],
|
||||
),
|
||||
html.p(
|
||||
[
|
||||
attribute.class(
|
||||
"lead text-white/90 text-xl md:text-2xl max-w-4xl mx-auto",
|
||||
),
|
||||
],
|
||||
[html.text(config.description)],
|
||||
),
|
||||
config.extra_content,
|
||||
]),
|
||||
])
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import fluxer_marketing/flags
|
||||
import fluxer_marketing/i18n
|
||||
import fluxer_marketing/icons
|
||||
import fluxer_marketing/locale
|
||||
import fluxer_marketing/web.{type Context, href}
|
||||
import gleam/list
|
||||
import kielet.{gettext as g_}
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/html
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn render_trigger(ctx: Context) -> Element(a) {
|
||||
let i18n_ctx = i18n.get_context(ctx.i18n_db, ctx.locale)
|
||||
|
||||
html.a(
|
||||
[
|
||||
attribute.class(
|
||||
"locale-toggle flex items-center justify-center p-2 rounded-lg hover:bg-gray-100 transition-colors",
|
||||
),
|
||||
attribute.attribute("aria-label", g_(i18n_ctx, "Change language")),
|
||||
attribute.id("locale-button"),
|
||||
href(ctx, "#locale-modal-backdrop"),
|
||||
],
|
||||
[icons.translate([attribute.class("h-5 w-5")])],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn render_modal(ctx: Context, current_path: String) -> Element(a) {
|
||||
let i18n_ctx = i18n.get_context(ctx.i18n_db, ctx.locale)
|
||||
let current_locale = ctx.locale
|
||||
let all_locales = locale.all_locales()
|
||||
|
||||
html.div(
|
||||
[
|
||||
attribute.id("locale-modal-backdrop"),
|
||||
attribute.class("locale-modal-backdrop"),
|
||||
],
|
||||
[
|
||||
html.div([attribute.class("locale-modal")], [
|
||||
html.div([attribute.class("flex flex-col h-full")], [
|
||||
html.div(
|
||||
[attribute.class("flex items-center justify-between p-6 pb-0")],
|
||||
[
|
||||
html.h2([attribute.class("text-xl font-bold text-gray-900")], [
|
||||
html.text(g_(i18n_ctx, "Choose your language")),
|
||||
]),
|
||||
html.a(
|
||||
[
|
||||
attribute.class(
|
||||
"p-2 hover:bg-gray-100 rounded-lg text-gray-600 hover:text-gray-900",
|
||||
),
|
||||
attribute.id("locale-close"),
|
||||
attribute.attribute("aria-label", "Close"),
|
||||
href(ctx, "#"),
|
||||
],
|
||||
[
|
||||
svg.svg(
|
||||
[
|
||||
attribute.class("w-5 h-5"),
|
||||
attribute.attribute("fill", "none"),
|
||||
attribute.attribute("stroke", "currentColor"),
|
||||
attribute.attribute("viewBox", "0 0 24 24"),
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute("stroke-linecap", "round"),
|
||||
attribute.attribute("stroke-linejoin", "round"),
|
||||
attribute.attribute("stroke-width", "2"),
|
||||
attribute.attribute("d", "M6 18L18 6M6 6l12 12"),
|
||||
]),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
html.p(
|
||||
[
|
||||
attribute.class("px-6 pb-2 text-xs text-gray-500 leading-relaxed"),
|
||||
],
|
||||
[
|
||||
html.text(g_(
|
||||
i18n_ctx,
|
||||
"All translations are currently LLM-generated with minimal human revision. We'd love to get real people to help us out localizing Fluxer into your language! To do so, shoot an email to i18n@fluxer.app and we'll be happy to accept your contributions.",
|
||||
)),
|
||||
],
|
||||
),
|
||||
html.div([attribute.class("flex-1 overflow-y-auto p-6 pt-4")], [
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"grid grid-cols-2 md:grid-cols-4 lg:grid-cols-5 gap-3",
|
||||
),
|
||||
],
|
||||
list.map(all_locales, fn(loc) {
|
||||
let is_current = loc == current_locale
|
||||
let locale_code = locale.get_code_from_locale(loc)
|
||||
let native_name = locale.get_locale_name(loc)
|
||||
let localized_name = case loc {
|
||||
locale.Ar -> g_(i18n_ctx, "Arabic")
|
||||
locale.Bg -> g_(i18n_ctx, "Bulgarian")
|
||||
locale.Cs -> g_(i18n_ctx, "Czech")
|
||||
locale.Da -> g_(i18n_ctx, "Danish")
|
||||
locale.De -> g_(i18n_ctx, "German")
|
||||
locale.El -> g_(i18n_ctx, "Greek")
|
||||
locale.EnGB -> g_(i18n_ctx, "English")
|
||||
locale.EnUS -> g_(i18n_ctx, "English (US)")
|
||||
locale.EsES -> g_(i18n_ctx, "Spanish (Spain)")
|
||||
locale.Es419 -> g_(i18n_ctx, "Spanish (Latin America)")
|
||||
locale.Fi -> g_(i18n_ctx, "Finnish")
|
||||
locale.Fr -> g_(i18n_ctx, "French")
|
||||
locale.He -> g_(i18n_ctx, "Hebrew")
|
||||
locale.Hi -> g_(i18n_ctx, "Hindi")
|
||||
locale.Hr -> g_(i18n_ctx, "Croatian")
|
||||
locale.Hu -> g_(i18n_ctx, "Hungarian")
|
||||
locale.Id -> g_(i18n_ctx, "Indonesian")
|
||||
locale.It -> g_(i18n_ctx, "Italian")
|
||||
locale.Ja -> g_(i18n_ctx, "Japanese")
|
||||
locale.Ko -> g_(i18n_ctx, "Korean")
|
||||
locale.Lt -> g_(i18n_ctx, "Lithuanian")
|
||||
locale.Nl -> g_(i18n_ctx, "Dutch")
|
||||
locale.No -> g_(i18n_ctx, "Norwegian")
|
||||
locale.Pl -> g_(i18n_ctx, "Polish")
|
||||
locale.PtBR -> g_(i18n_ctx, "Portuguese (Brazil)")
|
||||
locale.Ro -> g_(i18n_ctx, "Romanian")
|
||||
locale.Ru -> g_(i18n_ctx, "Russian")
|
||||
locale.SvSE -> g_(i18n_ctx, "Swedish")
|
||||
locale.Th -> g_(i18n_ctx, "Thai")
|
||||
locale.Tr -> g_(i18n_ctx, "Turkish")
|
||||
locale.Uk -> g_(i18n_ctx, "Ukrainian")
|
||||
locale.Vi -> g_(i18n_ctx, "Vietnamese")
|
||||
locale.ZhCN -> g_(i18n_ctx, "Chinese (Simplified)")
|
||||
locale.ZhTW -> g_(i18n_ctx, "Chinese (Traditional)")
|
||||
}
|
||||
|
||||
html.form(
|
||||
[
|
||||
attribute.action(web.prepend_base_path(ctx, "/_locale")),
|
||||
attribute.method("POST"),
|
||||
attribute.class("contents locale-form"),
|
||||
],
|
||||
[
|
||||
html.input([
|
||||
attribute.type_("hidden"),
|
||||
attribute.name("locale"),
|
||||
attribute.value(locale_code),
|
||||
]),
|
||||
html.input([
|
||||
attribute.type_("hidden"),
|
||||
attribute.name("redirect"),
|
||||
attribute.value(current_path),
|
||||
]),
|
||||
html.button(
|
||||
[
|
||||
attribute.type_("submit"),
|
||||
attribute.class(
|
||||
"relative flex flex-col items-center gap-3 p-4 rounded-xl border-2 hover:bg-gray-50 transition-colors text-center min-h-[120px] justify-center "
|
||||
<> case is_current {
|
||||
True -> "border-blue-500 bg-blue-50"
|
||||
False -> "border-gray-200"
|
||||
},
|
||||
),
|
||||
],
|
||||
[
|
||||
case is_current {
|
||||
True ->
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"absolute top-2 right-2 w-6 h-6 bg-blue-600 rounded-full flex items-center justify-center",
|
||||
),
|
||||
],
|
||||
[
|
||||
svg.svg(
|
||||
[
|
||||
attribute.class("w-4 h-4 text-white"),
|
||||
attribute.attribute("fill", "none"),
|
||||
attribute.attribute(
|
||||
"stroke",
|
||||
"currentColor",
|
||||
),
|
||||
attribute.attribute("viewBox", "0 0 24 24"),
|
||||
attribute.attribute("stroke-width", "2"),
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"stroke-linecap",
|
||||
"round",
|
||||
),
|
||||
attribute.attribute(
|
||||
"stroke-linejoin",
|
||||
"round",
|
||||
),
|
||||
attribute.attribute("d", "M5 13l4 4L19 7"),
|
||||
]),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
False -> html.text("")
|
||||
},
|
||||
flags.flag_svg(loc, ctx, [
|
||||
attribute.class("w-8 h-8 rounded"),
|
||||
]),
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"font-semibold text-gray-900 text-sm",
|
||||
),
|
||||
],
|
||||
[html.text(native_name)],
|
||||
),
|
||||
html.div([attribute.class("text-xs text-gray-500")], [
|
||||
html.text(localized_name),
|
||||
]),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
}),
|
||||
),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn render(ctx: Context, current_path: String) -> Element(a) {
|
||||
html.div([], [
|
||||
render_trigger(ctx),
|
||||
render_modal(ctx, current_path),
|
||||
])
|
||||
}
|
||||
@@ -0,0 +1,494 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import fluxer_marketing/components/locale_selector
|
||||
import fluxer_marketing/components/platform_download_button
|
||||
import fluxer_marketing/i18n
|
||||
import fluxer_marketing/icons
|
||||
import fluxer_marketing/web.{type Context, href}
|
||||
import kielet.{gettext as g_}
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/html
|
||||
import wisp.{type Request}
|
||||
|
||||
pub fn render(ctx: Context, _req: Request) -> Element(a) {
|
||||
let i18n_ctx = i18n.get_context(ctx.i18n_db, ctx.locale)
|
||||
let #(drawer_download_url, drawer_download_label, drawer_download_icon) =
|
||||
platform_download_button.get_platform_download_info(ctx)
|
||||
|
||||
html.nav(
|
||||
[
|
||||
attribute.id("navbar"),
|
||||
attribute.class("fixed left-0 right-0 z-40"),
|
||||
attribute.style("top", "var(--banner-height, 60px)"),
|
||||
],
|
||||
[
|
||||
html.input([
|
||||
attribute.type_("checkbox"),
|
||||
attribute.id("nav-toggle"),
|
||||
attribute.class("hidden peer"),
|
||||
]),
|
||||
html.div([attribute.class("px-4 py-6 md:px-8 md:py-8")], [
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"mx-auto max-w-7xl rounded-2xl bg-white/95 backdrop-blur-lg shadow-lg border border-gray-200/60 px-4 py-3 md:px-6 md:py-3",
|
||||
),
|
||||
],
|
||||
[
|
||||
html.div([attribute.class("flex items-center justify-between")], [
|
||||
html.div([attribute.class("flex items-center gap-8 xl:gap-12")], [
|
||||
html.a(
|
||||
[
|
||||
href(ctx, "/"),
|
||||
attribute.class(
|
||||
"flex items-center transition-opacity hover:opacity-80 relative z-10",
|
||||
),
|
||||
attribute.attribute("aria-label", "Fluxer home"),
|
||||
],
|
||||
[
|
||||
icons.fluxer_logo_wordmark([
|
||||
attribute.class("h-8 md:h-9 text-[#4641D9]"),
|
||||
]),
|
||||
],
|
||||
),
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"hidden lg:flex items-center gap-6 xl:gap-8",
|
||||
),
|
||||
],
|
||||
[
|
||||
html.a(
|
||||
[
|
||||
href(ctx, "/download"),
|
||||
attribute.class(
|
||||
"body-lg text-gray-900/90 font-semibold hover:text-gray-900 transition-colors",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Download"))],
|
||||
),
|
||||
html.a(
|
||||
[
|
||||
href(ctx, "/plutonium"),
|
||||
attribute.class(
|
||||
"body-lg text-gray-900/90 font-semibold hover:text-gray-900 transition-colors",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Pricing"))],
|
||||
),
|
||||
html.a(
|
||||
[
|
||||
href(ctx, "/help"),
|
||||
attribute.class(
|
||||
"body-lg text-gray-900/90 font-semibold hover:text-gray-900 transition-colors",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Help"))],
|
||||
),
|
||||
html.a(
|
||||
[
|
||||
attribute.href("https://docs.fluxer.app"),
|
||||
attribute.class(
|
||||
"body-lg text-gray-900/90 font-semibold hover:text-gray-900 transition-colors",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Docs"))],
|
||||
),
|
||||
html.a(
|
||||
[
|
||||
href(ctx, "/donate"),
|
||||
attribute.class(
|
||||
"body-lg text-gray-900/90 font-semibold hover:text-gray-900 transition-colors",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Donate"))],
|
||||
),
|
||||
],
|
||||
),
|
||||
]),
|
||||
html.div([attribute.class("flex items-center gap-3")], [
|
||||
html.a(
|
||||
[
|
||||
attribute.href("https://bsky.app/profile/fluxer.app"),
|
||||
attribute.class(
|
||||
"hidden lg:flex items-center p-2 rounded-lg text-[#4641D9] hover:text-[#3d38c7] hover:bg-gray-100 transition-colors",
|
||||
),
|
||||
attribute.target("_blank"),
|
||||
attribute.rel("noopener noreferrer"),
|
||||
attribute.attribute("aria-label", "Bluesky"),
|
||||
],
|
||||
[icons.bluesky([attribute.class("h-5 w-5")])],
|
||||
),
|
||||
html.a(
|
||||
[
|
||||
attribute.href("https://github.com/fluxerapp/fluxer"),
|
||||
attribute.class(
|
||||
"hidden lg:flex items-center p-2 rounded-lg text-[#4641D9] hover:text-[#3d38c7] hover:bg-gray-100 transition-colors",
|
||||
),
|
||||
attribute.target("_blank"),
|
||||
attribute.rel("noopener noreferrer"),
|
||||
attribute.attribute("aria-label", "GitHub"),
|
||||
],
|
||||
[icons.github([attribute.class("h-5 w-5")])],
|
||||
),
|
||||
html.a(
|
||||
[
|
||||
attribute.href("https://bsky.app/profile/fluxer.app/rss"),
|
||||
attribute.class(
|
||||
"hidden lg:flex items-center p-2 rounded-lg text-[#4641D9] hover:text-[#3d38c7] hover:bg-gray-100 transition-colors",
|
||||
),
|
||||
attribute.target("_blank"),
|
||||
attribute.rel("noopener noreferrer"),
|
||||
attribute.attribute("aria-label", "RSS Feed"),
|
||||
],
|
||||
[icons.rss([attribute.class("h-5 w-5")])],
|
||||
),
|
||||
html.div([attribute.class("hidden lg:block text-[#4641D9]")], [
|
||||
locale_selector.render_trigger(ctx),
|
||||
]),
|
||||
html.a(
|
||||
[
|
||||
attribute.href(ctx.app_endpoint <> "/channels/@me"),
|
||||
attribute.class(
|
||||
"hidden lg:inline-flex whitespace-nowrap rounded-xl bg-[#4641D9] px-5 py-2.5 text-base font-semibold text-white transition-colors hover:bg-opacity-90 shadow-md",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Open Fluxer"))],
|
||||
),
|
||||
html.label(
|
||||
[
|
||||
attribute.for("nav-toggle"),
|
||||
attribute.class(
|
||||
"lg:hidden flex items-center justify-center p-2 hover:bg-gray-100 rounded-lg transition-colors cursor-pointer relative z-10",
|
||||
),
|
||||
],
|
||||
[
|
||||
icons.menu([
|
||||
attribute.class(
|
||||
"h-6 w-6 text-gray-900 peer-checked:hidden",
|
||||
),
|
||||
]),
|
||||
],
|
||||
),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
),
|
||||
]),
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"fixed inset-0 z-50 bg-black/50 backdrop-blur-sm opacity-0 pointer-events-none peer-checked:opacity-100 peer-checked:pointer-events-auto transition-opacity lg:hidden",
|
||||
),
|
||||
],
|
||||
[
|
||||
html.label(
|
||||
[attribute.for("nav-toggle"), attribute.class("absolute inset-0")],
|
||||
[],
|
||||
),
|
||||
],
|
||||
),
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"fixed top-0 right-0 bottom-0 z-50 w-full sm:w-[420px] sm:max-w-[90vw] bg-white rounded-none sm:rounded-l-3xl shadow-2xl transform translate-x-full peer-checked:translate-x-0 transition-transform lg:hidden overflow-y-auto",
|
||||
),
|
||||
],
|
||||
[
|
||||
html.div([attribute.class("flex h-full flex-col p-6")], [
|
||||
html.div(
|
||||
[attribute.class("mb-4 flex items-center justify-between")],
|
||||
[
|
||||
html.a(
|
||||
[
|
||||
href(ctx, "/"),
|
||||
attribute.class(
|
||||
"flex items-center gap-3 rounded-xl px-2 py-1 hover:bg-gray-50 transition-colors",
|
||||
),
|
||||
attribute.attribute("aria-label", "Fluxer home"),
|
||||
],
|
||||
[
|
||||
icons.fluxer_logo_wordmark([
|
||||
attribute.class("h-7 text-[#4641D9]"),
|
||||
]),
|
||||
],
|
||||
),
|
||||
html.label(
|
||||
[
|
||||
attribute.for("nav-toggle"),
|
||||
attribute.class(
|
||||
"p-2 hover:bg-gray-100 rounded-lg transition-colors cursor-pointer",
|
||||
),
|
||||
],
|
||||
[icons.x([attribute.class("h-6 w-6 text-gray-900")])],
|
||||
),
|
||||
],
|
||||
),
|
||||
html.div([attribute.class("mb-4 text-gray-900")], [
|
||||
html.a(
|
||||
[
|
||||
href(ctx, "#locale-modal-backdrop"),
|
||||
attribute.class(
|
||||
"flex items-center gap-2 rounded-lg px-2 py-2 text-base font-semibold text-gray-900 hover:bg-gray-100 transition-colors",
|
||||
),
|
||||
attribute.attribute(
|
||||
"aria-label",
|
||||
g_(i18n_ctx, "Change language"),
|
||||
),
|
||||
],
|
||||
[
|
||||
icons.translate([attribute.class("h-5 w-5")]),
|
||||
html.span([], [html.text(g_(i18n_ctx, "Change language"))]),
|
||||
],
|
||||
),
|
||||
]),
|
||||
html.div([attribute.class("flex-1 overflow-y-auto")], [
|
||||
html.div([attribute.class("flex flex-col gap-4 py-2")], [
|
||||
html.div([], [
|
||||
html.p(
|
||||
[
|
||||
attribute.class(
|
||||
"mb-1 text-xs font-semibold uppercase tracking-wide text-gray-500",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Product"))],
|
||||
),
|
||||
html.div([attribute.class("flex flex-col gap-1")], [
|
||||
html.a(
|
||||
[
|
||||
href(ctx, "/download"),
|
||||
attribute.class(
|
||||
"px-2 py-2 text-base font-semibold text-gray-900 hover:bg-gray-100 rounded-lg transition-colors",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Download"))],
|
||||
),
|
||||
html.a(
|
||||
[
|
||||
href(ctx, "/plutonium"),
|
||||
attribute.class(
|
||||
"px-2 py-2 text-base font-semibold text-gray-900 hover:bg-gray-100 rounded-lg transition-colors",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Pricing"))],
|
||||
),
|
||||
html.a(
|
||||
[
|
||||
href(ctx, "/partners"),
|
||||
attribute.class(
|
||||
"px-2 py-2 text-base font-semibold text-gray-900 hover:bg-gray-100 rounded-lg transition-colors",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Partners"))],
|
||||
),
|
||||
]),
|
||||
]),
|
||||
html.div([], [
|
||||
html.p(
|
||||
[
|
||||
attribute.class(
|
||||
"mb-1 text-xs font-semibold uppercase tracking-wide text-gray-500",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Resources"))],
|
||||
),
|
||||
html.div([attribute.class("flex flex-col gap-1")], [
|
||||
html.a(
|
||||
[
|
||||
attribute.href("https://docs.fluxer.app"),
|
||||
attribute.class(
|
||||
"px-2 py-2 text-base font-semibold text-gray-900 hover:bg-gray-100 rounded-lg transition-colors",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Docs"))],
|
||||
),
|
||||
html.a(
|
||||
[
|
||||
href(ctx, "/help"),
|
||||
attribute.class(
|
||||
"px-2 py-2 text-base font-semibold text-gray-900 hover:bg-gray-100 rounded-lg transition-colors",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Help Center"))],
|
||||
),
|
||||
html.a(
|
||||
[
|
||||
href(ctx, "/press"),
|
||||
attribute.class(
|
||||
"px-2 py-2 text-base font-semibold text-gray-900 hover:bg-gray-100 rounded-lg transition-colors",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Press"))],
|
||||
),
|
||||
]),
|
||||
]),
|
||||
html.div([], [
|
||||
html.p(
|
||||
[
|
||||
attribute.class(
|
||||
"mb-1 text-xs font-semibold uppercase tracking-wide text-gray-500",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Company"))],
|
||||
),
|
||||
html.div([attribute.class("flex flex-col gap-1")], [
|
||||
html.a(
|
||||
[
|
||||
href(ctx, "/careers"),
|
||||
attribute.class(
|
||||
"px-2 py-2 text-base font-semibold text-gray-900 hover:bg-gray-100 rounded-lg transition-colors",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Careers"))],
|
||||
),
|
||||
html.a(
|
||||
[
|
||||
href(ctx, "/donate"),
|
||||
attribute.class(
|
||||
"px-2 py-2 text-base font-semibold text-gray-900 hover:bg-gray-100 rounded-lg transition-colors",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Donate"))],
|
||||
),
|
||||
html.a(
|
||||
[
|
||||
href(ctx, "/company-information"),
|
||||
attribute.class(
|
||||
"px-2 py-2 text-base font-semibold text-gray-900 hover:bg-gray-100 rounded-lg transition-colors",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Company Information"))],
|
||||
),
|
||||
]),
|
||||
]),
|
||||
html.div([], [
|
||||
html.p(
|
||||
[
|
||||
attribute.class(
|
||||
"mb-1 text-xs font-semibold uppercase tracking-wide text-gray-500",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Policies"))],
|
||||
),
|
||||
html.div([attribute.class("flex flex-col gap-1")], [
|
||||
html.a(
|
||||
[
|
||||
href(ctx, "/terms"),
|
||||
attribute.class(
|
||||
"px-2 py-2 text-base font-semibold text-gray-900 hover:bg-gray-100 rounded-lg transition-colors",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Terms of Service"))],
|
||||
),
|
||||
html.a(
|
||||
[
|
||||
href(ctx, "/privacy"),
|
||||
attribute.class(
|
||||
"px-2 py-2 text-base font-semibold text-gray-900 hover:bg-gray-100 rounded-lg transition-colors",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Privacy Policy"))],
|
||||
),
|
||||
html.a(
|
||||
[
|
||||
href(ctx, "/guidelines"),
|
||||
attribute.class(
|
||||
"px-2 py-2 text-base font-semibold text-gray-900 hover:bg-gray-100 rounded-lg transition-colors",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Community Guidelines"))],
|
||||
),
|
||||
]),
|
||||
]),
|
||||
html.div([], [
|
||||
html.p(
|
||||
[
|
||||
attribute.class(
|
||||
"mb-1 text-xs font-semibold uppercase tracking-wide text-gray-500",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Connect"))],
|
||||
),
|
||||
html.div([attribute.class("flex flex-col gap-1")], [
|
||||
html.a(
|
||||
[
|
||||
attribute.href("https://github.com/fluxerapp/fluxer"),
|
||||
attribute.class(
|
||||
"px-2 py-2 text-base font-semibold text-gray-900 hover:bg-gray-100 rounded-lg transition-colors",
|
||||
),
|
||||
attribute.target("_blank"),
|
||||
attribute.rel("noopener noreferrer"),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Source Code"))],
|
||||
),
|
||||
html.a(
|
||||
[
|
||||
attribute.href("https://bsky.app/profile/fluxer.app"),
|
||||
attribute.class(
|
||||
"px-2 py-2 text-base font-semibold text-gray-900 hover:bg-gray-100 rounded-lg transition-colors",
|
||||
),
|
||||
attribute.target("_blank"),
|
||||
attribute.rel("noopener noreferrer"),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Bluesky"))],
|
||||
),
|
||||
html.a(
|
||||
[
|
||||
attribute.href("mailto:support@fluxer.app"),
|
||||
attribute.class(
|
||||
"px-2 py-2 text-base font-semibold text-gray-900 hover:bg-gray-100 rounded-lg transition-colors",
|
||||
),
|
||||
],
|
||||
[html.text("support@fluxer.app")],
|
||||
),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
html.div([attribute.class("mt-6")], [
|
||||
html.div([attribute.class("flex flex-col gap-3")], [
|
||||
html.a(
|
||||
[
|
||||
attribute.href(ctx.app_endpoint <> "/channels/@me"),
|
||||
attribute.class(
|
||||
"flex items-center justify-center rounded-xl border border-gray-300 px-5 py-3 text-base font-semibold text-gray-900 transition-colors hover:bg-gray-50",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Open in Browser"))],
|
||||
),
|
||||
html.a(
|
||||
[
|
||||
attribute.href(drawer_download_url),
|
||||
attribute.class(
|
||||
"flex items-center justify-center gap-2 rounded-xl bg-[#4641D9] px-5 py-3 text-base font-semibold text-white transition-colors hover:bg-opacity-90 shadow-md",
|
||||
),
|
||||
],
|
||||
[
|
||||
drawer_download_icon,
|
||||
html.span([], [html.text(drawer_download_label)]),
|
||||
],
|
||||
),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import fluxer_marketing/i18n
|
||||
import fluxer_marketing/icons
|
||||
import fluxer_marketing/web.{type Context, href}
|
||||
import gleam/option.{None}
|
||||
import kielet.{gettext as g_}
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/html
|
||||
|
||||
pub fn render(ctx: Context) -> Element(a) {
|
||||
let i18n_ctx = i18n.get_context(ctx.i18n_db, ctx.locale)
|
||||
|
||||
html.section(
|
||||
[
|
||||
attribute.class(
|
||||
"bg-gradient-to-b from-gray-50 to-white px-6 pb-24 md:pb-40",
|
||||
),
|
||||
],
|
||||
[
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"mx-auto max-w-6xl rounded-3xl bg-gradient-to-br from-black to-gray-900 p-10 text-white md:p-16 lg:p-20 shadow-xl",
|
||||
),
|
||||
],
|
||||
[
|
||||
html.div([attribute.class("mb-10 md:mb-12 text-center")], [
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"inline-flex items-center justify-center w-20 h-20 md:w-24 md:h-24 rounded-2xl bg-white/10 backdrop-blur-sm mb-6 md:mb-8",
|
||||
),
|
||||
],
|
||||
[
|
||||
icons.fluxer_partner([
|
||||
attribute.class("h-10 w-10 md:h-12 md:w-12"),
|
||||
]),
|
||||
],
|
||||
),
|
||||
html.h2(
|
||||
[
|
||||
attribute.class(
|
||||
"display mb-6 md:mb-8 text-white text-3xl md:text-4xl lg:text-5xl",
|
||||
),
|
||||
],
|
||||
[
|
||||
html.text(g_(i18n_ctx, "Become a Fluxer Partner")),
|
||||
],
|
||||
),
|
||||
html.p([attribute.class("lead mx-auto max-w-3xl text-white/90")], [
|
||||
html.text(g_(
|
||||
i18n_ctx,
|
||||
"Content creators and community owners: unlock exclusive perks including free Plutonium, a Partner badge, custom vanity URLs, and more.",
|
||||
)),
|
||||
]),
|
||||
]),
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"mb-10 md:mb-12 grid gap-6 md:gap-8 grid-cols-1 sm:grid-cols-3 max-w-3xl mx-auto",
|
||||
),
|
||||
],
|
||||
[
|
||||
html.div(
|
||||
[attribute.class("flex flex-col items-center text-center")],
|
||||
[
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"inline-flex items-center justify-center w-16 h-16 md:w-20 md:h-20 rounded-xl bg-white/10 backdrop-blur-sm mb-4",
|
||||
),
|
||||
],
|
||||
[
|
||||
icons.fluxer_premium(None, [
|
||||
attribute.class("h-8 w-8 md:h-10 md:w-10"),
|
||||
]),
|
||||
],
|
||||
),
|
||||
html.p([attribute.class("body-lg font-semibold text-white")], [
|
||||
html.text(g_(i18n_ctx, "Free Plutonium")),
|
||||
]),
|
||||
],
|
||||
),
|
||||
html.div(
|
||||
[attribute.class("flex flex-col items-center text-center")],
|
||||
[
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"inline-flex items-center justify-center w-16 h-16 md:w-20 md:h-20 rounded-xl bg-white/10 backdrop-blur-sm mb-4",
|
||||
),
|
||||
],
|
||||
[
|
||||
icons.seal_check([
|
||||
attribute.class("h-8 w-8 md:h-10 md:w-10"),
|
||||
]),
|
||||
],
|
||||
),
|
||||
html.p([attribute.class("body-lg font-semibold text-white")], [
|
||||
html.text(g_(i18n_ctx, "Verified Community")),
|
||||
]),
|
||||
],
|
||||
),
|
||||
html.div(
|
||||
[attribute.class("flex flex-col items-center text-center")],
|
||||
[
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"inline-flex items-center justify-center w-16 h-16 md:w-20 md:h-20 rounded-xl bg-white/10 backdrop-blur-sm mb-4",
|
||||
),
|
||||
],
|
||||
[
|
||||
icons.chats_circle([
|
||||
attribute.class("h-8 w-8 md:h-10 md:w-10"),
|
||||
]),
|
||||
],
|
||||
),
|
||||
html.p([attribute.class("body-lg font-semibold text-white")], [
|
||||
html.text(g_(i18n_ctx, "Direct Team Access")),
|
||||
]),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
html.div([attribute.class("text-center")], [
|
||||
html.a(
|
||||
[
|
||||
href(ctx, "/partners"),
|
||||
attribute.class(
|
||||
"label inline-block rounded-lg bg-white px-8 py-4 text-black transition-colors hover:bg-opacity-90",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Become a Partner"))],
|
||||
),
|
||||
]),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
}
|
||||
144
fluxer_marketing/src/fluxer_marketing/components/picture.gleam
Normal file
144
fluxer_marketing/src/fluxer_marketing/components/picture.gleam
Normal file
@@ -0,0 +1,144 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import gleam/int
|
||||
import gleam/string
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/html
|
||||
|
||||
pub fn render(
|
||||
src: String,
|
||||
alt: String,
|
||||
class: String,
|
||||
sizes: String,
|
||||
width_1x: Int,
|
||||
width_2x: Int,
|
||||
) -> Element(a) {
|
||||
render_with_priority(src, alt, class, sizes, width_1x, width_2x, False)
|
||||
}
|
||||
|
||||
pub fn render_priority(
|
||||
src: String,
|
||||
alt: String,
|
||||
class: String,
|
||||
sizes: String,
|
||||
width_1x: Int,
|
||||
width_2x: Int,
|
||||
) -> Element(a) {
|
||||
render_with_priority(src, alt, class, sizes, width_1x, width_2x, True)
|
||||
}
|
||||
|
||||
fn render_with_priority(
|
||||
src: String,
|
||||
alt: String,
|
||||
class: String,
|
||||
sizes: String,
|
||||
width_1x: Int,
|
||||
width_2x: Int,
|
||||
high_priority: Bool,
|
||||
) -> Element(a) {
|
||||
let base_path = case string.split(src, ".") {
|
||||
[] -> src
|
||||
parts -> {
|
||||
parts
|
||||
|> remove_last
|
||||
|> string.join(".")
|
||||
}
|
||||
}
|
||||
|
||||
let width_1x_str = int.to_string(width_1x)
|
||||
let width_2x_str = int.to_string(width_2x)
|
||||
|
||||
html.picture([], [
|
||||
html.source([
|
||||
attribute.attribute(
|
||||
"srcset",
|
||||
base_path
|
||||
<> "-1x.avif "
|
||||
<> width_1x_str
|
||||
<> "w, "
|
||||
<> base_path
|
||||
<> "-2x.avif "
|
||||
<> width_2x_str
|
||||
<> "w",
|
||||
),
|
||||
attribute.attribute("sizes", sizes),
|
||||
attribute.attribute("type", "image/avif"),
|
||||
]),
|
||||
html.source([
|
||||
attribute.attribute(
|
||||
"srcset",
|
||||
base_path
|
||||
<> "-1x.webp "
|
||||
<> width_1x_str
|
||||
<> "w, "
|
||||
<> base_path
|
||||
<> "-2x.webp "
|
||||
<> width_2x_str
|
||||
<> "w",
|
||||
),
|
||||
attribute.attribute("sizes", sizes),
|
||||
attribute.attribute("type", "image/webp"),
|
||||
]),
|
||||
html.img(case high_priority {
|
||||
True -> [
|
||||
attribute.attribute(
|
||||
"srcset",
|
||||
base_path
|
||||
<> "-1x.png "
|
||||
<> width_1x_str
|
||||
<> "w, "
|
||||
<> base_path
|
||||
<> "-2x.png "
|
||||
<> width_2x_str
|
||||
<> "w",
|
||||
),
|
||||
attribute.attribute("sizes", sizes),
|
||||
attribute.attribute("fetchpriority", "high"),
|
||||
attribute.src(base_path <> "-1x.png"),
|
||||
attribute.alt(alt),
|
||||
attribute.class(class),
|
||||
]
|
||||
False -> [
|
||||
attribute.attribute(
|
||||
"srcset",
|
||||
base_path
|
||||
<> "-1x.png "
|
||||
<> width_1x_str
|
||||
<> "w, "
|
||||
<> base_path
|
||||
<> "-2x.png "
|
||||
<> width_2x_str
|
||||
<> "w",
|
||||
),
|
||||
attribute.attribute("sizes", sizes),
|
||||
attribute.src(base_path <> "-1x.png"),
|
||||
attribute.alt(alt),
|
||||
attribute.class(class),
|
||||
]
|
||||
}),
|
||||
])
|
||||
}
|
||||
|
||||
fn remove_last(list: List(a)) -> List(a) {
|
||||
case list {
|
||||
[] -> []
|
||||
[_] -> []
|
||||
[first, ..rest] -> [first, ..remove_last(rest)]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,543 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import fluxer_marketing/i18n
|
||||
import fluxer_marketing/icons
|
||||
import fluxer_marketing/web.{type Context}
|
||||
import gleam/list
|
||||
import gleam/option.{type Option, None, Some}
|
||||
import kielet.{gettext as g_}
|
||||
import kielet/context.{type Context as I18nContext}
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/html
|
||||
|
||||
pub type Platform {
|
||||
Windows
|
||||
MacOS
|
||||
Linux
|
||||
IOS
|
||||
Android
|
||||
}
|
||||
|
||||
pub type ButtonStyle {
|
||||
Light
|
||||
Dark
|
||||
}
|
||||
|
||||
const light_bg = "bg-white"
|
||||
|
||||
const light_text = "text-[#4641D9]"
|
||||
|
||||
const light_hover = "hover:bg-gray-50"
|
||||
|
||||
const light_helper = "text-sm text-[#4641D9]/60"
|
||||
|
||||
const dark_bg = "bg-[#4641D9]"
|
||||
|
||||
const dark_text = "text-white"
|
||||
|
||||
const dark_hover = "hover:bg-[#3a36b0]"
|
||||
|
||||
const dark_helper = "text-sm text-white/70"
|
||||
|
||||
const btn_base = "download-link flex flex-col items-start justify-center gap-1 rounded-l-2xl px-6 py-5 md:px-8 md:py-6 transition-colors shadow-lg"
|
||||
|
||||
const chevron_base = "overlay-toggle flex items-center self-stretch rounded-r-2xl px-4 transition-colors shadow-lg"
|
||||
|
||||
const mobile_btn_base = "inline-flex flex-col items-center justify-center gap-1 rounded-2xl px-6 py-5 md:px-8 md:py-6 transition-colors shadow-lg"
|
||||
|
||||
fn channel_segment(ctx: Context) -> String {
|
||||
case web.is_canary(ctx) {
|
||||
True -> "canary"
|
||||
False -> "stable"
|
||||
}
|
||||
}
|
||||
|
||||
fn desktop_redirect_url(
|
||||
ctx: Context,
|
||||
plat: String,
|
||||
arch: String,
|
||||
fmt: String,
|
||||
) -> String {
|
||||
web.api_url(
|
||||
ctx,
|
||||
"/dl/desktop/"
|
||||
<> channel_segment(ctx)
|
||||
<> "/"
|
||||
<> plat
|
||||
<> "/"
|
||||
<> arch
|
||||
<> "/latest/"
|
||||
<> fmt,
|
||||
)
|
||||
}
|
||||
|
||||
fn get_desktop_btn_classes(style: ButtonStyle) -> #(String, String, String) {
|
||||
case style {
|
||||
Light -> #(
|
||||
btn_base <> " " <> light_bg <> " " <> light_text <> " " <> light_hover,
|
||||
chevron_base
|
||||
<> " "
|
||||
<> light_bg
|
||||
<> " border-l border-gray-200 "
|
||||
<> light_text
|
||||
<> " "
|
||||
<> light_hover,
|
||||
light_helper,
|
||||
)
|
||||
Dark -> #(
|
||||
btn_base <> " " <> dark_bg <> " " <> dark_text <> " " <> dark_hover,
|
||||
chevron_base
|
||||
<> " "
|
||||
<> dark_bg
|
||||
<> " border-l border-white/20 "
|
||||
<> dark_text
|
||||
<> " "
|
||||
<> dark_hover,
|
||||
dark_helper,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mobile_btn_classes(style: ButtonStyle) -> #(String, String) {
|
||||
case style {
|
||||
Light -> #(
|
||||
mobile_btn_base
|
||||
<> " "
|
||||
<> light_bg
|
||||
<> " "
|
||||
<> light_text
|
||||
<> " hover:bg-white/90",
|
||||
"text-xs text-[#4641D9]/70",
|
||||
)
|
||||
Dark -> #(
|
||||
mobile_btn_base <> " " <> dark_bg <> " " <> dark_text <> " " <> dark_hover,
|
||||
"text-xs text-white/70",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn format_arch_label(
|
||||
i18n_ctx: I18nContext,
|
||||
platform: Platform,
|
||||
arch: String,
|
||||
fmt: String,
|
||||
) -> String {
|
||||
case platform {
|
||||
MacOS ->
|
||||
case arch {
|
||||
"arm64" -> g_(i18n_ctx, "Apple Silicon") <> " · " <> fmt
|
||||
_ -> g_(i18n_ctx, "Intel") <> " · " <> fmt
|
||||
}
|
||||
_ -> arch <> " · " <> fmt
|
||||
}
|
||||
}
|
||||
|
||||
fn format_overlay_label(
|
||||
i18n_ctx: I18nContext,
|
||||
platform: Platform,
|
||||
arch: String,
|
||||
fmt: String,
|
||||
) -> String {
|
||||
case platform {
|
||||
MacOS ->
|
||||
case arch {
|
||||
"arm64" -> g_(i18n_ctx, "Apple Silicon") <> " (" <> fmt <> ")"
|
||||
_ -> g_(i18n_ctx, "Intel") <> " (" <> fmt <> ")"
|
||||
}
|
||||
_ -> fmt <> " (" <> arch <> ")"
|
||||
}
|
||||
}
|
||||
|
||||
fn default_architecture(ctx: Context, platform: Platform) -> String {
|
||||
case platform {
|
||||
MacOS ->
|
||||
case ctx.architecture {
|
||||
web.ARM64 -> "arm64"
|
||||
web.ArchUnknown -> "arm64"
|
||||
_ -> "x64"
|
||||
}
|
||||
_ ->
|
||||
case ctx.architecture {
|
||||
web.ARM64 -> "arm64"
|
||||
_ -> "x64"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_platform_download_info(ctx: Context) -> #(String, String, Element(a)) {
|
||||
let i18n_ctx = i18n.get_context(ctx.i18n_db, ctx.locale)
|
||||
|
||||
case ctx.platform {
|
||||
web.Windows -> {
|
||||
let arch = default_architecture(ctx, Windows)
|
||||
#(
|
||||
desktop_redirect_url(ctx, "win32", arch, "setup"),
|
||||
g_(i18n_ctx, "Download for Windows"),
|
||||
icons.windows([attribute.class("h-5 w-5")]),
|
||||
)
|
||||
}
|
||||
web.MacOS -> {
|
||||
let arch = default_architecture(ctx, MacOS)
|
||||
#(
|
||||
desktop_redirect_url(ctx, "darwin", arch, "dmg"),
|
||||
g_(i18n_ctx, "Download for macOS"),
|
||||
icons.apple([attribute.class("h-5 w-5")]),
|
||||
)
|
||||
}
|
||||
web.Linux -> {
|
||||
let arch = default_architecture(ctx, Linux)
|
||||
#(
|
||||
desktop_redirect_url(ctx, "linux", arch, "deb"),
|
||||
g_(i18n_ctx, "Choose Linux distribution"),
|
||||
icons.linux([attribute.class("h-5 w-5")]),
|
||||
)
|
||||
}
|
||||
web.IOS -> #(
|
||||
web.prepend_base_path(ctx, "/download"),
|
||||
g_(i18n_ctx, "Mobile apps are underway"),
|
||||
icons.download([attribute.class("h-5 w-5")]),
|
||||
)
|
||||
web.Android -> #(
|
||||
web.prepend_base_path(ctx, "/download"),
|
||||
g_(i18n_ctx, "Mobile apps are underway"),
|
||||
icons.download([attribute.class("h-5 w-5")]),
|
||||
)
|
||||
web.Unknown -> #(
|
||||
web.prepend_base_path(ctx, "/download"),
|
||||
g_(i18n_ctx, "Download"),
|
||||
icons.download([attribute.class("h-5 w-5")]),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_system_requirements(ctx: Context, platform: Platform) -> String {
|
||||
let i18n_ctx = i18n.get_context(ctx.i18n_db, ctx.locale)
|
||||
case platform {
|
||||
Windows -> g_(i18n_ctx, "Windows 10+")
|
||||
MacOS -> g_(i18n_ctx, "macOS 10.15+")
|
||||
Linux -> ""
|
||||
IOS -> g_(i18n_ctx, "iOS 15+")
|
||||
Android -> g_(i18n_ctx, "Android 8+")
|
||||
}
|
||||
}
|
||||
|
||||
fn get_detected_platform_requirements(ctx: Context) -> String {
|
||||
case ctx.platform {
|
||||
web.Windows -> get_system_requirements(ctx, Windows)
|
||||
web.MacOS -> get_system_requirements(ctx, MacOS)
|
||||
web.Linux -> get_system_requirements(ctx, Linux)
|
||||
web.IOS -> get_system_requirements(ctx, IOS)
|
||||
web.Android -> get_system_requirements(ctx, Android)
|
||||
web.Unknown -> ""
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_with_overlay(ctx: Context) -> Element(a) {
|
||||
let i18n_ctx = i18n.get_context(ctx.i18n_db, ctx.locale)
|
||||
let requirements = get_detected_platform_requirements(ctx)
|
||||
|
||||
let button = case ctx.platform {
|
||||
web.Windows ->
|
||||
render_desktop_button(ctx, Windows, Light, None, False, False)
|
||||
web.MacOS -> render_desktop_button(ctx, MacOS, Light, None, False, False)
|
||||
web.Linux -> render_desktop_button(ctx, Linux, Light, None, False, False)
|
||||
web.IOS -> render_mobile_redirect_button(ctx, Light)
|
||||
web.Android -> render_mobile_redirect_button(ctx, Light)
|
||||
web.Unknown ->
|
||||
html.a(
|
||||
[
|
||||
attribute.href(web.prepend_base_path(ctx, "/download")),
|
||||
attribute.class(
|
||||
"inline-flex items-center justify-center gap-3 rounded-2xl "
|
||||
<> light_bg
|
||||
<> " px-8 py-5 md:px-10 md:py-6 text-lg md:text-xl font-semibold "
|
||||
<> light_text
|
||||
<> " transition-colors hover:bg-white/90 shadow-lg",
|
||||
),
|
||||
],
|
||||
[
|
||||
icons.download([attribute.class("h-6 w-6 shrink-0")]),
|
||||
html.span([], [html.text(g_(i18n_ctx, "Download Fluxer"))]),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
case requirements {
|
||||
"" -> button
|
||||
req ->
|
||||
html.div([attribute.class("relative")], [
|
||||
button,
|
||||
html.p(
|
||||
[
|
||||
attribute.class(
|
||||
"absolute left-1/2 -translate-x-1/2 top-full mt-2 text-xs text-white/50 text-center whitespace-nowrap",
|
||||
),
|
||||
],
|
||||
[html.text(req)],
|
||||
),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
fn render_mobile_redirect_button(ctx: Context, style: ButtonStyle) -> Element(a) {
|
||||
let i18n_ctx = i18n.get_context(ctx.i18n_db, ctx.locale)
|
||||
let #(btn_class, helper_class) = get_mobile_btn_classes(style)
|
||||
|
||||
html.a(
|
||||
[
|
||||
attribute.class(btn_class),
|
||||
attribute.href(web.prepend_base_path(ctx, "/download")),
|
||||
],
|
||||
[
|
||||
html.div([attribute.class("flex items-center gap-3")], [
|
||||
icons.download([attribute.class("h-6 w-6 shrink-0")]),
|
||||
html.span([attribute.class("text-base md:text-lg font-semibold")], [
|
||||
html.text(g_(i18n_ctx, "Mobile apps are underway")),
|
||||
]),
|
||||
]),
|
||||
html.span([attribute.class(helper_class)], [
|
||||
html.text(g_(i18n_ctx, "Use Fluxer in your mobile browser for now")),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
fn linux_download_options(ctx: Context) -> List(#(String, String, String)) {
|
||||
[
|
||||
#("x64", "AppImage", desktop_redirect_url(ctx, "linux", "x64", "appimage")),
|
||||
#(
|
||||
"arm64",
|
||||
"AppImage",
|
||||
desktop_redirect_url(ctx, "linux", "arm64", "appimage"),
|
||||
),
|
||||
#("x64", "DEB", desktop_redirect_url(ctx, "linux", "x64", "deb")),
|
||||
#("arm64", "DEB", desktop_redirect_url(ctx, "linux", "arm64", "deb")),
|
||||
#("x64", "RPM", desktop_redirect_url(ctx, "linux", "x64", "rpm")),
|
||||
#("arm64", "RPM", desktop_redirect_url(ctx, "linux", "arm64", "rpm")),
|
||||
#("x64", "tar.gz", desktop_redirect_url(ctx, "linux", "x64", "tar_gz")),
|
||||
#("arm64", "tar.gz", desktop_redirect_url(ctx, "linux", "arm64", "tar_gz")),
|
||||
]
|
||||
}
|
||||
|
||||
fn get_platform_config(
|
||||
ctx: Context,
|
||||
platform: Platform,
|
||||
i18n_ctx: I18nContext,
|
||||
) -> #(String, String, Element(a), List(#(String, String, String))) {
|
||||
case platform {
|
||||
Windows -> #(
|
||||
"windows",
|
||||
g_(i18n_ctx, "Windows"),
|
||||
icons.windows([attribute.class("h-6 w-6 shrink-0")]),
|
||||
[
|
||||
#("x64", "EXE", desktop_redirect_url(ctx, "win32", "x64", "setup")),
|
||||
#("arm64", "EXE", desktop_redirect_url(ctx, "win32", "arm64", "setup")),
|
||||
],
|
||||
)
|
||||
MacOS -> #(
|
||||
"macos",
|
||||
g_(i18n_ctx, "macOS"),
|
||||
icons.apple([attribute.class("h-6 w-6 shrink-0")]),
|
||||
[
|
||||
#("arm64", "DMG", desktop_redirect_url(ctx, "darwin", "arm64", "dmg")),
|
||||
#("x64", "DMG", desktop_redirect_url(ctx, "darwin", "x64", "dmg")),
|
||||
],
|
||||
)
|
||||
Linux -> #(
|
||||
"linux",
|
||||
g_(i18n_ctx, "Linux"),
|
||||
icons.linux([attribute.class("h-6 w-6 shrink-0")]),
|
||||
linux_download_options(ctx),
|
||||
)
|
||||
_ -> #("", "", element.none(), [])
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_desktop_button(
|
||||
ctx: Context,
|
||||
platform: Platform,
|
||||
style: ButtonStyle,
|
||||
id_prefix: Option(String),
|
||||
compact: Bool,
|
||||
full_width: Bool,
|
||||
) -> Element(a) {
|
||||
let i18n_ctx = i18n.get_context(ctx.i18n_db, ctx.locale)
|
||||
|
||||
let #(base_platform_id, platform_name, icon, options) =
|
||||
get_platform_config(ctx, platform, i18n_ctx)
|
||||
|
||||
let platform_id = case id_prefix {
|
||||
Some(prefix) -> prefix <> "-" <> base_platform_id
|
||||
None -> base_platform_id
|
||||
}
|
||||
|
||||
let default_arch = default_architecture(ctx, platform)
|
||||
|
||||
let #(default_arch_label, default_fmt, default_url) =
|
||||
list.find(options, fn(opt) {
|
||||
let #(arch, _, _) = opt
|
||||
arch == default_arch
|
||||
})
|
||||
|> fn(r) {
|
||||
case r {
|
||||
Ok(opt) -> opt
|
||||
Error(_) ->
|
||||
case options {
|
||||
[first, ..] -> first
|
||||
_ -> #("", "", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let helper_text = case platform {
|
||||
Linux -> g_(i18n_ctx, "Choose distribution")
|
||||
_ -> format_arch_label(i18n_ctx, platform, default_arch_label, default_fmt)
|
||||
}
|
||||
let #(btn_class, chevron_class, helper_class) = get_desktop_btn_classes(style)
|
||||
|
||||
let container_class = case full_width {
|
||||
True -> "flex w-full"
|
||||
False -> "flex"
|
||||
}
|
||||
|
||||
let width_modifier = case full_width {
|
||||
True -> " flex-1 w-full min-w-0"
|
||||
False -> ""
|
||||
}
|
||||
|
||||
let button_class = btn_class <> width_modifier
|
||||
|
||||
let button_label = case compact {
|
||||
True -> platform_name
|
||||
False -> g_(i18n_ctx, "Download for ") <> platform_name
|
||||
}
|
||||
|
||||
let overlay_items =
|
||||
options
|
||||
|> list.map(fn(opt) {
|
||||
let #(arch, fmt, url) = opt
|
||||
html.a(
|
||||
[
|
||||
attribute.class(
|
||||
"download-overlay-link block px-4 py-3 text-sm text-gray-900 hover:bg-gray-100 transition-colors",
|
||||
),
|
||||
attribute.attribute("data-base-url", url),
|
||||
attribute.attribute("data-arch", arch),
|
||||
attribute.attribute("data-format", fmt),
|
||||
attribute.attribute("data-platform", platform_id),
|
||||
attribute.href(url),
|
||||
],
|
||||
[html.text(format_overlay_label(i18n_ctx, platform, arch, fmt))],
|
||||
)
|
||||
})
|
||||
|
||||
html.div([attribute.class("relative")], [
|
||||
html.div(
|
||||
[
|
||||
attribute.class(container_class),
|
||||
attribute.id(platform_id <> "-download-buttons"),
|
||||
],
|
||||
[
|
||||
html.a(
|
||||
[
|
||||
attribute.class(button_class),
|
||||
attribute.href(default_url),
|
||||
attribute.attribute("data-base-url", default_url),
|
||||
attribute.attribute("data-arch", default_arch_label),
|
||||
attribute.attribute("data-format", default_fmt),
|
||||
attribute.attribute("data-platform", platform_id),
|
||||
],
|
||||
[
|
||||
html.div([attribute.class("flex items-center gap-3")], [
|
||||
icon,
|
||||
html.span(
|
||||
[attribute.class("text-base md:text-lg font-semibold")],
|
||||
[
|
||||
html.text(button_label),
|
||||
],
|
||||
),
|
||||
]),
|
||||
html.span([attribute.class(helper_class)], [
|
||||
html.text(helper_text),
|
||||
]),
|
||||
],
|
||||
),
|
||||
html.button(
|
||||
[
|
||||
attribute.class(chevron_class),
|
||||
attribute.attribute(
|
||||
"data-overlay-target",
|
||||
platform_id <> "-overlay",
|
||||
),
|
||||
attribute.attribute(
|
||||
"aria-label",
|
||||
g_(i18n_ctx, "Show download options"),
|
||||
),
|
||||
attribute.attribute("type", "button"),
|
||||
],
|
||||
[icons.caret_down([attribute.class("h-5 w-5")])],
|
||||
),
|
||||
],
|
||||
),
|
||||
html.div(
|
||||
[
|
||||
attribute.id(platform_id <> "-overlay"),
|
||||
attribute.class(
|
||||
"download-overlay absolute left-0 z-20 mt-2 w-full min-w-[220px] rounded-xl bg-white shadow-xl border border-gray-200 hidden",
|
||||
),
|
||||
],
|
||||
[html.div([attribute.class("py-1")], overlay_items)],
|
||||
),
|
||||
])
|
||||
}
|
||||
|
||||
pub fn render_mobile_button(
|
||||
ctx: Context,
|
||||
platform: Platform,
|
||||
style: ButtonStyle,
|
||||
) -> Element(a) {
|
||||
let i18n_ctx = i18n.get_context(ctx.i18n_db, ctx.locale)
|
||||
let #(platform_name, icon, url, helper_text) = case platform {
|
||||
IOS -> #(
|
||||
g_(i18n_ctx, "iOS"),
|
||||
icons.apple([attribute.class("h-6 w-6 shrink-0")]),
|
||||
web.api_url(ctx, "/dl/ios/testflight"),
|
||||
"TestFlight",
|
||||
)
|
||||
Android -> #(
|
||||
g_(i18n_ctx, "Android"),
|
||||
icons.android([attribute.class("h-6 w-6 shrink-0")]),
|
||||
web.api_url(ctx, "/dl/android/arm64/apk"),
|
||||
"APK",
|
||||
)
|
||||
_ -> #("", element.none(), "", "")
|
||||
}
|
||||
|
||||
let #(btn_class, helper_class) = get_mobile_btn_classes(style)
|
||||
|
||||
html.a([attribute.class(btn_class), attribute.href(url)], [
|
||||
html.div([attribute.class("flex items-center gap-3")], [
|
||||
icon,
|
||||
html.span([attribute.class("text-base md:text-lg font-semibold")], [
|
||||
html.text(g_(i18n_ctx, "Download for ") <> platform_name),
|
||||
]),
|
||||
]),
|
||||
html.span([attribute.class(helper_class)], [html.text(helper_text)]),
|
||||
])
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import fluxer_marketing/i18n
|
||||
import fluxer_marketing/icons
|
||||
import fluxer_marketing/number_format
|
||||
import fluxer_marketing/web.{type Context, href}
|
||||
import gleam/string
|
||||
import kielet.{gettext as g_}
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/html
|
||||
|
||||
pub fn render(ctx: Context) -> Element(a) {
|
||||
let i18n_ctx = i18n.get_context(ctx.i18n_db, ctx.locale)
|
||||
let slots = ctx.visionary_slots
|
||||
let remaining_text = number_format.format_number(slots.remaining)
|
||||
let total_text = number_format.format_number(slots.total)
|
||||
|
||||
let headline = case slots.total {
|
||||
0 ->
|
||||
g_(
|
||||
i18n_ctx,
|
||||
"Lifetime Plutonium + Operator Pass with Visionary — limited slots",
|
||||
)
|
||||
_ ->
|
||||
g_(
|
||||
i18n_ctx,
|
||||
"Only {0} of {1} Visionary lifetime slots left — lifetime Plutonium + Operator Pass",
|
||||
)
|
||||
|> string.replace("{0}", remaining_text)
|
||||
|> string.replace("{1}", total_text)
|
||||
}
|
||||
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"fixed top-0 left-0 right-0 z-30 bg-gradient-to-r from-black to-gray-900 text-white",
|
||||
),
|
||||
],
|
||||
[
|
||||
html.div([attribute.class("mx-auto max-w-7xl px-4 py-3 md:py-2.5")], [
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"flex flex-col sm:flex-row items-center justify-center gap-2 sm:gap-3 text-xs sm:text-sm font-medium md:text-base text-center sm:text-left",
|
||||
),
|
||||
],
|
||||
[
|
||||
html.div([attribute.class("flex items-center gap-2 sm:gap-3")], [
|
||||
icons.infinity([
|
||||
attribute.class("hidden sm:block h-5 w-5 flex-shrink-0"),
|
||||
]),
|
||||
html.span([attribute.class("leading-tight")], [
|
||||
html.text(headline),
|
||||
]),
|
||||
]),
|
||||
html.a(
|
||||
[
|
||||
href(ctx, "/plutonium#visionary"),
|
||||
attribute.class(
|
||||
"rounded-lg bg-white px-3 py-1.5 sm:px-4 text-xs sm:text-sm font-semibold text-black hover:bg-gray-100 transition-colors whitespace-nowrap",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Get Visionary"))],
|
||||
),
|
||||
],
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import fluxer_marketing/i18n
|
||||
import fluxer_marketing/icons
|
||||
import fluxer_marketing/pricing
|
||||
import fluxer_marketing/web.{type Context, href}
|
||||
import kielet.{gettext as g_}
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/html
|
||||
|
||||
pub fn render(ctx: Context) -> Element(a) {
|
||||
let i18n_ctx = i18n.get_context(ctx.i18n_db, ctx.locale)
|
||||
|
||||
let monthly_price =
|
||||
pricing.get_formatted_price(pricing.Monthly, ctx.country_code)
|
||||
let yearly_price =
|
||||
pricing.get_formatted_price(pricing.Yearly, ctx.country_code)
|
||||
|
||||
html.section(
|
||||
[
|
||||
attribute.class(
|
||||
"bg-gradient-to-b from-white to-gray-50 px-6 py-24 md:py-40",
|
||||
),
|
||||
],
|
||||
[
|
||||
html.div([attribute.class("mx-auto max-w-7xl")], [
|
||||
html.div([attribute.class("mb-16 md:mb-20 text-center")], [
|
||||
html.h2(
|
||||
[
|
||||
attribute.class(
|
||||
"display mb-8 md:mb-10 text-black text-4xl md:text-5xl lg:text-6xl",
|
||||
),
|
||||
],
|
||||
[
|
||||
html.text(g_(i18n_ctx, "Get more with Fluxer Plutonium")),
|
||||
],
|
||||
),
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"flex flex-col sm:flex-row items-center justify-center gap-3 mb-6",
|
||||
),
|
||||
],
|
||||
[
|
||||
html.span(
|
||||
[attribute.class("text-3xl md:text-4xl font-bold text-black")],
|
||||
[
|
||||
html.text(
|
||||
monthly_price
|
||||
<> g_(i18n_ctx, "/mo")
|
||||
<> " "
|
||||
<> g_(i18n_ctx, "or")
|
||||
<> " "
|
||||
<> yearly_price
|
||||
<> g_(i18n_ctx, "/yr"),
|
||||
),
|
||||
],
|
||||
),
|
||||
html.span(
|
||||
[
|
||||
attribute.class(
|
||||
"inline-flex items-center rounded-xl bg-[#4641D9] px-4 py-2 text-sm md:text-base font-semibold text-white",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Save 17%"))],
|
||||
),
|
||||
],
|
||||
),
|
||||
html.p(
|
||||
[
|
||||
attribute.class("lead mx-auto max-w-2xl text-gray-700"),
|
||||
],
|
||||
[
|
||||
html.text(g_(
|
||||
i18n_ctx,
|
||||
"Higher limits, exclusive features, and early access to new updates",
|
||||
)),
|
||||
],
|
||||
),
|
||||
]),
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"mb-16 md:mb-20 grid grid-cols-1 md:grid-cols-3 gap-8 md:gap-12 max-w-4xl mx-auto",
|
||||
),
|
||||
],
|
||||
[
|
||||
html.div(
|
||||
[attribute.class("flex flex-col items-center text-center")],
|
||||
[
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"inline-flex items-center justify-center w-20 h-20 md:w-24 md:h-24 rounded-3xl bg-gradient-to-br from-[#4641D9]/10 to-[#4641D9]/5 mb-4",
|
||||
),
|
||||
],
|
||||
[
|
||||
icons.hash([
|
||||
attribute.class(
|
||||
"h-10 w-10 md:h-12 md:w-12 text-[#4641D9]",
|
||||
),
|
||||
]),
|
||||
],
|
||||
),
|
||||
html.h3(
|
||||
[
|
||||
attribute.class(
|
||||
"title text-black text-lg md:text-xl whitespace-nowrap",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Custom identity"))],
|
||||
),
|
||||
],
|
||||
),
|
||||
html.div(
|
||||
[attribute.class("flex flex-col items-center text-center")],
|
||||
[
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"inline-flex items-center justify-center w-20 h-20 md:w-24 md:h-24 rounded-3xl bg-gradient-to-br from-[#4641D9]/10 to-[#4641D9]/5 mb-4",
|
||||
),
|
||||
],
|
||||
[
|
||||
icons.video_camera([
|
||||
attribute.class(
|
||||
"h-10 w-10 md:h-12 md:w-12 text-[#4641D9]",
|
||||
),
|
||||
]),
|
||||
],
|
||||
),
|
||||
html.h3(
|
||||
[
|
||||
attribute.class(
|
||||
"title text-black text-lg md:text-xl whitespace-nowrap",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Premium quality"))],
|
||||
),
|
||||
],
|
||||
),
|
||||
html.div(
|
||||
[attribute.class("flex flex-col items-center text-center")],
|
||||
[
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"inline-flex items-center justify-center w-20 h-20 md:w-24 md:h-24 rounded-3xl bg-gradient-to-br from-[#4641D9]/10 to-[#4641D9]/5 mb-4",
|
||||
),
|
||||
],
|
||||
[
|
||||
icons.sparkle([
|
||||
attribute.class(
|
||||
"h-10 w-10 md:h-12 md:w-12 text-[#4641D9]",
|
||||
),
|
||||
]),
|
||||
],
|
||||
),
|
||||
html.h3(
|
||||
[
|
||||
attribute.class(
|
||||
"title text-black text-lg md:text-xl whitespace-nowrap",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Exclusive features"))],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
html.div([attribute.class("text-center")], [
|
||||
html.a(
|
||||
[
|
||||
href(ctx, "/plutonium"),
|
||||
attribute.class(
|
||||
"label inline-block rounded-xl bg-[#4641D9] px-10 py-5 md:px-12 md:py-6 text-lg md:text-xl text-white transition hover:bg-opacity-90 shadow-lg",
|
||||
),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Learn more"))],
|
||||
),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,408 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import fluxer_marketing/i18n
|
||||
import fluxer_marketing/icons
|
||||
import fluxer_marketing/web.{type Context}
|
||||
import kielet.{gettext as g_}
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/html
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn render_trigger(ctx: Context) -> Element(a) {
|
||||
let i18n_ctx = i18n.get_context(ctx.i18n_db, ctx.locale)
|
||||
|
||||
html.button(
|
||||
[
|
||||
attribute.id("pwa-install-button"),
|
||||
attribute.class(
|
||||
"inline-flex items-center gap-2 rounded-xl px-5 py-3 transition-colors bg-white/10 hover:bg-white/20 text-white font-medium text-sm",
|
||||
),
|
||||
],
|
||||
[
|
||||
icons.devices([attribute.class("h-5 w-5")]),
|
||||
html.text(g_(i18n_ctx, "How to install as an app")),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn render_modal(ctx: Context) -> Element(a) {
|
||||
let i18n_ctx = i18n.get_context(ctx.i18n_db, ctx.locale)
|
||||
|
||||
html.div(
|
||||
[
|
||||
attribute.id("pwa-modal-backdrop"),
|
||||
attribute.class("pwa-modal-backdrop"),
|
||||
],
|
||||
[
|
||||
html.div([attribute.class("pwa-modal")], [
|
||||
html.div([attribute.class("flex flex-col h-full")], [
|
||||
html.div(
|
||||
[attribute.class("flex items-center justify-between p-6 pb-4")],
|
||||
[
|
||||
html.h2([attribute.class("text-xl font-bold text-gray-900")], [
|
||||
html.text(g_(i18n_ctx, "Install Fluxer as an app")),
|
||||
]),
|
||||
html.button(
|
||||
[
|
||||
attribute.class(
|
||||
"p-2 hover:bg-gray-100 rounded-lg text-gray-600 hover:text-gray-900",
|
||||
),
|
||||
attribute.id("pwa-close"),
|
||||
attribute.attribute("aria-label", "Close"),
|
||||
],
|
||||
[
|
||||
svg.svg(
|
||||
[
|
||||
attribute.class("w-5 h-5"),
|
||||
attribute.attribute("fill", "none"),
|
||||
attribute.attribute("stroke", "currentColor"),
|
||||
attribute.attribute("viewBox", "0 0 24 24"),
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute("stroke-linecap", "round"),
|
||||
attribute.attribute("stroke-linejoin", "round"),
|
||||
attribute.attribute("stroke-width", "2"),
|
||||
attribute.attribute("d", "M6 18L18 6M6 6l12 12"),
|
||||
]),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
html.div([attribute.class("px-6")], [
|
||||
html.div(
|
||||
[
|
||||
attribute.class("flex gap-1 p-1 bg-gray-100 rounded-xl"),
|
||||
attribute.id("pwa-tabs"),
|
||||
],
|
||||
[
|
||||
tab_button("android", g_(i18n_ctx, "Android"), True),
|
||||
tab_button("ios", g_(i18n_ctx, "iOS / iPadOS"), False),
|
||||
tab_button("desktop", g_(i18n_ctx, "Desktop"), False),
|
||||
],
|
||||
),
|
||||
]),
|
||||
html.div([attribute.class("flex-1 overflow-y-auto p-6 pt-4")], [
|
||||
html.div(
|
||||
[
|
||||
attribute.id("pwa-panel-android"),
|
||||
attribute.class("pwa-panel"),
|
||||
],
|
||||
[render_android_steps(ctx)],
|
||||
),
|
||||
html.div(
|
||||
[
|
||||
attribute.id("pwa-panel-ios"),
|
||||
attribute.class("pwa-panel hidden"),
|
||||
],
|
||||
[render_ios_steps(ctx)],
|
||||
),
|
||||
html.div(
|
||||
[
|
||||
attribute.id("pwa-panel-desktop"),
|
||||
attribute.class("pwa-panel hidden"),
|
||||
],
|
||||
[render_desktop_steps(ctx)],
|
||||
),
|
||||
]),
|
||||
html.div(
|
||||
[
|
||||
attribute.class("px-6 py-4 border-t border-gray-100 text-center"),
|
||||
],
|
||||
[
|
||||
html.p([attribute.class("text-xs text-gray-400")], [
|
||||
html.text(g_(i18n_ctx, "Screenshots courtesy of ")),
|
||||
html.a(
|
||||
[
|
||||
attribute.href("https://installpwa.com/"),
|
||||
attribute.target("_blank"),
|
||||
attribute.rel("noopener noreferrer"),
|
||||
attribute.class(
|
||||
"text-blue-500 hover:text-blue-600 underline",
|
||||
),
|
||||
],
|
||||
[html.text("installpwa.com")],
|
||||
),
|
||||
]),
|
||||
],
|
||||
),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
fn tab_button(id: String, label: String, active: Bool) -> Element(a) {
|
||||
html.button(
|
||||
[
|
||||
attribute.attribute("data-tab", id),
|
||||
attribute.class(
|
||||
"pwa-tab flex-1 px-4 py-2 text-sm font-medium rounded-lg transition-colors "
|
||||
<> case active {
|
||||
True -> "bg-white text-gray-900 shadow-sm"
|
||||
False -> "text-gray-600 hover:text-gray-900"
|
||||
},
|
||||
),
|
||||
],
|
||||
[html.text(label)],
|
||||
)
|
||||
}
|
||||
|
||||
fn render_android_steps(ctx: Context) -> Element(a) {
|
||||
let i18n_ctx = i18n.get_context(ctx.i18n_db, ctx.locale)
|
||||
|
||||
html.div([attribute.class("flex flex-col md:flex-row gap-6")], [
|
||||
html.div([attribute.class("md:w-1/3 flex justify-center")], [
|
||||
render_image(ctx, "android", "240", "320", "480"),
|
||||
]),
|
||||
html.div([attribute.class("md:w-2/3")], [
|
||||
html.ol([attribute.class("space-y-4")], [
|
||||
step_item(
|
||||
"1",
|
||||
html.span([], [
|
||||
html.a(
|
||||
[
|
||||
attribute.href("https://web.fluxer.app"),
|
||||
attribute.target("_blank"),
|
||||
attribute.rel("noopener noreferrer"),
|
||||
attribute.class("text-blue-600 hover:text-blue-700 underline"),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Open the web app"))],
|
||||
),
|
||||
html.text(g_(i18n_ctx, " in Chrome")),
|
||||
]),
|
||||
),
|
||||
step_item(
|
||||
"2",
|
||||
html.text(g_(
|
||||
i18n_ctx,
|
||||
"Press the \"More\" (\u{22EE}) button in the top-right corner",
|
||||
)),
|
||||
),
|
||||
step_item("3", html.text(g_(i18n_ctx, "Press \"Install app\""))),
|
||||
step_item(
|
||||
"4",
|
||||
html.span([attribute.class("text-green-600 font-medium")], [
|
||||
html.text(g_(
|
||||
i18n_ctx,
|
||||
"Done! You can open Fluxer from your home screen.",
|
||||
)),
|
||||
]),
|
||||
),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
}
|
||||
|
||||
fn render_ios_steps(ctx: Context) -> Element(a) {
|
||||
let i18n_ctx = i18n.get_context(ctx.i18n_db, ctx.locale)
|
||||
|
||||
html.div([attribute.class("flex flex-col md:flex-row gap-6")], [
|
||||
html.div([attribute.class("md:w-1/2 flex justify-center")], [
|
||||
render_image(ctx, "ios", "320", "480", "640"),
|
||||
]),
|
||||
html.div([attribute.class("md:w-1/2")], [
|
||||
html.ol([attribute.class("space-y-4")], [
|
||||
step_item(
|
||||
"1",
|
||||
html.span([], [
|
||||
html.a(
|
||||
[
|
||||
attribute.href("https://web.fluxer.app"),
|
||||
attribute.target("_blank"),
|
||||
attribute.rel("noopener noreferrer"),
|
||||
attribute.class("text-blue-600 hover:text-blue-700 underline"),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Open the web app"))],
|
||||
),
|
||||
html.text(g_(i18n_ctx, " in Safari")),
|
||||
]),
|
||||
),
|
||||
step_item(
|
||||
"2",
|
||||
html.text(g_(
|
||||
i18n_ctx,
|
||||
"Press the Share button (rectangle with upwards-pointing arrow)",
|
||||
)),
|
||||
),
|
||||
step_item("3", html.text(g_(i18n_ctx, "Press \"Add to Home Screen\""))),
|
||||
step_item(
|
||||
"4",
|
||||
html.text(g_(i18n_ctx, "Press \"Add\" in the upper-right corner")),
|
||||
),
|
||||
step_item(
|
||||
"5",
|
||||
html.span([attribute.class("text-green-600 font-medium")], [
|
||||
html.text(g_(
|
||||
i18n_ctx,
|
||||
"Done! You can open Fluxer from your home screen.",
|
||||
)),
|
||||
]),
|
||||
),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
}
|
||||
|
||||
fn render_desktop_steps(ctx: Context) -> Element(a) {
|
||||
let i18n_ctx = i18n.get_context(ctx.i18n_db, ctx.locale)
|
||||
|
||||
html.div([attribute.class("flex flex-col md:flex-row gap-6")], [
|
||||
html.div([attribute.class("md:w-1/2 flex justify-center")], [
|
||||
render_image(ctx, "desktop", "320", "480", "640"),
|
||||
]),
|
||||
html.div([attribute.class("md:w-1/2")], [
|
||||
html.ol([attribute.class("space-y-4")], [
|
||||
step_item(
|
||||
"1",
|
||||
html.span([], [
|
||||
html.a(
|
||||
[
|
||||
attribute.href("https://web.fluxer.app"),
|
||||
attribute.target("_blank"),
|
||||
attribute.rel("noopener noreferrer"),
|
||||
attribute.class("text-blue-600 hover:text-blue-700 underline"),
|
||||
],
|
||||
[html.text(g_(i18n_ctx, "Open the web app"))],
|
||||
),
|
||||
html.text(g_(
|
||||
i18n_ctx,
|
||||
" in Chrome or another browser with PWA support",
|
||||
)),
|
||||
]),
|
||||
),
|
||||
step_item(
|
||||
"2",
|
||||
html.text(g_(
|
||||
i18n_ctx,
|
||||
"Press the install button (downwards-pointing arrow on monitor) in the address bar",
|
||||
)),
|
||||
),
|
||||
step_item(
|
||||
"3",
|
||||
html.text(g_(i18n_ctx, "Press \"Install\" in the popup that appears")),
|
||||
),
|
||||
step_item(
|
||||
"4",
|
||||
html.span([attribute.class("text-green-600 font-medium")], [
|
||||
html.text(g_(
|
||||
i18n_ctx,
|
||||
"Done! You can now open Fluxer as if it were a regular program.",
|
||||
)),
|
||||
]),
|
||||
),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
}
|
||||
|
||||
fn step_item(number: String, content: Element(a)) -> Element(a) {
|
||||
html.li([attribute.class("flex gap-4")], [
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"flex-shrink-0 w-8 h-8 rounded-full bg-blue-100 text-blue-600 flex items-center justify-center font-bold text-sm",
|
||||
),
|
||||
],
|
||||
[html.text(number)],
|
||||
),
|
||||
html.div([attribute.class("pt-1 text-gray-700")], [content]),
|
||||
])
|
||||
}
|
||||
|
||||
fn render_image(
|
||||
ctx: Context,
|
||||
name: String,
|
||||
small: String,
|
||||
medium: String,
|
||||
large: String,
|
||||
) -> Element(a) {
|
||||
let base_path = ctx.cdn_endpoint <> "/marketing/pwa-install/" <> name
|
||||
|
||||
element.element("picture", [], [
|
||||
element.element(
|
||||
"source",
|
||||
[
|
||||
attribute.type_("image/avif"),
|
||||
attribute.attribute(
|
||||
"srcset",
|
||||
base_path
|
||||
<> "-"
|
||||
<> small
|
||||
<> "w.avif 1x, "
|
||||
<> base_path
|
||||
<> "-"
|
||||
<> medium
|
||||
<> "w.avif 1.5x, "
|
||||
<> base_path
|
||||
<> "-"
|
||||
<> large
|
||||
<> "w.avif 2x",
|
||||
),
|
||||
],
|
||||
[],
|
||||
),
|
||||
element.element(
|
||||
"source",
|
||||
[
|
||||
attribute.type_("image/webp"),
|
||||
attribute.attribute(
|
||||
"srcset",
|
||||
base_path
|
||||
<> "-"
|
||||
<> small
|
||||
<> "w.webp 1x, "
|
||||
<> base_path
|
||||
<> "-"
|
||||
<> medium
|
||||
<> "w.webp 1.5x, "
|
||||
<> base_path
|
||||
<> "-"
|
||||
<> large
|
||||
<> "w.webp 2x",
|
||||
),
|
||||
],
|
||||
[],
|
||||
),
|
||||
html.img([
|
||||
attribute.src(base_path <> "-" <> medium <> "w.png"),
|
||||
attribute.attribute(
|
||||
"srcset",
|
||||
base_path
|
||||
<> "-"
|
||||
<> small
|
||||
<> "w.png 1x, "
|
||||
<> base_path
|
||||
<> "-"
|
||||
<> medium
|
||||
<> "w.png 1.5x, "
|
||||
<> base_path
|
||||
<> "-"
|
||||
<> large
|
||||
<> "w.png 2x",
|
||||
),
|
||||
attribute.alt("PWA installation guide for " <> name),
|
||||
attribute.class(
|
||||
"max-w-full h-auto rounded-lg shadow-lg border border-gray-200",
|
||||
),
|
||||
]),
|
||||
])
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import fluxer_marketing/icons
|
||||
import fluxer_marketing/web.{type Context}
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/html
|
||||
|
||||
pub fn render(
|
||||
_ctx: Context,
|
||||
icon: String,
|
||||
title: String,
|
||||
description: String,
|
||||
button_text: String,
|
||||
button_href: String,
|
||||
) -> Element(a) {
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"flex h-full flex-col rounded-3xl bg-white/95 backdrop-blur-sm p-10 md:p-12 shadow-lg border border-white/50",
|
||||
),
|
||||
],
|
||||
[
|
||||
html.div([attribute.class("mb-8 text-center")], [
|
||||
html.div(
|
||||
[
|
||||
attribute.class(
|
||||
"inline-flex items-center justify-center w-24 h-24 md:w-28 md:h-28 rounded-3xl bg-[#4641D9] mb-6",
|
||||
),
|
||||
],
|
||||
[
|
||||
case icon {
|
||||
"rocket-launch" ->
|
||||
icons.rocket_launch([
|
||||
attribute.class("h-12 w-12 md:h-14 md:w-14 text-white"),
|
||||
])
|
||||
"fluxer-partner" ->
|
||||
icons.fluxer_partner([
|
||||
attribute.class("h-12 w-12 md:h-14 md:w-14 text-white"),
|
||||
])
|
||||
"chat-centered-text" ->
|
||||
icons.chat_centered_text([
|
||||
attribute.class("h-12 w-12 md:h-14 md:w-14 text-white"),
|
||||
])
|
||||
"bluesky" ->
|
||||
icons.bluesky([
|
||||
attribute.class("h-12 w-12 md:h-14 md:w-14 text-white"),
|
||||
])
|
||||
"bug" ->
|
||||
icons.bug([
|
||||
attribute.class("h-12 w-12 md:h-14 md:w-14 text-white"),
|
||||
])
|
||||
"code" ->
|
||||
icons.code_icon([
|
||||
attribute.class("h-12 w-12 md:h-14 md:w-14 text-white"),
|
||||
])
|
||||
"translate" ->
|
||||
icons.translate([
|
||||
attribute.class("h-12 w-12 md:h-14 md:w-14 text-white"),
|
||||
])
|
||||
"shield-check" ->
|
||||
icons.shield_check([
|
||||
attribute.class("h-12 w-12 md:h-14 md:w-14 text-white"),
|
||||
])
|
||||
_ -> html.div([], [])
|
||||
},
|
||||
],
|
||||
),
|
||||
html.h3(
|
||||
[attribute.class("title text-gray-900 mb-4 text-xl md:text-2xl")],
|
||||
[
|
||||
html.text(title),
|
||||
],
|
||||
),
|
||||
html.p([attribute.class("body-lg text-gray-700 leading-relaxed")], [
|
||||
html.text(description),
|
||||
]),
|
||||
]),
|
||||
html.div([attribute.class("mt-auto flex flex-col items-center")], [
|
||||
html.a(
|
||||
case button_href {
|
||||
"https://" <> _ | "http://" <> _ | "mailto:" <> _ -> [
|
||||
attribute.href(button_href),
|
||||
attribute.class(
|
||||
"label inline-block rounded-xl bg-[#4641D9] px-8 py-4 text-base md:text-lg text-white transition-colors hover:bg-opacity-90 shadow-md w-full text-center",
|
||||
),
|
||||
attribute.target("_blank"),
|
||||
attribute.rel("noopener noreferrer"),
|
||||
]
|
||||
_ -> [
|
||||
attribute.href(button_href),
|
||||
attribute.class(
|
||||
"label inline-block rounded-xl bg-[#4641D9] px-8 py-4 text-base md:text-lg text-white transition-colors hover:bg-opacity-90 shadow-md w-full text-center",
|
||||
),
|
||||
]
|
||||
},
|
||||
[html.text(button_text)],
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
134
fluxer_marketing/src/fluxer_marketing/config.gleam
Normal file
134
fluxer_marketing/src/fluxer_marketing/config.gleam
Normal file
@@ -0,0 +1,134 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import envoy
|
||||
import gleam/int
|
||||
import gleam/list
|
||||
import gleam/option
|
||||
import gleam/result
|
||||
import gleam/string
|
||||
|
||||
pub type Config {
|
||||
Config(
|
||||
secret_key_base: String,
|
||||
api_endpoint: String,
|
||||
api_host: String,
|
||||
app_endpoint: String,
|
||||
cdn_endpoint: String,
|
||||
marketing_endpoint: String,
|
||||
port: Int,
|
||||
base_path: String,
|
||||
geoip_host: String,
|
||||
build_timestamp: String,
|
||||
release_channel: String,
|
||||
gateway_rpc_secret: String,
|
||||
metrics_endpoint: option.Option(String),
|
||||
)
|
||||
}
|
||||
|
||||
fn normalize_base_path(base_path: String) -> String {
|
||||
let segments =
|
||||
base_path
|
||||
|> string.trim
|
||||
|> string.split("/")
|
||||
|> list.filter(fn(segment) { segment != "" })
|
||||
|
||||
case segments {
|
||||
[] -> ""
|
||||
_ -> "/" <> string.join(segments, "/")
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_endpoint(endpoint: String) -> String {
|
||||
let len = string.length(endpoint)
|
||||
case len > 0 && string.ends_with(endpoint, "/") {
|
||||
True -> normalize_endpoint(string.slice(endpoint, 0, len - 1))
|
||||
False -> endpoint
|
||||
}
|
||||
}
|
||||
|
||||
fn required_env(key: String) -> Result(String, String) {
|
||||
case envoy.get(key) {
|
||||
Ok(value) ->
|
||||
case string.trim(value) {
|
||||
"" -> Error("Missing required env: " <> key)
|
||||
trimmed -> Ok(trimmed)
|
||||
}
|
||||
Error(_) -> Error("Missing required env: " <> key)
|
||||
}
|
||||
}
|
||||
|
||||
fn optional_env(key: String) -> option.Option(String) {
|
||||
case envoy.get(key) {
|
||||
Ok(value) ->
|
||||
case string.trim(value) {
|
||||
"" -> option.None
|
||||
trimmed -> option.Some(trimmed)
|
||||
}
|
||||
Error(_) -> option.None
|
||||
}
|
||||
}
|
||||
|
||||
fn required_int_env(key: String) -> Result(Int, String) {
|
||||
use raw <- result.try(required_env(key))
|
||||
case int.parse(raw) {
|
||||
Ok(n) -> Ok(n)
|
||||
Error(_) -> Error("Invalid integer for env " <> key <> ": " <> raw)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_config() -> Result(Config, String) {
|
||||
use secret_key_base <- result.try(required_env("SECRET_KEY_BASE"))
|
||||
use api_endpoint_raw <- result.try(required_env("FLUXER_API_PUBLIC_ENDPOINT"))
|
||||
use api_host <- result.try(required_env("FLUXER_API_HOST"))
|
||||
use app_endpoint_raw <- result.try(required_env("FLUXER_APP_ENDPOINT"))
|
||||
use cdn_endpoint_raw <- result.try(required_env("FLUXER_CDN_ENDPOINT"))
|
||||
use marketing_endpoint_raw <- result.try(required_env(
|
||||
"FLUXER_MARKETING_ENDPOINT",
|
||||
))
|
||||
use base_path_raw <- result.try(required_env("FLUXER_PATH_MARKETING"))
|
||||
use geoip_host <- result.try(required_env("GEOIP_HOST"))
|
||||
use port <- result.try(required_int_env("FLUXER_MARKETING_PORT"))
|
||||
use release_channel <- result.try(required_env("RELEASE_CHANNEL"))
|
||||
use gateway_rpc_secret <- result.try(required_env("GATEWAY_RPC_SECRET"))
|
||||
|
||||
let api_endpoint = normalize_endpoint(api_endpoint_raw)
|
||||
let app_endpoint = normalize_endpoint(app_endpoint_raw)
|
||||
let cdn_endpoint = normalize_endpoint(cdn_endpoint_raw)
|
||||
let marketing_endpoint = normalize_endpoint(marketing_endpoint_raw)
|
||||
let base_path = normalize_base_path(base_path_raw)
|
||||
let metrics_endpoint = case optional_env("FLUXER_METRICS_HOST") {
|
||||
option.Some(host) -> option.Some("http://" <> host)
|
||||
option.None -> option.None
|
||||
}
|
||||
|
||||
Ok(Config(
|
||||
secret_key_base: secret_key_base,
|
||||
api_endpoint: api_endpoint,
|
||||
api_host: api_host,
|
||||
app_endpoint: app_endpoint,
|
||||
cdn_endpoint: cdn_endpoint,
|
||||
marketing_endpoint: marketing_endpoint,
|
||||
port: port,
|
||||
base_path: base_path,
|
||||
geoip_host: geoip_host,
|
||||
build_timestamp: optional_env("BUILD_TIMESTAMP") |> option.unwrap(""),
|
||||
release_channel: release_channel,
|
||||
gateway_rpc_secret: gateway_rpc_secret,
|
||||
metrics_endpoint: metrics_endpoint,
|
||||
))
|
||||
}
|
||||
42
fluxer_marketing/src/fluxer_marketing/flags.gleam
Normal file
42
fluxer_marketing/src/fluxer_marketing/flags.gleam
Normal file
@@ -0,0 +1,42 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import fluxer_marketing/locale.{type Locale}
|
||||
import fluxer_marketing/web.{type Context}
|
||||
import gleam/list
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/html
|
||||
|
||||
pub fn flag_svg(
|
||||
locale: Locale,
|
||||
ctx: Context,
|
||||
attributes: List(attribute.Attribute(a)),
|
||||
) -> Element(a) {
|
||||
let flag_code = locale.get_flag_code(locale)
|
||||
|
||||
html.img(
|
||||
[
|
||||
attribute.src(
|
||||
ctx.cdn_endpoint <> "/marketing/flags/" <> flag_code <> ".svg",
|
||||
),
|
||||
attribute.alt("Flag"),
|
||||
attribute.attribute("loading", "lazy"),
|
||||
]
|
||||
|> list.append(attributes),
|
||||
)
|
||||
}
|
||||
96
fluxer_marketing/src/fluxer_marketing/frontmatter.gleam
Normal file
96
fluxer_marketing/src/fluxer_marketing/frontmatter.gleam
Normal file
@@ -0,0 +1,96 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import gleam/dict.{type Dict}
|
||||
import gleam/int
|
||||
import gleam/list
|
||||
import gleam/option.{type Option, None, Some}
|
||||
import gleam/string
|
||||
|
||||
pub type Frontmatter {
|
||||
Frontmatter(data: Dict(String, String), content: String)
|
||||
}
|
||||
|
||||
pub fn parse(markdown: String) -> Frontmatter {
|
||||
case string.starts_with(markdown, "---\n") {
|
||||
True -> parse_with_frontmatter(markdown)
|
||||
False -> Frontmatter(data: dict.new(), content: markdown)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_with_frontmatter(markdown: String) -> Frontmatter {
|
||||
let without_first = string.drop_start(markdown, 4)
|
||||
|
||||
case string.split_once(without_first, "\n---\n") {
|
||||
Ok(#(frontmatter_text, content)) -> {
|
||||
let metadata = parse_frontmatter_text(frontmatter_text)
|
||||
Frontmatter(data: metadata, content: string.trim(content))
|
||||
}
|
||||
Error(_) -> Frontmatter(data: dict.new(), content: markdown)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_frontmatter_text(text: String) -> Dict(String, String) {
|
||||
text
|
||||
|> string.split("\n")
|
||||
|> list.filter(fn(line) { !string.is_empty(string.trim(line)) })
|
||||
|> list.filter_map(parse_frontmatter_line)
|
||||
|> dict.from_list
|
||||
}
|
||||
|
||||
fn parse_frontmatter_line(line: String) -> Result(#(String, String), Nil) {
|
||||
case string.split_once(line, ":") {
|
||||
Ok(#(key, value)) -> {
|
||||
let clean_key = string.trim(key)
|
||||
let clean_value = string.trim(value)
|
||||
Ok(#(clean_key, clean_value))
|
||||
}
|
||||
Error(_) -> Error(Nil)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_string(frontmatter: Frontmatter, key: String) -> Option(String) {
|
||||
dict.get(frontmatter.data, key) |> option.from_result
|
||||
}
|
||||
|
||||
pub fn get_string_or(
|
||||
frontmatter: Frontmatter,
|
||||
key: String,
|
||||
default: String,
|
||||
) -> String {
|
||||
get_string(frontmatter, key) |> option.unwrap(default)
|
||||
}
|
||||
|
||||
pub fn get_int(frontmatter: Frontmatter, key: String) -> Option(Int) {
|
||||
case get_string(frontmatter, key) {
|
||||
Some(value) -> {
|
||||
case int.parse(value) {
|
||||
Ok(num) -> Some(num)
|
||||
Error(_) -> None
|
||||
}
|
||||
}
|
||||
None -> None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_int_or(frontmatter: Frontmatter, key: String, default: Int) -> Int {
|
||||
get_int(frontmatter, key) |> option.unwrap(default)
|
||||
}
|
||||
|
||||
pub fn get_content(frontmatter: Frontmatter) -> String {
|
||||
frontmatter.content
|
||||
}
|
||||
194
fluxer_marketing/src/fluxer_marketing/geoip.gleam
Normal file
194
fluxer_marketing/src/fluxer_marketing/geoip.gleam
Normal file
@@ -0,0 +1,194 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import gleam/bit_array
|
||||
import gleam/http/request
|
||||
import gleam/httpc
|
||||
import gleam/json
|
||||
import gleam/list
|
||||
import gleam/result
|
||||
import gleam/string
|
||||
import wisp
|
||||
|
||||
const default_cc = "US"
|
||||
|
||||
pub fn country_code(req: wisp.Request, geoip_host: String) -> String {
|
||||
let get_header = fn(name) { request.get_header(req, name) }
|
||||
country_code_core(get_header, geoip_host, fetch_country_code_http)
|
||||
}
|
||||
|
||||
pub fn country_code_core(
|
||||
get_header: fn(String) -> Result(String, Nil),
|
||||
geoip_host: String,
|
||||
fetch_country: fn(String) -> Result(String, Nil),
|
||||
) -> String {
|
||||
case geoip_host {
|
||||
"" -> default_cc
|
||||
_ -> {
|
||||
case extract_client_ip_from(get_header) {
|
||||
"" -> default_cc
|
||||
ip -> {
|
||||
let url =
|
||||
"http://" <> geoip_host <> "/lookup?ip=" <> percent_encode_ip(ip)
|
||||
case fetch_country(url) {
|
||||
Ok(body) -> {
|
||||
let cc = string.uppercase(string.trim(body))
|
||||
case is_valid_country_code(cc) {
|
||||
True -> cc
|
||||
False -> default_cc
|
||||
}
|
||||
}
|
||||
Error(_) -> default_cc
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fetch_country_code_http(url: String) -> Result(String, Nil) {
|
||||
let assert Ok(req) = request.to(url)
|
||||
let req = request.prepend_header(req, "accept", "text/plain")
|
||||
case httpc.send(req) {
|
||||
Ok(resp) if resp.status >= 200 && resp.status < 300 -> Ok(resp.body)
|
||||
_ -> Error(Nil)
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_client_ip(req: wisp.Request) -> String {
|
||||
extract_client_ip_from(fn(name) { request.get_header(req, name) })
|
||||
}
|
||||
|
||||
pub fn extract_client_ip_from(
|
||||
get_header: fn(String) -> Result(String, Nil),
|
||||
) -> String {
|
||||
case get_header("x-forwarded-for") {
|
||||
Ok(xff) -> {
|
||||
xff
|
||||
|> string.split(",")
|
||||
|> list.first
|
||||
|> result.unwrap("")
|
||||
|> string.trim
|
||||
|> strip_brackets
|
||||
|> validate_ip
|
||||
}
|
||||
Error(_) -> ""
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_ip(s: String) -> String {
|
||||
case string.contains(s, ".") || string.contains(s, ":") {
|
||||
True -> s
|
||||
False -> ""
|
||||
}
|
||||
}
|
||||
|
||||
pub fn strip_brackets(ip: String) -> String {
|
||||
let len = string.length(ip)
|
||||
let has_brackets =
|
||||
len >= 2
|
||||
&& string.first(ip) == Ok("[")
|
||||
&& string.slice(ip, len - 1, 1) == "]"
|
||||
|
||||
case has_brackets {
|
||||
True -> string.slice(ip, 1, len - 2)
|
||||
False -> ip
|
||||
}
|
||||
}
|
||||
|
||||
pub fn percent_encode_ip(s: String) -> String {
|
||||
s
|
||||
|> string.replace("%", "%25")
|
||||
|> string.replace(":", "%3A")
|
||||
|> string.replace(" ", "%20")
|
||||
}
|
||||
|
||||
pub fn is_ascii_upper_alpha2(s: String) -> Bool {
|
||||
case string.byte_size(s) == 2 {
|
||||
False -> False
|
||||
True ->
|
||||
case string.to_graphemes(s) {
|
||||
[a, b] -> is_uppercase_letter(a) && is_uppercase_letter(b)
|
||||
_ -> False
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_valid_country_code(s: String) -> Bool {
|
||||
is_ascii_upper_alpha2(s)
|
||||
}
|
||||
|
||||
fn is_uppercase_letter(g: String) -> Bool {
|
||||
case bit_array.from_string(g) {
|
||||
<<c:8>> -> c >= 65 && c <= 90
|
||||
_ -> False
|
||||
}
|
||||
}
|
||||
|
||||
pub fn debug_info(req: wisp.Request, geoip_host: String) -> String {
|
||||
let xff =
|
||||
request.get_header(req, "x-forwarded-for") |> result.unwrap("(not set)")
|
||||
let ip = extract_client_ip(req)
|
||||
let host_display = case geoip_host {
|
||||
"" -> "(not set)"
|
||||
h -> h
|
||||
}
|
||||
|
||||
let url = case ip {
|
||||
"" -> "(empty IP - no URL)"
|
||||
_ -> "http://" <> host_display <> "/lookup?ip=" <> percent_encode_ip(ip)
|
||||
}
|
||||
|
||||
let response = case ip {
|
||||
"" -> "(empty IP - no request)"
|
||||
_ -> fetch_geoip_debug(url)
|
||||
}
|
||||
|
||||
json.object([
|
||||
#("x_forwarded_for_header", json.string(xff)),
|
||||
#("extracted_ip", json.string(ip)),
|
||||
#(
|
||||
"stripped_brackets",
|
||||
json.string(case ip {
|
||||
"" -> "(empty)"
|
||||
i -> strip_brackets(i)
|
||||
}),
|
||||
),
|
||||
#(
|
||||
"percent_encoded",
|
||||
json.string(case ip {
|
||||
"" -> "(empty)"
|
||||
i -> percent_encode_ip(i)
|
||||
}),
|
||||
),
|
||||
#("geoip_host", json.string(host_display)),
|
||||
#("geoip_url", json.string(url)),
|
||||
#("geoip_response", json.string(response)),
|
||||
#("final_country_code", json.string(country_code(req, geoip_host))),
|
||||
])
|
||||
|> json.to_string
|
||||
}
|
||||
|
||||
fn fetch_geoip_debug(url: String) -> String {
|
||||
let assert Ok(req) = request.to(url)
|
||||
let req = request.prepend_header(req, "accept", "text/plain")
|
||||
case httpc.send(req) {
|
||||
Ok(resp) ->
|
||||
"Status: " <> string.inspect(resp.status) <> ", Body: " <> resp.body
|
||||
Error(_) -> "(request failed)"
|
||||
}
|
||||
}
|
||||
332
fluxer_marketing/src/fluxer_marketing/help_center.gleam
Normal file
332
fluxer_marketing/src/fluxer_marketing/help_center.gleam
Normal file
@@ -0,0 +1,332 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import fluxer_marketing/frontmatter
|
||||
import fluxer_marketing/locale.{type Locale}
|
||||
import gleam/dict
|
||||
import gleam/int
|
||||
import gleam/list
|
||||
import gleam/regexp
|
||||
import gleam/result
|
||||
import gleam/string
|
||||
import simplifile
|
||||
|
||||
const regex_options = regexp.Options(case_insensitive: False, multi_line: True)
|
||||
|
||||
pub type HelpArticle {
|
||||
HelpArticle(
|
||||
title: String,
|
||||
description: String,
|
||||
category: String,
|
||||
category_title: String,
|
||||
category_icon: String,
|
||||
order: Int,
|
||||
slug: String,
|
||||
snowflake_id: String,
|
||||
content: String,
|
||||
)
|
||||
}
|
||||
|
||||
pub type HelpCategory {
|
||||
HelpCategory(
|
||||
name: String,
|
||||
title: String,
|
||||
icon: String,
|
||||
article_count: Int,
|
||||
articles: List(HelpArticle),
|
||||
)
|
||||
}
|
||||
|
||||
pub type HelpCenterData {
|
||||
HelpCenterData(
|
||||
categories: List(HelpCategory),
|
||||
all_articles: List(HelpArticle),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn load_help_articles(locale: Locale) -> HelpCenterData {
|
||||
let locale_code = locale.get_code_from_locale(locale)
|
||||
let help_root = "priv/help"
|
||||
|
||||
case scan_help_directory(help_root, locale_code) {
|
||||
Ok(articles) -> {
|
||||
let categories = group_by_category(articles)
|
||||
HelpCenterData(categories: categories, all_articles: articles)
|
||||
}
|
||||
Error(_) -> HelpCenterData(categories: [], all_articles: [])
|
||||
}
|
||||
}
|
||||
|
||||
fn scan_help_directory(
|
||||
root: String,
|
||||
locale_code: String,
|
||||
) -> Result(List(HelpArticle), Nil) {
|
||||
case list_directory_recursive(root) {
|
||||
Ok(files) -> {
|
||||
let locale_suffix = "/" <> locale_code <> ".md"
|
||||
let fallback_suffix = "/en-US.md"
|
||||
|
||||
let locale_files =
|
||||
files
|
||||
|> list.filter(fn(path) { string.ends_with(path, locale_suffix) })
|
||||
|
||||
let fallback_files = case locale_code == "en-US" {
|
||||
True -> []
|
||||
False ->
|
||||
files
|
||||
|> list.filter(fn(path) { string.ends_with(path, fallback_suffix) })
|
||||
|> list.filter(fn(fallback_path) {
|
||||
let base_dir = get_article_base_dir(fallback_path)
|
||||
let has_locale_version =
|
||||
list.any(locale_files, fn(locale_path) {
|
||||
get_article_base_dir(locale_path) == base_dir
|
||||
})
|
||||
!has_locale_version
|
||||
})
|
||||
}
|
||||
|
||||
let articles =
|
||||
list.append(locale_files, fallback_files)
|
||||
|> list.filter_map(fn(path) { load_article(path) })
|
||||
|
||||
Ok(articles)
|
||||
}
|
||||
Error(_) -> Error(Nil)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_article_base_dir(path: String) -> String {
|
||||
path
|
||||
|> string.split("/")
|
||||
|> list.reverse
|
||||
|> list.drop(1)
|
||||
|> list.reverse
|
||||
|> string.join("/")
|
||||
}
|
||||
|
||||
fn list_directory_recursive(dir: String) -> Result(List(String), Nil) {
|
||||
case simplifile.read_directory(dir) {
|
||||
Ok(entries) -> {
|
||||
let results =
|
||||
entries
|
||||
|> list.map(fn(entry) {
|
||||
let path = dir <> "/" <> entry
|
||||
case simplifile.is_directory(path) {
|
||||
Ok(True) -> list_directory_recursive(path)
|
||||
Ok(False) -> Ok([path])
|
||||
Error(_) -> Ok([])
|
||||
}
|
||||
})
|
||||
|
||||
let all_files =
|
||||
results
|
||||
|> list.filter_map(fn(r) { result.unwrap(r, []) |> Ok })
|
||||
|> list.flatten
|
||||
|
||||
Ok(all_files)
|
||||
}
|
||||
Error(_) -> Error(Nil)
|
||||
}
|
||||
}
|
||||
|
||||
fn load_article(path: String) -> Result(HelpArticle, Nil) {
|
||||
case simplifile.read(path) {
|
||||
Ok(content) -> {
|
||||
let fm = frontmatter.parse(content)
|
||||
|
||||
let slug = extract_slug_from_path(path)
|
||||
|
||||
let title = frontmatter.get_string_or(fm, "title", "Untitled")
|
||||
let description =
|
||||
frontmatter.get_string_or(fm, "description", "No description")
|
||||
let category = frontmatter.get_string_or(fm, "category", "general")
|
||||
let category_title =
|
||||
frontmatter.get_string_or(fm, "category_title", "General")
|
||||
let category_icon =
|
||||
frontmatter.get_string_or(fm, "category_icon", "sparkle")
|
||||
let order = frontmatter.get_int_or(fm, "order", 999)
|
||||
let snowflake_id = frontmatter.get_string_or(fm, "snowflake_id", "")
|
||||
|
||||
Ok(HelpArticle(
|
||||
title: title,
|
||||
description: description,
|
||||
category: category,
|
||||
category_title: category_title,
|
||||
category_icon: category_icon,
|
||||
order: order,
|
||||
slug: slug,
|
||||
snowflake_id: snowflake_id,
|
||||
content: frontmatter.get_content(fm),
|
||||
))
|
||||
}
|
||||
Error(_) -> Error(Nil)
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_slug_from_path(path: String) -> String {
|
||||
path
|
||||
|> string.split("/")
|
||||
|> list.reverse
|
||||
|> list.drop(1)
|
||||
|> list.first
|
||||
|> result.unwrap("unknown")
|
||||
}
|
||||
|
||||
fn group_by_category(articles: List(HelpArticle)) -> List(HelpCategory) {
|
||||
let grouped =
|
||||
articles
|
||||
|> list.group(fn(article) { article.category })
|
||||
|
||||
grouped
|
||||
|> dict.to_list
|
||||
|> list.map(fn(entry) {
|
||||
let #(category_name, category_articles) = entry
|
||||
|
||||
let sorted_articles =
|
||||
category_articles
|
||||
|> list.sort(fn(a: HelpArticle, b: HelpArticle) {
|
||||
int.compare(a.order, b.order)
|
||||
})
|
||||
|
||||
let first = list.first(sorted_articles)
|
||||
let category_title = case first {
|
||||
Ok(article) -> article.category_title
|
||||
Error(_) -> category_name
|
||||
}
|
||||
let category_icon = case first {
|
||||
Ok(article) -> article.category_icon
|
||||
Error(_) -> "sparkle"
|
||||
}
|
||||
|
||||
HelpCategory(
|
||||
name: category_name,
|
||||
title: category_title,
|
||||
icon: category_icon,
|
||||
article_count: list.length(category_articles),
|
||||
articles: sorted_articles,
|
||||
)
|
||||
})
|
||||
|> list.sort(fn(a, b) { string.compare(a.title, b.title) })
|
||||
}
|
||||
|
||||
pub fn get_category(
|
||||
data: HelpCenterData,
|
||||
category_name: String,
|
||||
) -> Result(HelpCategory, Nil) {
|
||||
data.categories
|
||||
|> list.find(fn(cat) { cat.name == category_name })
|
||||
}
|
||||
|
||||
pub fn get_article(
|
||||
data: HelpCenterData,
|
||||
category_name: String,
|
||||
article_slug: String,
|
||||
) -> Result(HelpArticle, Nil) {
|
||||
data.all_articles
|
||||
|> list.find(fn(article) {
|
||||
article.category == category_name && article.slug == article_slug
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_article_by_snowflake(
|
||||
data: HelpCenterData,
|
||||
snowflake_id: String,
|
||||
) -> Result(HelpArticle, Nil) {
|
||||
data.all_articles
|
||||
|> list.find(fn(article) { article.snowflake_id == snowflake_id })
|
||||
}
|
||||
|
||||
pub fn create_slug(title: String) -> String {
|
||||
let lower = string.lowercase(title)
|
||||
|
||||
let hyphened = case regexp.compile("\\s+", regex_options) {
|
||||
Ok(regex) -> regexp.replace(regex, lower, "-")
|
||||
Error(_) -> lower
|
||||
}
|
||||
|
||||
let cleaned = case regexp.compile("[^\\p{L}\\p{N}\\-._~]+", regex_options) {
|
||||
Ok(regex) -> regexp.replace(regex, hyphened, "-")
|
||||
Error(_) -> hyphened
|
||||
}
|
||||
|
||||
let collapsed = case regexp.compile("-+", regex_options) {
|
||||
Ok(regex) -> regexp.replace(regex, cleaned, "-")
|
||||
Error(_) -> cleaned
|
||||
}
|
||||
|
||||
let trimmed = trim_hyphens(collapsed)
|
||||
|
||||
case string.is_empty(trimmed) {
|
||||
True -> "article"
|
||||
False -> trimmed
|
||||
}
|
||||
}
|
||||
|
||||
fn trim_hyphens(text: String) -> String {
|
||||
let chars = string.to_graphemes(text)
|
||||
|
||||
let trimmed_start =
|
||||
list.drop_while(chars, fn(c) { c == "-" })
|
||||
|> list.reverse
|
||||
|> list.drop_while(fn(c) { c == "-" })
|
||||
|> list.reverse
|
||||
|
||||
case trimmed_start {
|
||||
[] -> ""
|
||||
_ -> string.join(trimmed_start, "")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn search_articles(data: HelpCenterData, query: String) -> List(HelpArticle) {
|
||||
let lower_query = string.lowercase(query)
|
||||
|
||||
data.all_articles
|
||||
|> list.filter(fn(article) {
|
||||
let title_match =
|
||||
string.lowercase(article.title) |> string.contains(lower_query)
|
||||
let desc_match =
|
||||
string.lowercase(article.description) |> string.contains(lower_query)
|
||||
title_match || desc_match
|
||||
})
|
||||
}
|
||||
|
||||
pub fn filter_by_category(
|
||||
articles: List(HelpArticle),
|
||||
category: String,
|
||||
) -> List(HelpArticle) {
|
||||
articles
|
||||
|> list.filter(fn(article) { article.category == category })
|
||||
}
|
||||
|
||||
pub fn article_href(
|
||||
locale: Locale,
|
||||
data: HelpCenterData,
|
||||
snowflake_id: String,
|
||||
) -> String {
|
||||
let locale_code = locale.get_code_from_locale(locale) |> string.lowercase
|
||||
|
||||
case get_article_by_snowflake(data, snowflake_id) {
|
||||
Ok(article) ->
|
||||
"/help/"
|
||||
<> locale_code
|
||||
<> "/articles/"
|
||||
<> article.snowflake_id
|
||||
<> "-"
|
||||
<> create_slug(article.title)
|
||||
Error(_) -> "/help/articles/" <> snowflake_id
|
||||
}
|
||||
}
|
||||
56
fluxer_marketing/src/fluxer_marketing/i18n.gleam
Normal file
56
fluxer_marketing/src/fluxer_marketing/i18n.gleam
Normal file
@@ -0,0 +1,56 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import fluxer_marketing/locale.{type Locale}
|
||||
import kielet/context.{type Context, Context}
|
||||
import kielet/database
|
||||
import kielet/language
|
||||
import simplifile
|
||||
|
||||
pub fn setup_database() -> database.Database {
|
||||
let db = database.new()
|
||||
|
||||
db
|
||||
}
|
||||
|
||||
pub fn get_context(db: database.Database, locale: Locale) -> Context {
|
||||
let locale_code = locale.get_code_from_locale(locale)
|
||||
|
||||
let db_with_fallback = case load_locale(db, locale_code) {
|
||||
Ok(updated_db) -> updated_db
|
||||
Error(_) -> db
|
||||
}
|
||||
|
||||
Context(db_with_fallback, locale_code)
|
||||
}
|
||||
|
||||
fn load_locale(
|
||||
db: database.Database,
|
||||
locale_code: String,
|
||||
) -> Result(database.Database, Nil) {
|
||||
let mo_path = "priv/locales/" <> locale_code <> "/LC_MESSAGES/messages.mo"
|
||||
|
||||
case simplifile.read_bits(mo_path) {
|
||||
Ok(mo_data) -> {
|
||||
case language.load(locale_code, mo_data) {
|
||||
Ok(lang) -> Ok(database.add_language(db, lang))
|
||||
Error(_) -> Error(Nil)
|
||||
}
|
||||
}
|
||||
Error(_) -> Error(Nil)
|
||||
}
|
||||
}
|
||||
227
fluxer_marketing/src/fluxer_marketing/icons.gleam
Normal file
227
fluxer_marketing/src/fluxer_marketing/icons.gleam
Normal file
@@ -0,0 +1,227 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import fluxer_marketing/icons/android
|
||||
import fluxer_marketing/icons/apple
|
||||
import fluxer_marketing/icons/arrow_right
|
||||
import fluxer_marketing/icons/arrow_up
|
||||
import fluxer_marketing/icons/bluesky
|
||||
import fluxer_marketing/icons/brain
|
||||
import fluxer_marketing/icons/bug
|
||||
import fluxer_marketing/icons/butterfly
|
||||
import fluxer_marketing/icons/calendar_check
|
||||
import fluxer_marketing/icons/caret_down
|
||||
import fluxer_marketing/icons/chart_line
|
||||
import fluxer_marketing/icons/chat_centered_text
|
||||
import fluxer_marketing/icons/chats
|
||||
import fluxer_marketing/icons/chats_circle
|
||||
import fluxer_marketing/icons/check
|
||||
import fluxer_marketing/icons/check_circle
|
||||
import fluxer_marketing/icons/code_icon
|
||||
import fluxer_marketing/icons/coins
|
||||
import fluxer_marketing/icons/crown
|
||||
import fluxer_marketing/icons/devices
|
||||
import fluxer_marketing/icons/download
|
||||
import fluxer_marketing/icons/flask
|
||||
import fluxer_marketing/icons/fluxer_bug_hunter
|
||||
import fluxer_marketing/icons/fluxer_ctp
|
||||
import fluxer_marketing/icons/fluxer_logo_wordmark
|
||||
import fluxer_marketing/icons/fluxer_partner
|
||||
import fluxer_marketing/icons/fluxer_premium
|
||||
import fluxer_marketing/icons/fluxer_staff
|
||||
import fluxer_marketing/icons/game_controller
|
||||
import fluxer_marketing/icons/gear
|
||||
import fluxer_marketing/icons/gif
|
||||
import fluxer_marketing/icons/gift
|
||||
import fluxer_marketing/icons/github
|
||||
import fluxer_marketing/icons/globe
|
||||
import fluxer_marketing/icons/google_play
|
||||
import fluxer_marketing/icons/graduation_cap
|
||||
import fluxer_marketing/icons/hash
|
||||
import fluxer_marketing/icons/heart
|
||||
import fluxer_marketing/icons/infinity
|
||||
import fluxer_marketing/icons/lightning
|
||||
import fluxer_marketing/icons/link
|
||||
import fluxer_marketing/icons/linux
|
||||
import fluxer_marketing/icons/magnifying_glass
|
||||
import fluxer_marketing/icons/medal
|
||||
import fluxer_marketing/icons/menu
|
||||
import fluxer_marketing/icons/microphone
|
||||
import fluxer_marketing/icons/newspaper
|
||||
import fluxer_marketing/icons/palette
|
||||
import fluxer_marketing/icons/paperclip
|
||||
import fluxer_marketing/icons/percent
|
||||
import fluxer_marketing/icons/question
|
||||
import fluxer_marketing/icons/rocket
|
||||
import fluxer_marketing/icons/rocket_launch
|
||||
import fluxer_marketing/icons/rss
|
||||
import fluxer_marketing/icons/seal_check
|
||||
import fluxer_marketing/icons/shield_check
|
||||
import fluxer_marketing/icons/shopping_cart
|
||||
import fluxer_marketing/icons/smiley
|
||||
import fluxer_marketing/icons/sparkle
|
||||
import fluxer_marketing/icons/speaker_high
|
||||
import fluxer_marketing/icons/swish
|
||||
import fluxer_marketing/icons/translate
|
||||
import fluxer_marketing/icons/tshirt
|
||||
import fluxer_marketing/icons/user_circle
|
||||
import fluxer_marketing/icons/user_plus
|
||||
import fluxer_marketing/icons/users_three
|
||||
import fluxer_marketing/icons/video
|
||||
import fluxer_marketing/icons/video_camera
|
||||
import fluxer_marketing/icons/windows
|
||||
import fluxer_marketing/icons/x
|
||||
|
||||
pub const android = android.android
|
||||
|
||||
pub const apple = apple.apple
|
||||
|
||||
pub const arrow_right = arrow_right.arrow_right
|
||||
|
||||
pub const arrow_up = arrow_up.arrow_up
|
||||
|
||||
pub const bluesky = bluesky.bluesky
|
||||
|
||||
pub const brain = brain.brain
|
||||
|
||||
pub const bug = bug.bug
|
||||
|
||||
pub const butterfly = butterfly.butterfly
|
||||
|
||||
pub const calendar_check = calendar_check.calendar_check
|
||||
|
||||
pub const caret_down = caret_down.caret_down
|
||||
|
||||
pub const chart_line = chart_line.chart_line
|
||||
|
||||
pub const chat_centered_text = chat_centered_text.chat_centered_text
|
||||
|
||||
pub const chats = chats.chats
|
||||
|
||||
pub const chats_circle = chats_circle.chats_circle
|
||||
|
||||
pub const check = check.check
|
||||
|
||||
pub const check_circle = check_circle.check_circle
|
||||
|
||||
pub const code_icon = code_icon.code_icon
|
||||
|
||||
pub const coins = coins.coins
|
||||
|
||||
pub const crown = crown.crown
|
||||
|
||||
pub const devices = devices.devices
|
||||
|
||||
pub const download = download.download
|
||||
|
||||
pub const flask = flask.flask
|
||||
|
||||
pub const fluxer_bug_hunter = fluxer_bug_hunter.fluxer_bug_hunter
|
||||
|
||||
pub const fluxer_ctp = fluxer_ctp.fluxer_ctp
|
||||
|
||||
pub const fluxer_logo_wordmark = fluxer_logo_wordmark.fluxer_logo_wordmark
|
||||
|
||||
pub const fluxer_partner = fluxer_partner.fluxer_partner
|
||||
|
||||
pub const fluxer_premium = fluxer_premium.fluxer_premium
|
||||
|
||||
pub const fluxer_staff = fluxer_staff.fluxer_staff
|
||||
|
||||
pub const game_controller = game_controller.game_controller
|
||||
|
||||
pub const gear = gear.gear
|
||||
|
||||
pub const gif = gif.gif
|
||||
|
||||
pub const github = github.github
|
||||
|
||||
pub const gift = gift.gift
|
||||
|
||||
pub const globe = globe.globe
|
||||
|
||||
pub const google_play = google_play.google_play
|
||||
|
||||
pub const graduation_cap = graduation_cap.graduation_cap
|
||||
|
||||
pub const hash = hash.hash
|
||||
|
||||
pub const heart = heart.heart
|
||||
|
||||
pub const infinity = infinity.infinity
|
||||
|
||||
pub const lightning = lightning.lightning
|
||||
|
||||
pub const link = link.link
|
||||
|
||||
pub const linux = linux.linux
|
||||
|
||||
pub const magnifying_glass = magnifying_glass.magnifying_glass
|
||||
|
||||
pub const medal = medal.medal
|
||||
|
||||
pub const menu = menu.menu
|
||||
|
||||
pub const microphone = microphone.microphone
|
||||
|
||||
pub const newspaper = newspaper.newspaper
|
||||
|
||||
pub const palette = palette.palette
|
||||
|
||||
pub const paperclip = paperclip.paperclip
|
||||
|
||||
pub const percent = percent.percent
|
||||
|
||||
pub const question = question.question
|
||||
|
||||
pub const rocket = rocket.rocket
|
||||
|
||||
pub const rocket_launch = rocket_launch.rocket_launch
|
||||
|
||||
pub const rss = rss.rss
|
||||
|
||||
pub const seal_check = seal_check.seal_check
|
||||
|
||||
pub const shield_check = shield_check.shield_check
|
||||
|
||||
pub const shopping_cart = shopping_cart.shopping_cart
|
||||
|
||||
pub const smiley = smiley.smiley
|
||||
|
||||
pub const speaker_high = speaker_high.speaker_high
|
||||
|
||||
pub const sparkle = sparkle.sparkle
|
||||
|
||||
pub const swish = swish.swish
|
||||
|
||||
pub const translate = translate.translate
|
||||
|
||||
pub const tshirt = tshirt.tshirt
|
||||
|
||||
pub const user_circle = user_circle.user_circle
|
||||
|
||||
pub const user_plus = user_plus.user_plus
|
||||
|
||||
pub const users_three = users_three.users_three
|
||||
|
||||
pub const video = video.video
|
||||
|
||||
pub const video_camera = video_camera.video_camera
|
||||
|
||||
pub const windows = windows.windows
|
||||
|
||||
pub const x = x.x
|
||||
39
fluxer_marketing/src/fluxer_marketing/icons/android.gleam
Normal file
39
fluxer_marketing/src/fluxer_marketing/icons/android.gleam
Normal file
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute.{type Attribute}
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn android(attributes: List(Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attributes
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M207.06,80.67c-.74-.74-1.49-1.46-2.24-2.17l24.84-24.84a8,8,0,0,0-11.32-11.32l-26,26a111.43,111.43,0,0,0-128.55.19L37.66,42.34A8,8,0,0,0,26.34,53.66L51.4,78.72A113.38,113.38,0,0,0,16,161.13V184a16,16,0,0,0,16,16H224a16,16,0,0,0,16-16V160A111.25,111.25,0,0,0,207.06,80.67ZM92,160a12,12,0,1,1,12-12A12,12,0,0,1,92,160Zm72,0a12,12,0,1,1,12-12A12,12,0,0,1,164,160Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
39
fluxer_marketing/src/fluxer_marketing/icons/apple.gleam
Normal file
39
fluxer_marketing/src/fluxer_marketing/icons/apple.gleam
Normal file
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute.{type Attribute}
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn apple(attributes: List(Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attributes
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M128.23,30A40,40,0,0,1,167,0h1a8,8,0,0,1,0,16h-1a24,24,0,0,0-23.24,18,8,8,0,1,1-15.5-4ZM223.3,169.59a8.07,8.07,0,0,0-2.8-3.4C203.53,154.53,200,134.64,200,120c0-17.67,13.47-33.06,21.5-40.67a8,8,0,0,0,0-11.62C208.82,55.74,187.82,48,168,48a72.23,72.23,0,0,0-40,12.13,71.56,71.56,0,0,0-90.71,9.09A74.63,74.63,0,0,0,16,123.4a127,127,0,0,0,40.14,89.73A39.8,39.8,0,0,0,83.59,224h87.68a39.84,39.84,0,0,0,29.12-12.57,125,125,0,0,0,17.82-24.6C225.23,174,224.33,172,223.3,169.59Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn arrow_right(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "none"),
|
||||
attribute.attribute("stroke", "currentColor"),
|
||||
attribute.attribute("stroke-linecap", "round"),
|
||||
attribute.attribute("stroke-linejoin", "round"),
|
||||
attribute.attribute("stroke-width", "24"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.line([
|
||||
attribute.attribute("x1", "40"),
|
||||
attribute.attribute("y1", "128"),
|
||||
attribute.attribute("x2", "216"),
|
||||
attribute.attribute("y2", "128"),
|
||||
]),
|
||||
svg.polyline([attribute.attribute("points", "144,56 216,128 144,200")]),
|
||||
],
|
||||
)
|
||||
}
|
||||
39
fluxer_marketing/src/fluxer_marketing/icons/arrow_up.gleam
Normal file
39
fluxer_marketing/src/fluxer_marketing/icons/arrow_up.gleam
Normal file
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn arrow_up(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M208.49,120.49a12,12,0,0,1-17,0L140,69V216a12,12,0,0,1-24,0V69L64.49,120.49a12,12,0,0,1-17-17l72-72a12,12,0,0,1,17,0l72,72A12,12,0,0,1,208.49,120.49Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
40
fluxer_marketing/src/fluxer_marketing/icons/bluesky.gleam
Normal file
40
fluxer_marketing/src/fluxer_marketing/icons/bluesky.gleam
Normal file
@@ -0,0 +1,40 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn bluesky(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 600 530"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"m135.72 44.03c66.496 49.921 138.02 151.14 164.28 205.46 26.262-54.316 97.782-155.54 164.28-205.46 47.98-36.021 125.72-63.892 125.72 24.795 0 17.712-10.155 148.79-16.111 170.07-20.703 73.984-96.144 92.854-163.25 81.433 117.3 19.964 147.14 86.092 82.697 152.22-122.39 125.59-175.91-31.511-189.63-71.766-2.514-7.3797-3.6904-10.832-3.7077-7.8964-0.0174-2.9357-1.1937 0.51669-3.7077 7.8964-13.714 40.255-67.233 197.36-189.63 71.766-64.444-66.128-34.605-132.26 82.697-152.22-67.108 11.421-142.55-7.4491-163.25-81.433-5.9562-21.282-16.111-152.36-16.111-170.07 0-88.687 77.742-60.816 125.72-24.795z",
|
||||
),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
39
fluxer_marketing/src/fluxer_marketing/icons/brain.gleam
Normal file
39
fluxer_marketing/src/fluxer_marketing/icons/brain.gleam
Normal file
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn brain(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M212,76V72a44,44,0,0,0-74.86-31.31,3.93,3.93,0,0,0-1.14,2.8v88.72a4,4,0,0,0,6.2,3.33A47.67,47.67,0,0,1,167.68,128a8.18,8.18,0,0,1,8.31,7.58,8,8,0,0,1-8,8.42,32,32,0,0,0-32,32v33.88a4,4,0,0,0,1.49,3.12,47.92,47.92,0,0,0,74.21-17.16,4,4,0,0,0-4.49-5.56A68.06,68.06,0,0,1,192,192h-7.73a8.18,8.18,0,0,1-8.25-7.47,8,8,0,0,1,8-8.53h8a51.6,51.6,0,0,0,24-5.88v0A52,52,0,0,0,212,76Zm-12,36h-4a36,36,0,0,1-36-36V72a8,8,0,0,1,16,0v4a20,20,0,0,0,20,20h4a8,8,0,0,1,0,16ZM88,28A44.05,44.05,0,0,0,44,72v4a52,52,0,0,0-4,94.12h0A51.6,51.6,0,0,0,64,176h7.73A8.18,8.18,0,0,1,80,183.47,8,8,0,0,1,72,192H64a67.48,67.48,0,0,1-15.21-1.73,4,4,0,0,0-4.5,5.55A47.93,47.93,0,0,0,118.51,213a4,4,0,0,0,1.49-3.12V176a32,32,0,0,0-32-32,8,8,0,0,1-8-8.42A8.18,8.18,0,0,1,88.32,128a47.67,47.67,0,0,1,25.48,7.54,4,4,0,0,0,6.2-3.33V43.49a4,4,0,0,0-1.14-2.81A43.85,43.85,0,0,0,88,28Zm8,48a36,36,0,0,1-36,36H56a8,8,0,0,1,0-16h4A20,20,0,0,0,80,76V72a8,8,0,0,1,16,0Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
39
fluxer_marketing/src/fluxer_marketing/icons/bug.gleam
Normal file
39
fluxer_marketing/src/fluxer_marketing/icons/bug.gleam
Normal file
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn bug(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M168,92a12,12,0,1,1-12-12A12,12,0,0,1,168,92ZM100,80a12,12,0,1,0,12,12A12,12,0,0,0,100,80Zm116,64A87.76,87.76,0,0,1,213,167l22.24,9.72A8,8,0,0,1,232,192a7.89,7.89,0,0,1-3.2-.67L207.38,182a88,88,0,0,1-158.76,0L27.2,191.33A7.89,7.89,0,0,1,24,192a8,8,0,0,1-3.2-15.33L43,167A87.76,87.76,0,0,1,40,144v-8H16a8,8,0,0,1,0-16H40v-8a87.76,87.76,0,0,1,3-23L20.8,79.33a8,8,0,1,1,6.4-14.66L48.62,74a88,88,0,0,1,158.76,0l21.42-9.36a8,8,0,0,1,6.4,14.66L213,89.05a87.76,87.76,0,0,1,3,23v8h24a8,8,0,0,1,0,16H216Zm-80,0a8,8,0,0,0-16,0v64a8,8,0,0,0,16,0Zm64-32a72,72,0,0,0-144,0v8H200Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
39
fluxer_marketing/src/fluxer_marketing/icons/butterfly.gleam
Normal file
39
fluxer_marketing/src/fluxer_marketing/icons/butterfly.gleam
Normal file
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn butterfly(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M128,100.17a108.42,108.42,0,0,0-8-12.64V56a8,8,0,0,1,16,0V87.53A108.42,108.42,0,0,0,128,100.17ZM232.7,50.48C229,45.7,221.84,40,209,40c-16.85,0-38.46,11.28-57.81,30.16A140.07,140.07,0,0,0,136,87.53V180a8,8,0,0,1-16,0V87.53a140.07,140.07,0,0,0-15.15-17.37C85.49,51.28,63.88,40,47,40,34.16,40,27,45.7,23.3,50.48c-6.82,8.77-12.18,24.08-.21,71.2,6.05,23.83,19.51,33,30.63,36.42A44,44,0,0,0,128,205.27a44,44,0,0,0,74.28-47.17c11.12-3.4,24.57-12.59,30.63-36.42C239.63,95.24,244.85,66.1,232.7,50.48Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn calendar_check(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M208,32H184V24a8,8,0,0,0-16,0v8H88V24a8,8,0,0,0-16,0v8H48A16,16,0,0,0,32,48V208a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V48A16,16,0,0,0,208,32ZM169.66,133.66l-48,48a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L116,164.69l42.34-42.35a8,8,0,0,1,11.32,11.32ZM48,80V48H72v8a8,8,0,0,0,16,0V48h80v8a8,8,0,0,0,16,0V48h24V80Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
41
fluxer_marketing/src/fluxer_marketing/icons/caret_down.gleam
Normal file
41
fluxer_marketing/src/fluxer_marketing/icons/caret_down.gleam
Normal file
@@ -0,0 +1,41 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn caret_down(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "none"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.polyline([
|
||||
attribute.attribute("points", "208 96 128 176 48 96"),
|
||||
attribute.attribute("fill", "none"),
|
||||
attribute.attribute("stroke", "currentColor"),
|
||||
attribute.attribute("stroke-linecap", "round"),
|
||||
attribute.attribute("stroke-linejoin", "round"),
|
||||
attribute.attribute("stroke-width", "24"),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
39
fluxer_marketing/src/fluxer_marketing/icons/chart_line.gleam
Normal file
39
fluxer_marketing/src/fluxer_marketing/icons/chart_line.gleam
Normal file
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn chart_line(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M236,208a12,12,0,0,1-12,12H32a12,12,0,0,1-12-12V48a12,12,0,0,1,24,0v85.55L88.1,95a12,12,0,0,1,15.1-.57l56.22,42.16L216.1,87A12,12,0,1,1,231.9,105l-64,56a12,12,0,0,1-15.1.57L96.58,119.44,44,165.45V196H224A12,12,0,0,1,236,208Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn chat_centered_text(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M216,40H40A16,16,0,0,0,24,56V184a16,16,0,0,0,16,16h60.43l13.68,23.94a16,16,0,0,0,27.78,0L155.57,200H216a16,16,0,0,0,16-16V56A16,16,0,0,0,216,40ZM160,144H96a8,8,0,0,1,0-16h64a8,8,0,0,1,0,16Zm0-32H96a8,8,0,0,1,0-16h64a8,8,0,0,1,0,16Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
39
fluxer_marketing/src/fluxer_marketing/icons/chats.gleam
Normal file
39
fluxer_marketing/src/fluxer_marketing/icons/chats.gleam
Normal file
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn chats(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M232,96a16,16,0,0,0-16-16H184V48a16,16,0,0,0-16-16H40A16,16,0,0,0,24,48V176a8,8,0,0,0,13,6.22L72,154V184a16,16,0,0,0,16,16h93.59L219,230.22a8,8,0,0,0,5,1.78,8,8,0,0,0,8-8Zm-42.55,89.78a8,8,0,0,0-5-1.78H88V152h80a16,16,0,0,0,16-16V96h32V207.25Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn chats_circle(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M232.07,186.76a80,80,0,0,0-62.5-114.17A80,80,0,1,0,23.93,138.76l-7.27,24.71a16,16,0,0,0,19.87,19.87l24.71-7.27a80.39,80.39,0,0,0,25.18,7.35,80,80,0,0,0,108.34,40.65l24.71,7.27a16,16,0,0,0,19.87-19.86Zm-16.25,1.47L224,216l-27.76-8.17a8,8,0,0,0-6,.63,64.05,64.05,0,0,1-85.87-24.88A79.93,79.93,0,0,0,174.7,89.71a64,64,0,0,1,41.75,92.48A8,8,0,0,0,215.82,188.23Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
36
fluxer_marketing/src/fluxer_marketing/icons/check.gleam
Normal file
36
fluxer_marketing/src/fluxer_marketing/icons/check.gleam
Normal file
@@ -0,0 +1,36 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn check(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "none"),
|
||||
attribute.attribute("stroke", "currentColor"),
|
||||
attribute.attribute("stroke-linecap", "round"),
|
||||
attribute.attribute("stroke-linejoin", "round"),
|
||||
attribute.attribute("stroke-width", "24"),
|
||||
..attrs
|
||||
],
|
||||
[svg.polyline([attribute.attribute("points", "40,144 96,200 224,72")])],
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn check_circle(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm45.66,85.66-56,56a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35a8,8,0,0,1,11.32,11.32Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
64
fluxer_marketing/src/fluxer_marketing/icons/code_icon.gleam
Normal file
64
fluxer_marketing/src/fluxer_marketing/icons/code_icon.gleam
Normal file
@@ -0,0 +1,64 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn code_icon(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.rect([
|
||||
attribute.attribute("width", "256"),
|
||||
attribute.attribute("height", "256"),
|
||||
attribute.attribute("fill", "none"),
|
||||
]),
|
||||
svg.polyline([
|
||||
attribute.attribute("points", "64 88 16 128 64 168"),
|
||||
attribute.attribute("fill", "none"),
|
||||
attribute.attribute("stroke", "currentColor"),
|
||||
attribute.attribute("stroke-linecap", "round"),
|
||||
attribute.attribute("stroke-linejoin", "round"),
|
||||
attribute.attribute("stroke-width", "24"),
|
||||
]),
|
||||
svg.polyline([
|
||||
attribute.attribute("points", "192 88 240 128 192 168"),
|
||||
attribute.attribute("fill", "none"),
|
||||
attribute.attribute("stroke", "currentColor"),
|
||||
attribute.attribute("stroke-linecap", "round"),
|
||||
attribute.attribute("stroke-linejoin", "round"),
|
||||
attribute.attribute("stroke-width", "24"),
|
||||
]),
|
||||
svg.line([
|
||||
attribute.attribute("x1", "160"),
|
||||
attribute.attribute("y1", "40"),
|
||||
attribute.attribute("x2", "96"),
|
||||
attribute.attribute("y2", "216"),
|
||||
attribute.attribute("fill", "none"),
|
||||
attribute.attribute("stroke", "currentColor"),
|
||||
attribute.attribute("stroke-linecap", "round"),
|
||||
attribute.attribute("stroke-linejoin", "round"),
|
||||
attribute.attribute("stroke-width", "24"),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
39
fluxer_marketing/src/fluxer_marketing/icons/coins.gleam
Normal file
39
fluxer_marketing/src/fluxer_marketing/icons/coins.gleam
Normal file
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn coins(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M184,89.57V84c0-25.08-37.83-44-88-44S8,58.92,8,84v40c0,20.89,26.25,37.49,64,42.46V172c0,25.08,37.83,44,88,44s88-18.92,88-44V132C248,111.3,222.58,94.68,184,89.57ZM56,146.87C36.41,141.4,24,132.39,24,124V109.93c8.16,5.78,19.09,10.44,32,13.57Zm80-23.37c12.91-3.13,23.84-7.79,32-13.57V124c0,8.39-12.41,17.4-32,22.87Zm-16,71.37C100.41,189.4,88,180.39,88,172v-4.17c2.63.1,5.29.17,8,.17,3.88,0,7.67-.13,11.39-.35A121.92,121.92,0,0,0,120,171.41Zm0-44.62A163,163,0,0,1,96,152a163,163,0,0,1-24-1.75V126.46A183.74,183.74,0,0,0,96,128a183.74,183.74,0,0,0,24-1.54Zm64,48a165.45,165.45,0,0,1-48,0V174.4a179.48,179.48,0,0,0,24,1.6,183.74,183.74,0,0,0,24-1.54ZM232,172c0,8.39-12.41,17.4-32,22.87V171.5c12.91-3.13,23.84-7.79,32-13.57Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
39
fluxer_marketing/src/fluxer_marketing/icons/crown.gleam
Normal file
39
fluxer_marketing/src/fluxer_marketing/icons/crown.gleam
Normal file
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn crown(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M248,80a28,28,0,1,0-51.12,15.77l-26.79,33L146,73.4a28,28,0,1,0-36.06,0L85.91,128.74l-26.79-33a28,28,0,1,0-26.6,12L47,194.63A16,16,0,0,0,62.78,208H193.22A16,16,0,0,0,209,194.63l14.47-86.85A28,28,0,0,0,248,80ZM128,40a12,12,0,1,1-12,12A12,12,0,0,1,128,40ZM24,80A12,12,0,1,1,36,92,12,12,0,0,1,24,80ZM220,92a12,12,0,1,1,12-12A12,12,0,0,1,220,92Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
39
fluxer_marketing/src/fluxer_marketing/icons/devices.gleam
Normal file
39
fluxer_marketing/src/fluxer_marketing/icons/devices.gleam
Normal file
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn devices(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M224,72H208V64a24,24,0,0,0-24-24H40A24,24,0,0,0,16,64v96a24,24,0,0,0,24,24H152v8a24,24,0,0,0,24,24h48a24,24,0,0,0,24-24V96A24,24,0,0,0,224,72Zm8,120a8,8,0,0,1-8,8H176a8,8,0,0,1-8-8V96a8,8,0,0,1,8-8h48a8,8,0,0,1,8,8Zm-96,16a8,8,0,0,1-8,8H88a8,8,0,0,1,0-16h40A8,8,0,0,1,136,208Zm80-96a8,8,0,0,1-8,8H192a8,8,0,0,1,0-16h16A8,8,0,0,1,216,112Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
39
fluxer_marketing/src/fluxer_marketing/icons/download.gleam
Normal file
39
fluxer_marketing/src/fluxer_marketing/icons/download.gleam
Normal file
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute.{type Attribute}
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn download(attributes: List(Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attributes
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M224,144v64a8,8,0,0,1-8,8H40a8,8,0,0,1-8-8V144a8,8,0,0,1,16,0v56H208V144a8,8,0,0,1,16,0Zm-101.66,5.66a8,8,0,0,0,11.32,0l40-40a8,8,0,0,0-11.32-11.32L136,124.69V40a8,8,0,0,0-16,0v84.69L93.66,98.34a8,8,0,0,0-11.32,11.32Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
39
fluxer_marketing/src/fluxer_marketing/icons/flask.gleam
Normal file
39
fluxer_marketing/src/fluxer_marketing/icons/flask.gleam
Normal file
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn flask(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M221.69,199.77,160,96.92V40h8a8,8,0,0,0,0-16H88a8,8,0,0,0,0,16h8V96.92L34.31,199.77A16,16,0,0,0,48,224H208a16,16,0,0,0,13.72-24.23Zm-90.08-42.91c-15.91-8.05-31.05-12.32-45.22-12.81l24.47-40.8A7.93,7.93,0,0,0,112,99.14V40h32V99.14a7.93,7.93,0,0,0,1.14,4.11L183.36,167C171.4,169.34,154.29,168.34,131.61,156.86Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn fluxer_bug_hunter(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("width", "512"),
|
||||
attribute.attribute("height", "512"),
|
||||
attribute.attribute("viewBox", "0 0 512 512"),
|
||||
attribute.attribute("fill", "none"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M464 208C464 296.365 392.365 368 304 368C215.635 368 144 296.365 144 208C144 119.634 215.635 48 304 48C392.365 48 464 119.634 464 208Z",
|
||||
),
|
||||
attribute.attribute("fill", "#7570FF"),
|
||||
]),
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M389.57 123.713C398.407 139.018 396.264 156.798 384.784 163.426C373.306 170.053 356.837 163.018 348 147.713C339.163 132.408 341.306 114.627 352.784 108C364.264 101.373 380.733 108.408 389.57 123.713Z",
|
||||
),
|
||||
attribute.attribute("fill", "#837FFF"),
|
||||
]),
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M480 208C480 305.202 401.203 384 304 384C267.277 384 233.181 372.754 204.97 353.515C209.366 372.938 204.003 394.123 188.882 409.246L137.969 460.158C114.538 483.589 76.548 483.589 53.1166 460.158C29.6851 436.726 29.6851 398.736 53.1166 375.306L104.028 324.394C119.028 309.394 139.995 303.995 159.289 308.202C139.561 279.763 128 245.232 128 208C128 110.798 206.798 32 304 32C401.203 32 480 110.798 480 208ZM448 208C448 128.471 383.53 64 304 64C224.472 64 160 128.471 160 208C160 287.53 224.472 352 304 352C383.53 352 448 287.53 448 208Z",
|
||||
),
|
||||
attribute.attribute("fill", "#4641D9"),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
53
fluxer_marketing/src/fluxer_marketing/icons/fluxer_ctp.gleam
Normal file
53
fluxer_marketing/src/fluxer_marketing/icons/fluxer_ctp.gleam
Normal file
@@ -0,0 +1,53 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn fluxer_ctp(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("width", "512"),
|
||||
attribute.attribute("height", "512"),
|
||||
attribute.attribute("viewBox", "0 0 512 512"),
|
||||
attribute.attribute("fill", "none"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.g([attribute.attribute("clip-path", "url(#clip0_80_10)")], [
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M96 80H416C424.487 80 432.626 83.3714 438.627 89.3726C444.629 95.3737 448 103.513 448 112V224C448 329.44 396.96 393.34 354.14 428.38C308.02 466.1 262.14 478.92 260.14 479.44C257.39 480.188 254.49 480.188 251.74 479.44C249.74 478.92 203.92 466.1 157.74 428.38C115.04 393.34 64 329.44 64 224V112C64 103.513 67.3714 95.3737 73.3726 89.3726C79.3738 83.3714 87.5131 80 96 80ZM256 447.24C284.296 437.359 310.641 422.596 333.84 403.62C380.34 365.58 407.26 316 414.2 256H256V112H96V224C95.9939 234.692 96.5949 245.376 97.8 256H256V447.24Z",
|
||||
),
|
||||
attribute.attribute("fill", "#4641D9"),
|
||||
]),
|
||||
]),
|
||||
svg.defs([], [
|
||||
svg.clip_path([attribute.attribute("id", "clip0_80_10")], [
|
||||
svg.rect([
|
||||
attribute.attribute("width", "512"),
|
||||
attribute.attribute("height", "512"),
|
||||
attribute.attribute("fill", "white"),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn fluxer_logo_wordmark(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("fill", "none"),
|
||||
attribute.attribute("viewBox", "0 0 1959 512"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M585 431.4V93.48h82.944V431.4H585Zm41.472-120.32v-67.584h172.032v67.584H626.472Zm0-148.48V93.48h189.44v69.12h-189.44ZM843.951 431.4V73h82.944v358.4h-82.944ZM1047.92 438.568c-29.35 0-51.2-10.24-65.536-30.72-13.995-20.48-20.992-51.883-20.992-94.208V161.064h82.948v151.552c0 19.797 2.73 33.621 8.19 41.472 5.8 7.851 13.99 11.776 24.57 11.776 7.51 0 14.34-1.877 20.48-5.632 6.49-4.096 12.12-10.069 16.9-17.92 4.78-8.192 8.36-18.603 10.75-31.232 2.73-12.629 4.1-27.819 4.1-45.568V161.064h82.94V431.4h-70.65V325.928h-3.59c-2.05 26.283-6.65 47.787-13.82 64.512-7.17 16.384-17.07 28.501-29.7 36.352-12.63 7.851-28.16 11.776-46.59 11.776ZM1232.57 431.4l84.99-135.68-83.97-134.656h96.26l39.42 87.552h2.56l38.4-87.552h95.23l-81.4 134.656 82.43 135.68h-97.79l-37.38-86.016h-2.05L1327.8 431.4h-95.23Z",
|
||||
),
|
||||
]),
|
||||
svg.path([
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M1630.96 438.568c-25.6 0-47.1-3.584-64.51-10.752-17.06-7.509-30.89-17.579-41.47-30.208-10.58-12.971-18.26-27.648-23.04-44.032-4.44-16.384-6.66-33.621-6.66-51.712 0-19.456 2.39-38.059 7.17-55.808 5.12-17.749 12.8-33.451 23.04-47.104 10.58-13.995 24.07-24.917 40.45-32.768 16.73-8.192 36.69-12.288 59.9-12.288s43.01 4.096 59.4 12.288c16.72 7.851 30.03 18.944 39.93 33.28 9.9 14.336 16.22 30.891 18.95 49.664 3.07 18.773 2.73 39.083-1.03 60.928L1547 313.128v-45.056l132.6-2.56-10.75 26.112c2.05-15.701 1.71-28.843-1.02-39.424-2.39-10.923-7-19.115-13.83-24.576-6.82-5.803-16.21-8.704-28.16-8.704-12.63 0-22.69 3.243-30.2 9.728-7.51 6.485-12.8 15.701-15.88 27.648-3.07 11.605-4.6 25.429-4.6 41.472 0 27.648 4.6 47.787 13.82 60.416 9.22 12.629 23.38 18.944 42.5 18.944 8.19 0 15.01-1.024 20.48-3.072 5.46-2.048 9.89-4.949 13.31-8.704 3.41-4.096 5.8-8.875 7.17-14.336 1.36-5.803 1.87-12.288 1.53-19.456l75.78 4.096c1.02 11.264-.17 22.869-3.59 34.816-3.07 11.947-9.04 23.04-17.92 33.28-8.87 10.24-21.33 18.603-37.37 25.088-15.7 6.485-35.67 9.728-59.91 9.728ZM1778.45 431.4V161.064h71.68v107.52h4.1c2.05-28.672 5.97-51.2 11.77-67.584 6.15-16.725 13.66-28.501 22.53-35.328 9.22-7.168 19.46-10.752 30.72-10.752 6.15 0 12.46.853 18.95 2.56 6.82 1.707 13.48 4.437 19.96 8.192l-4.09 92.16c-7.51-4.437-14.85-7.68-22.02-9.728-7.17-2.389-13.99-3.584-20.48-3.584-10.92 0-20.14 3.072-27.65 9.216-7.51 6.144-13.31 15.189-17.4 27.136-3.76 11.947-5.64 26.453-5.64 43.52V431.4h-82.43ZM256 0c141.385 0 256 114.615 256 256S397.385 512 256 512 0 397.385 0 256 114.615 0 256 0Zm-68.47 266.057c-15.543 0-30.324 3.505-44.343 10.514-13.866 7.01-25.143 18.21-33.828 33.6-5.616 10.129-9.318 22.403-11.1061 36.822-1.6543 13.341 9.5761 24.207 23.0181 24.207 13.778 0 24.065-11.574 27.402-24.941 1.891-7.579 4.939-13.589 9.142-18.03 8.076-8.534 18.286-12.8 30.629-12.8 8.229 0 15.772 2.057 22.629 6.171 6.857 3.962 15.771 10.743 26.742 20.343 16.762 14.781 31.544 25.524 44.344 32.228 12.8 6.553 26.971 9.829 42.514 9.829 15.543 0 30.324-3.505 44.343-10.514 14.019-7.01 25.371-18.21 34.057-33.6 5.738-10.168 9.448-22.497 11.129-36.987 1.543-13.302-9.704-24.042-23.096-24.042-13.863.001-24.202 11.704-27.888 25.07-1.797 6.515-4.512 12.025-8.145 16.53-7.619 9.448-18.057 14.172-31.314 14.172-8.229 0-15.696-1.982-22.4-5.943-6.553-4.115-15.543-10.972-26.972-20.572-16.914-14.171-31.772-24.685-44.572-31.543-12.647-7.009-26.742-10.514-42.285-10.514Zm0-138.057c-15.543 0-30.324 3.505-44.343 10.514-13.866 7.01-25.143 18.21-33.828 33.6-5.616 10.129-9.318 22.403-11.1061 36.821-1.6544 13.341 9.5761 24.207 23.0181 24.208 13.778 0 24.065-11.574 27.402-24.941 1.891-7.579 4.939-13.589 9.142-18.031 8.076-8.533 18.286-12.8 30.629-12.8 8.229 0 15.772 2.058 22.629 6.172 6.857 3.962 15.771 10.743 26.742 20.343 16.762 14.781 31.544 25.524 44.344 32.228 12.8 6.553 26.971 9.829 42.514 9.829 15.543 0 30.324-3.505 44.343-10.514 14.019-7.01 25.371-18.21 34.057-33.6 5.738-10.168 9.448-22.497 11.129-36.987 1.543-13.303-9.704-24.042-23.096-24.042-13.863 0-24.202 11.704-27.888 25.07-1.797 6.515-4.512 12.025-8.145 16.53-7.619 9.448-18.057 14.171-31.314 14.171-8.229 0-15.696-1.981-22.4-5.942-6.553-4.115-15.543-10.972-26.972-20.572-16.914-14.171-31.772-24.686-44.572-31.543C217.168 131.505 203.073 128 187.53 128Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn fluxer_partner(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("width", "512"),
|
||||
attribute.attribute("height", "512"),
|
||||
attribute.attribute("viewBox", "0 0 512 512"),
|
||||
attribute.attribute("fill", "none"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M372.514 144.155C335.074 142.235 301.794 158.235 280.034 184.635C280.034 184.635 255.554 216.315 255.554 216.155C255.554 216.155 224.674 256.635 224.834 256.635L198.274 291.515L196.194 294.235C180.994 314.555 154.113 325.915 125.793 317.595C104.353 311.195 87.5529 293.275 82.7529 271.515C73.3129 230.075 104.833 193.115 144.673 193.115C164.034 193.115 178.434 201.275 188.354 209.915C198.914 219.195 215.074 217.755 223.714 206.555C231.234 196.795 230.274 182.715 221.314 174.395C170.754 127.995 75.0729 138.555 44.5129 203.035C4.83292 286.715 65.313 369.275 144.673 369.275C179.554 369.275 210.754 353.755 231.394 328.795L238.274 319.835C238.274 319.835 255.714 296.955 255.714 297.115C255.714 297.115 286.594 256.635 286.434 256.635L313.474 221.275C314.114 220.475 314.754 219.675 315.394 218.875C329.314 199.995 353.314 189.275 379.714 194.395C403.074 199.035 422.434 217.275 428.194 240.475C438.754 282.555 407.074 320.475 366.594 320.475C347.554 320.475 333.154 312.315 323.234 303.515C312.674 294.235 296.354 295.835 287.874 307.035C280.034 317.275 281.474 331.515 291.074 340.155C305.954 353.435 331.394 369.595 366.594 369.595C433.794 369.595 487.394 310.555 478.274 241.595C471.234 188.475 425.794 146.875 372.514 144.155Z",
|
||||
),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import gleam/option.{type Option}
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn fluxer_premium(
|
||||
fill_color: Option(String),
|
||||
attrs: List(attribute.Attribute(a)),
|
||||
) -> Element(a) {
|
||||
let fill = option.unwrap(fill_color, "white")
|
||||
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("width", "336"),
|
||||
attribute.attribute("height", "274"),
|
||||
attribute.attribute("viewBox", "0 0 336 274"),
|
||||
attribute.attribute("fill", "none"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute("fill-rule", "evenodd"),
|
||||
attribute.attribute("clip-rule", "evenodd"),
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M59.2774 198C59.2774 208.471 50.7886 216.96 40.3174 216.96C29.8461 216.96 21.3574 208.471 21.3574 198C21.3574 187.529 29.8461 179.04 40.3174 179.04C50.7886 179.04 59.2774 187.529 59.2774 198ZM314.398 198C314.398 208.471 305.909 216.96 295.438 216.96C284.966 216.96 276.478 208.471 276.478 198C276.478 187.529 284.966 179.04 295.438 179.04C305.909 179.04 314.398 187.529 314.398 198Z",
|
||||
),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
]),
|
||||
svg.path([
|
||||
attribute.attribute("fill-rule", "evenodd"),
|
||||
attribute.attribute("clip-rule", "evenodd"),
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M98.9976 75.24L137.158 126.48L58.0781 135.6L83.5181 76.92C77.3981 74.52 73.0781 68.52 73.0781 61.56C73.0781 52.44 80.5181 45 89.6381 45C98.7576 45 106.198 52.44 106.198 61.56C106.198 67.32 103.318 72.24 98.9976 75.24ZM252.12 76.92L277.56 135.6L198.48 126.48L236.64 75.24C232.32 72.24 229.44 67.2 229.44 61.56C229.44 52.44 236.88 45 246 45C255.12 45 262.56 52.44 262.56 61.56C262.56 68.52 258.24 74.52 252.12 76.92Z",
|
||||
),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
]),
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M335.76 73.08C335.76 63.96 328.32 56.52 319.2 56.52C310.08 56.52 302.64 63.96 302.64 73.08C302.64 76.44 303.72 79.68 305.4 82.2C302.76 82.2 300.12 82.8 297.6 84.48L250.44 114.96C244.2 118.92 236.04 117.48 231.6 111.48L179.16 40.32C178.8 39.72 178.32 39.24 177.84 38.76C184.2 35.2801 188.52 28.44 188.52 20.64C188.52 9.24 179.28 0 167.88 0C156.48 0 147.24 9.24 147.24 20.64C147.24 28.44 151.56 35.2801 157.92 38.76C157.44 39.24 156.96 39.72 156.6 40.32L104.16 111.48C99.84 117.48 91.56 118.92 85.32 114.96L38.16 84.48C35.64 82.8 33 82.2 30.36 82.2C32.16 79.56 33.12 76.44 33.12 73.08C33.12 63.96 25.68 56.52 16.56 56.52C7.44 56.52 0 63.96 0 73.08C0 82.2 7.44 89.64 16.56 89.64C17.16 89.64 17.76 89.64 18.24 89.52C16.68 92.28 16.08 95.52 16.8 99L48 254.64C50.28 265.8 60.12 273.84 71.52 273.84H264.24C275.64 273.84 285.48 265.8 287.76 254.64L318.96 99C319.68 95.52 318.96 92.16 317.52 89.52C318.12 89.52 318.72 89.64 319.2 89.64C328.32 89.64 335.76 82.2 335.76 73.08Z",
|
||||
),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
]),
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M167.88 228C184.448 228 197.88 214.568 197.88 198C197.88 181.432 184.448 168 167.88 168C151.311 168 137.88 181.432 137.88 198C137.88 214.568 151.311 228 167.88 228Z",
|
||||
),
|
||||
attribute.attribute("fill", fill),
|
||||
]),
|
||||
svg.path([
|
||||
attribute.attribute("fill-rule", "evenodd"),
|
||||
attribute.attribute("clip-rule", "evenodd"),
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M108 198C108 207.941 99.9408 216 90 216C80.0588 216 72 207.941 72 198C72 188.059 80.0588 180 90 180C99.9408 180 108 188.059 108 198ZM264 198C264 207.941 255.941 216 246 216C236.059 216 228 207.941 228 198C228 188.059 236.059 180 246 180C255.941 180 264 188.059 264 198Z",
|
||||
),
|
||||
attribute.attribute("fill", fill),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn fluxer_staff(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("width", "512"),
|
||||
attribute.attribute("height", "512"),
|
||||
attribute.attribute("viewBox", "0 0 512 512"),
|
||||
attribute.attribute("fill", "none"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M320.878 273.038L469.518 124.399C472.718 121.199 472.718 116.079 469.518 112.719L446.318 89.5191C443.118 86.3191 437.998 86.3191 434.638 89.5191L285.998 238.158C280.878 243.278 273.838 246.158 266.478 246.158C258.958 246.158 251.598 249.198 246.318 254.478L86.3188 414.638C81.5187 419.438 81.5187 427.278 86.3188 432.078L126.959 472.718C131.759 477.518 139.599 477.518 144.399 472.718L304.558 312.558C309.838 307.278 312.878 299.918 312.878 292.398C312.878 285.198 315.758 278.158 320.878 273.038Z",
|
||||
),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
]),
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M262.483 243.76L113.842 95.1207C110.642 91.9207 105.522 91.9207 102.162 95.1207L78.9625 118.481C75.7625 121.681 75.7625 126.801 78.9625 130.161L227.603 278.8C232.723 283.92 235.603 290.96 235.603 298.32C235.603 305.84 238.643 313.2 243.923 318.48L398.003 472.56C402.803 477.36 410.643 477.36 415.443 472.56L456.083 431.92C460.883 427.12 460.883 419.28 456.083 414.48L302.003 260.4C296.723 255.12 289.363 252.08 281.843 252.08C274.643 251.92 267.603 248.88 262.483 243.76Z",
|
||||
),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
]),
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M116.72 266.32L250.16 132.88C254.96 128.08 254.96 120.24 250.32 115.44L186.48 51.6C181.68 46.8 173.84 46.8 169.04 51.6L35.6 185.04C30.8 189.84 30.8 197.68 35.6 202.48L99.28 266.32C104.08 271.12 111.92 271.12 116.72 266.32Z",
|
||||
),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
]),
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M303.92 132.88L428.4 257.36C438.16 267.12 454 267.12 463.76 257.2C467.28 253.68 469.68 249.04 470.64 243.92L478.64 198.32C483.6 170 474.48 140.88 454 120.4L385.2 51.6C380.4 46.8 372.56 46.8 367.76 51.6L303.92 115.44C299.12 120.24 299.12 128.08 303.92 132.88Z",
|
||||
),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn game_controller(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M247.44,173.75a.68.68,0,0,0,0-.14L231.05,89.44c0-.06,0-.12,0-.18A60.08,60.08,0,0,0,172,40H83.89a59.88,59.88,0,0,0-59,49.52L8.58,173.61a.68.68,0,0,0,0,.14,36,36,0,0,0,60.9,31.71l.35-.37L109.52,160h37l39.71,45.09c.11.13.23.25.35.37A36.08,36.08,0,0,0,212,216a36,36,0,0,0,35.43-42.25ZM104,112H96v8a8,8,0,0,1-16,0v-8H72a8,8,0,0,1,0-16h8V88a8,8,0,0,1,16,0v8h8a8,8,0,0,1,0,16Zm40-8a8,8,0,0,1,8-8h24a8,8,0,0,1,0,16H152A8,8,0,0,1,144,104Zm84.37,87.47a19.84,19.84,0,0,1-12.9,8.23A20.09,20.09,0,0,1,198,194.31L167.8,160H172a60,60,0,0,0,51-28.38l8.74,45A19.82,19.82,0,0,1,228.37,191.47Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
39
fluxer_marketing/src/fluxer_marketing/icons/gear.gleam
Normal file
39
fluxer_marketing/src/fluxer_marketing/icons/gear.gleam
Normal file
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn gear(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M216,130.16q.06-2.16,0-4.32l14.92-18.64a8,8,0,0,0,1.48-7.06,107.6,107.6,0,0,0-10.88-26.25,8,8,0,0,0-6-3.93l-23.72-2.64q-1.48-1.56-3-3L186,40.54a8,8,0,0,0-3.94-6,107.29,107.29,0,0,0-26.25-10.86,8,8,0,0,0-7.06,1.48L130.16,40Q128,40,125.84,40L107.2,25.11a8,8,0,0,0-7.06-1.48A107.6,107.6,0,0,0,73.89,34.51a8,8,0,0,0-3.93,6L67.32,64.27q-1.56,1.49-3,3L40.54,70a8,8,0,0,0-6,3.94,107.71,107.71,0,0,0-10.87,26.25,8,8,0,0,0,1.49,7.06L40,125.84Q40,128,40,130.16L25.11,148.8a8,8,0,0,0-1.48,7.06,107.6,107.6,0,0,0,10.88,26.25,8,8,0,0,0,6,3.93l23.72,2.64q1.49,1.56,3,3L70,215.46a8,8,0,0,0,3.94,6,107.71,107.71,0,0,0,26.25,10.87,8,8,0,0,0,7.06-1.49L125.84,216q2.16.06,4.32,0l18.64,14.92a8,8,0,0,0,7.06,1.48,107.21,107.21,0,0,0,26.25-10.88,8,8,0,0,0,3.93-6l2.64-23.72q1.56-1.48,3-3L215.46,186a8,8,0,0,0,6-3.94,107.71,107.71,0,0,0,10.87-26.25,8,8,0,0,0-1.49-7.06ZM128,168a40,40,0,1,1,40-40A40,40,0,0,1,128,168Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
39
fluxer_marketing/src/fluxer_marketing/icons/gif.gleam
Normal file
39
fluxer_marketing/src/fluxer_marketing/icons/gif.gleam
Normal file
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn gif(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M216,40H40A16,16,0,0,0,24,56V200a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A16,16,0,0,0,216,40ZM112,144a32,32,0,0,1-64,0V112a32,32,0,0,1,55.85-21.33,8,8,0,1,1-11.92,10.66A16,16,0,0,0,64,112v32a16,16,0,0,0,32,0v-8H88a8,8,0,0,1,0-16h16a8,8,0,0,1,8,8Zm32,24a8,8,0,0,1-16,0V88a8,8,0,0,1,16,0Zm60-72H176v24h20a8,8,0,0,1,0,16H176v32a8,8,0,0,1-16,0V88a8,8,0,0,1,8-8h36a8,8,0,0,1,0,16Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
39
fluxer_marketing/src/fluxer_marketing/icons/gift.gleam
Normal file
39
fluxer_marketing/src/fluxer_marketing/icons/gift.gleam
Normal file
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn gift(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M216,72H180.92c.39-.33.79-.65,1.17-1A29.53,29.53,0,0,0,192,49.57,32.62,32.62,0,0,0,158.44,16,29.53,29.53,0,0,0,137,25.91a54.94,54.94,0,0,0-9,14.48,54.94,54.94,0,0,0-9-14.48A29.53,29.53,0,0,0,97.56,16,32.62,32.62,0,0,0,64,49.57,29.53,29.53,0,0,0,73.91,71c.38.33.78.65,1.17,1H40A16,16,0,0,0,24,88v32a16,16,0,0,0,16,16v64a16,16,0,0,0,16,16h60a4,4,0,0,0,4-4V120H40V88h80v32h16V88h80v32H136v92a4,4,0,0,0,4,4h60a16,16,0,0,0,16-16V136a16,16,0,0,0,16-16V88A16,16,0,0,0,216,72ZM84.51,59a13.69,13.69,0,0,1-4.5-10A16.62,16.62,0,0,1,96.59,32h.49a13.69,13.69,0,0,1,10,4.5c8.39,9.48,11.35,25.2,12.39,34.92C109.71,70.39,94,67.43,84.51,59Zm87,0c-9.49,8.4-25.24,11.36-35,12.4C137.7,60.89,141,45.5,149,36.51a13.69,13.69,0,0,1,10-4.5h.49A16.62,16.62,0,0,1,176,49.08,13.69,13.69,0,0,1,171.49,59Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
45
fluxer_marketing/src/fluxer_marketing/icons/github.gleam
Normal file
45
fluxer_marketing/src/fluxer_marketing/icons/github.gleam
Normal file
@@ -0,0 +1,45 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn github(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.rect([
|
||||
attribute.attribute("width", "256"),
|
||||
attribute.attribute("height", "256"),
|
||||
attribute.attribute("fill", "none"),
|
||||
]),
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M216,104v8a56.06,56.06,0,0,1-48.44,55.47A39.8,39.8,0,0,1,176,192v40a8,8,0,0,1-8,8H104a8,8,0,0,1-8-8V216H72a40,40,0,0,1-40-40A24,24,0,0,0,8,152a8,8,0,0,1,0-16,40,40,0,0,1,40,40,24,24,0,0,0,24,24H96v-8a39.8,39.8,0,0,1,8.44-24.53A56.06,56.06,0,0,1,56,112v-8a58.14,58.14,0,0,1,7.69-28.32A59.78,59.78,0,0,1,69.07,28,8,8,0,0,1,76,24a59.75,59.75,0,0,1,48,24h24a59.75,59.75,0,0,1,48-24,8,8,0,0,1,6.93,4,59.74,59.74,0,0,1,5.37,47.68A58,58,0,0,1,216,104Z",
|
||||
),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
39
fluxer_marketing/src/fluxer_marketing/icons/globe.gleam
Normal file
39
fluxer_marketing/src/fluxer_marketing/icons/globe.gleam
Normal file
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn globe(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M128,24h0A104,104,0,1,0,232,128,104.12,104.12,0,0,0,128,24Zm78.36,64H170.71a135.28,135.28,0,0,0-22.3-45.6A88.29,88.29,0,0,1,206.37,88ZM216,128a87.61,87.61,0,0,1-3.33,24H174.16a157.44,157.44,0,0,0,0-48h38.51A87.61,87.61,0,0,1,216,128ZM128,43a115.27,115.27,0,0,1,26,45H102A115.11,115.11,0,0,1,128,43ZM102,168H154a115.11,115.11,0,0,1-26,45A115.27,115.27,0,0,1,102,168Zm-3.9-16a140.84,140.84,0,0,1,0-48h59.88a140.84,140.84,0,0,1,0,48Zm50.35,61.6a135.28,135.28,0,0,0,22.3-45.6h35.66A88.29,88.29,0,0,1,148.41,213.6Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute.{type Attribute}
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn google_play(attributes: List(Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attributes
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M239.82,114.18,72,18.16a16,16,0,0,0-16.12,0A15.68,15.68,0,0,0,48,31.87V224.13a15.68,15.68,0,0,0,7.92,13.67,16,16,0,0,0,16.12,0l167.78-96a15.76,15.76,0,0,0,0-27.64ZM160,139.31l18.92,18.92-88.5,50.66ZM90.4,47.1l88.53,50.67L160,116.69ZM193.31,150l-22-22,22-22,38.43,22Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn graduation_cap(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M176,207.24a119,119,0,0,0,16-7.73V240a8,8,0,0,1-16,0Zm11.76-88.43-56-29.87a8,8,0,0,0-7.52,14.12L171,128l17-9.06Zm64-29.87-120-64a8,8,0,0,0-7.52,0l-120,64a8,8,0,0,0,0,14.12L32,117.87v48.42a15.91,15.91,0,0,0,4.06,10.65C49.16,191.53,78.51,216,128,216a130,130,0,0,0,48-8.76V130.67L171,128l-43,22.93L43.83,106l0,0L25,96,128,41.07,231,96l-18.78,10-.06,0L188,118.94a8,8,0,0,1,4,6.93v73.64a115.63,115.63,0,0,0,27.94-22.57A15.91,15.91,0,0,0,224,166.29V117.87l27.76-14.81a8,8,0,0,0,0-14.12Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
61
fluxer_marketing/src/fluxer_marketing/icons/hash.gleam
Normal file
61
fluxer_marketing/src/fluxer_marketing/icons/hash.gleam
Normal file
@@ -0,0 +1,61 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn hash(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "none"),
|
||||
attribute.attribute("stroke", "currentColor"),
|
||||
attribute.attribute("stroke-linecap", "round"),
|
||||
attribute.attribute("stroke-linejoin", "round"),
|
||||
attribute.attribute("stroke-width", "24"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.line([
|
||||
attribute.attribute("x1", "48"),
|
||||
attribute.attribute("y1", "96"),
|
||||
attribute.attribute("x2", "224"),
|
||||
attribute.attribute("y2", "96"),
|
||||
]),
|
||||
svg.line([
|
||||
attribute.attribute("x1", "176"),
|
||||
attribute.attribute("y1", "40"),
|
||||
attribute.attribute("x2", "144"),
|
||||
attribute.attribute("y2", "216"),
|
||||
]),
|
||||
svg.line([
|
||||
attribute.attribute("x1", "112"),
|
||||
attribute.attribute("y1", "40"),
|
||||
attribute.attribute("x2", "80"),
|
||||
attribute.attribute("y2", "216"),
|
||||
]),
|
||||
svg.line([
|
||||
attribute.attribute("x1", "32"),
|
||||
attribute.attribute("y1", "160"),
|
||||
attribute.attribute("x2", "208"),
|
||||
attribute.attribute("y2", "160"),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
39
fluxer_marketing/src/fluxer_marketing/icons/heart.gleam
Normal file
39
fluxer_marketing/src/fluxer_marketing/icons/heart.gleam
Normal file
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn heart(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M240,102c0,70-103.79,126.66-108.21,129a8,8,0,0,1-7.58,0C119.79,228.66,16,172,16,102A62.07,62.07,0,0,1,78,40c20.65,0,38.73,8.88,50,23.89C139.27,48.88,157.35,40,178,40A62.07,62.07,0,0,1,240,102Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
43
fluxer_marketing/src/fluxer_marketing/icons/infinity.gleam
Normal file
43
fluxer_marketing/src/fluxer_marketing/icons/infinity.gleam
Normal file
@@ -0,0 +1,43 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn infinity(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "none"),
|
||||
attribute.attribute("stroke", "currentColor"),
|
||||
attribute.attribute("stroke-linecap", "round"),
|
||||
attribute.attribute("stroke-linejoin", "round"),
|
||||
attribute.attribute("stroke-width", "24"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M101.28,158.17l-3.34,3.77a48,48,0,1,1,0-67.88l60.12,67.88a48,48,0,1,0,0-67.88l-3.34,3.77",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
39
fluxer_marketing/src/fluxer_marketing/icons/lightning.gleam
Normal file
39
fluxer_marketing/src/fluxer_marketing/icons/lightning.gleam
Normal file
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn lightning(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M213.85,125.46l-112,120a8,8,0,0,1-13.69-7l14.66-73.33L45.19,143.49a8,8,0,0,1-3-13l112-120a8,8,0,0,1,13.69,7L153.18,90.9l57.63,21.61a8,8,0,0,1,3,12.95Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
39
fluxer_marketing/src/fluxer_marketing/icons/link.gleam
Normal file
39
fluxer_marketing/src/fluxer_marketing/icons/link.gleam
Normal file
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn link(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M117.18,188.74a12,12,0,0,1,0,17l-5.12,5.12A58.26,58.26,0,0,1,70.6,228h0A58.62,58.62,0,0,1,29.14,127.92L63.89,93.17a58.64,58.64,0,0,1,98.56,28.11,12,12,0,1,1-23.37,5.44,34.65,34.65,0,0,0-58.22-16.58L46.11,144.89A34.62,34.62,0,0,0,70.57,204h0a34.41,34.41,0,0,0,24.49-10.14l5.11-5.12A12,12,0,0,1,117.18,188.74ZM226.83,45.17a58.65,58.65,0,0,0-82.93,0l-5.11,5.11a12,12,0,0,0,17,17l5.12-5.12a34.63,34.63,0,1,1,49,49L175.1,145.86A34.39,34.39,0,0,1,150.61,156h0a34.63,34.63,0,0,1-33.69-26.72,12,12,0,0,0-23.38,5.44A58.64,58.64,0,0,0,150.56,180h.05a58.28,58.28,0,0,0,41.47-17.17l34.75-34.75a58.62,58.62,0,0,0,0-82.91Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
39
fluxer_marketing/src/fluxer_marketing/icons/linux.gleam
Normal file
39
fluxer_marketing/src/fluxer_marketing/icons/linux.gleam
Normal file
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute.{type Attribute}
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn linux(attributes: List(Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attributes
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M161.22,209.74a4,4,0,0,1-3.31,6.26H98.1a4,4,0,0,1-3.31-6.26,40,40,0,0,1,66.43,0Zm68.93,3.37a8.29,8.29,0,0,1-6.43,2.89H184.56a4,4,0,0,1-3.76-2.65,56,56,0,0,0-105.59,0A4,4,0,0,1,71.45,216H32.23a8.2,8.2,0,0,1-6.42-2.93A8,8,0,0,1,25.75,203c.06-.07,7.64-9.78,15.12-28.72C47.77,156.8,56,127.64,56,88a72,72,0,0,1,144,0c0,39.64,8.23,68.8,15.13,86.28,7.48,18.94,15.06,28.65,15.13,28.74A8,8,0,0,1,230.15,213.11ZM88,100a12,12,0,1,0,12-12A12,12,0,0,0,88,100Zm79.16,32.42a8,8,0,0,0-10.73-3.58L128,143.06,99.58,128.84a8,8,0,0,0-7.15,14.32l32,16a8,8,0,0,0,7.15,0l32-16A8,8,0,0,0,167.16,132.42ZM168,100a12,12,0,1,0-12,12A12,12,0,0,0,168,100Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn magnifying_glass(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M168,112a56,56,0,1,1-56-56A56,56,0,0,1,168,112Zm61.66,117.66a8,8,0,0,1-11.32,0l-50.06-50.07a88,88,0,1,1,11.32-11.31l50.06,50.06A8,8,0,0,1,229.66,229.66ZM112,184a72,72,0,1,0-72-72A72.08,72.08,0,0,0,112,184Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
39
fluxer_marketing/src/fluxer_marketing/icons/medal.gleam
Normal file
39
fluxer_marketing/src/fluxer_marketing/icons/medal.gleam
Normal file
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn medal(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M216,96A88,88,0,1,0,72,163.83V240a8,8,0,0,0,11.58,7.16L128,225l44.43,22.21A8.07,8.07,0,0,0,176,248a8,8,0,0,0,8-8V163.83A87.85,87.85,0,0,0,216,96ZM56,96a72,72,0,1,1,72,72A72.08,72.08,0,0,1,56,96Zm16,0a56,56,0,1,1,56,56A56.06,56.06,0,0,1,72,96Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
55
fluxer_marketing/src/fluxer_marketing/icons/menu.gleam
Normal file
55
fluxer_marketing/src/fluxer_marketing/icons/menu.gleam
Normal file
@@ -0,0 +1,55 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn menu(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "none"),
|
||||
attribute.attribute("stroke", "currentColor"),
|
||||
attribute.attribute("stroke-linecap", "round"),
|
||||
attribute.attribute("stroke-linejoin", "round"),
|
||||
attribute.attribute("stroke-width", "24"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.line([
|
||||
attribute.attribute("x1", "40"),
|
||||
attribute.attribute("y1", "64"),
|
||||
attribute.attribute("x2", "216"),
|
||||
attribute.attribute("y2", "64"),
|
||||
]),
|
||||
svg.line([
|
||||
attribute.attribute("x1", "40"),
|
||||
attribute.attribute("y1", "128"),
|
||||
attribute.attribute("x2", "216"),
|
||||
attribute.attribute("y2", "128"),
|
||||
]),
|
||||
svg.line([
|
||||
attribute.attribute("x1", "40"),
|
||||
attribute.attribute("y1", "192"),
|
||||
attribute.attribute("x2", "216"),
|
||||
attribute.attribute("y2", "192"),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
39
fluxer_marketing/src/fluxer_marketing/icons/microphone.gleam
Normal file
39
fluxer_marketing/src/fluxer_marketing/icons/microphone.gleam
Normal file
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn microphone(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M80,128V64a48,48,0,0,1,96,0v64a48,48,0,0,1-96,0Zm128,0a8,8,0,0,0-16,0,64,64,0,0,1-128,0,8,8,0,0,0-16,0,80.11,80.11,0,0,0,72,79.6V240a8,8,0,0,0,16,0V207.6A80.11,80.11,0,0,0,208,128Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
39
fluxer_marketing/src/fluxer_marketing/icons/newspaper.gleam
Normal file
39
fluxer_marketing/src/fluxer_marketing/icons/newspaper.gleam
Normal file
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn newspaper(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M216,48H56A16,16,0,0,0,40,64V184a8,8,0,0,1-16,0V88A8,8,0,0,0,8,88v96.11A24,24,0,0,0,32,208H208a24,24,0,0,0,24-24V64A16,16,0,0,0,216,48ZM176,152H96a8,8,0,0,1,0-16h80a8,8,0,0,1,0,16Zm0-32H96a8,8,0,0,1,0-16h80a8,8,0,0,1,0,16Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
39
fluxer_marketing/src/fluxer_marketing/icons/palette.gleam
Normal file
39
fluxer_marketing/src/fluxer_marketing/icons/palette.gleam
Normal file
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn palette(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M200.77,53.89A103.27,103.27,0,0,0,128,24h-1.07A104,104,0,0,0,24,128c0,43,26.58,79.06,69.36,94.17A32,32,0,0,0,136,192a16,16,0,0,1,16-16h46.21a31.81,31.81,0,0,0,31.2-24.88,104.43,104.43,0,0,0,2.59-24A103.28,103.28,0,0,0,200.77,53.89ZM84,168a12,12,0,1,1,12-12A12,12,0,0,1,84,168Zm0-56a12,12,0,1,1,12-12A12,12,0,0,1,84,112Zm44-24a12,12,0,1,1,12-12A12,12,0,0,1,128,88Zm44,24a12,12,0,1,1,12-12A12,12,0,0,1,172,112Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
43
fluxer_marketing/src/fluxer_marketing/icons/paperclip.gleam
Normal file
43
fluxer_marketing/src/fluxer_marketing/icons/paperclip.gleam
Normal file
@@ -0,0 +1,43 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn paperclip(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "none"),
|
||||
attribute.attribute("stroke", "currentColor"),
|
||||
attribute.attribute("stroke-linecap", "round"),
|
||||
attribute.attribute("stroke-linejoin", "round"),
|
||||
attribute.attribute("stroke-width", "24"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M96,176l95.8-92.2a28,28,0,0,0-39.59-39.6L54.06,142.06a48,48,0,0,0,67.88,67.88L204,128",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
39
fluxer_marketing/src/fluxer_marketing/icons/percent.gleam
Normal file
39
fluxer_marketing/src/fluxer_marketing/icons/percent.gleam
Normal file
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn percent(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M176,172a12,12,0,1,1-12-12A12,12,0,0,1,176,172ZM92,96A12,12,0,1,0,80,84,12,12,0,0,0,92,96ZM224,48V208a16,16,0,0,1-16,16H48a16,16,0,0,1-16-16V48A16,16,0,0,1,48,32H208A16,16,0,0,1,224,48ZM64,84A28,28,0,1,0,92,56,28,28,0,0,0,64,84Zm128,88a28,28,0,1,0-28,28A28,28,0,0,0,192,172ZM189.66,66.34a8,8,0,0,0-11.32,0l-112,112a8,8,0,0,0,11.32,11.32l112-112A8,8,0,0,0,189.66,66.34Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
39
fluxer_marketing/src/fluxer_marketing/icons/question.gleam
Normal file
39
fluxer_marketing/src/fluxer_marketing/icons/question.gleam
Normal file
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn question(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,168a12,12,0,1,1,12-12A12,12,0,0,1,128,192Zm8-48.72V144a8,8,0,0,1-16,0v-8a8,8,0,0,1,8-8c13.23,0,24-9,24-20s-10.77-20-24-20-24,9-24,20v4a8,8,0,0,1-16,0v-4c0-19.85,17.94-36,40-36s40,16.15,40,36C168,125.38,154.24,139.93,136,143.28Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
39
fluxer_marketing/src/fluxer_marketing/icons/rocket.gleam
Normal file
39
fluxer_marketing/src/fluxer_marketing/icons/rocket.gleam
Normal file
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn rocket(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M152,224a8,8,0,0,1-8,8H112a8,8,0,0,1,0-16h32A8,8,0,0,1,152,224Zm71.62-68.17-12.36,55.63a16,16,0,0,1-25.51,9.11L158.51,200h-61L70.25,220.57a16,16,0,0,1-25.51-9.11L32.38,155.83a16.09,16.09,0,0,1,3.32-13.71l28.56-34.26a123.07,123.07,0,0,1,8.57-36.67c12.9-32.34,36-52.63,45.37-59.85a16,16,0,0,1,19.6,0c9.34,7.22,32.47,27.51,45.37,59.85a123.07,123.07,0,0,1,8.57,36.67l28.56,34.26A16.09,16.09,0,0,1,223.62,155.83Zm-139.23,34Q68.28,160.5,64.83,132.16L48,152.36,60.36,208l.18-.13ZM140,100a12,12,0,1,0-12,12A12,12,0,0,0,140,100Zm68,52.36-16.83-20.2q-3.42,28.28-19.56,57.69l23.85,18,.18.13Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn rocket_launch(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M101.85,191.14C97.34,201,82.29,224,40,224a8,8,0,0,1-8-8c0-42.29,23-57.34,32.86-61.85a8,8,0,0,1,6.64,14.56c-6.43,2.93-20.62,12.36-23.12,38.91,26.55-2.5,36-16.69,38.91-23.12a8,8,0,1,1,14.56,6.64Zm122-144a16,16,0,0,0-15-15c-12.58-.75-44.73.4-71.4,27.07h0L88,108.7A8,8,0,0,1,76.67,97.39l26.56-26.57A4,4,0,0,0,100.41,64H74.35A15.9,15.9,0,0,0,63,68.68L28.7,103a16,16,0,0,0,9.07,27.16l38.47,5.37,44.21,44.21,5.37,38.49a15.94,15.94,0,0,0,10.78,12.92,16.11,16.11,0,0,0,5.1.83A15.91,15.91,0,0,0,153,227.3L187.32,193A16,16,0,0,0,192,181.65V155.59a4,4,0,0,0-6.83-2.82l-26.57,26.56a8,8,0,0,1-11.71-.42,8.2,8.2,0,0,1,.6-11.1l49.27-49.27h0C223.45,91.86,224.6,59.71,223.85,47.12Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
46
fluxer_marketing/src/fluxer_marketing/icons/rss.gleam
Normal file
46
fluxer_marketing/src/fluxer_marketing/icons/rss.gleam
Normal file
@@ -0,0 +1,46 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn rss(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "none"),
|
||||
attribute.attribute("stroke", "currentColor"),
|
||||
attribute.attribute("stroke-linecap", "round"),
|
||||
attribute.attribute("stroke-linejoin", "round"),
|
||||
attribute.attribute("stroke-width", "24"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([attribute.attribute("d", "M56,136a64,64,0,0,1,64,64")]),
|
||||
svg.path([attribute.attribute("d", "M56,88A112,112,0,0,1,168,200")]),
|
||||
svg.path([attribute.attribute("d", "M56,40A160,160,0,0,1,216,200")]),
|
||||
svg.circle([
|
||||
attribute.attribute("cx", "60"),
|
||||
attribute.attribute("cy", "196"),
|
||||
attribute.attribute("r", "16"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
39
fluxer_marketing/src/fluxer_marketing/icons/seal_check.gleam
Normal file
39
fluxer_marketing/src/fluxer_marketing/icons/seal_check.gleam
Normal file
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn seal_check(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M225.86,102.82c-3.77-3.94-7.67-8-9.14-11.57-1.36-3.27-1.44-8.69-1.52-13.94-.15-9.76-.31-20.82-8-28.51s-18.75-7.85-28.51-8c-5.25-.08-10.67-.16-13.94-1.52-3.56-1.47-7.63-5.37-11.57-9.14C146.28,23.51,138.44,16,128,16s-18.27,7.51-25.18,14.14c-3.94,3.77-8,7.67-11.57,9.14C88,40.64,82.56,40.72,77.31,40.8c-9.76.15-20.82.31-28.51,8S41,67.55,40.8,77.31c-.08,5.25-.16,10.67-1.52,13.94-1.47,3.56-5.37,7.63-9.14,11.57C23.51,109.72,16,117.56,16,128s7.51,18.27,14.14,25.18c3.77,3.94,7.67,8,9.14,11.57,1.36,3.27,1.44,8.69,1.52,13.94.15,9.76.31,20.82,8,28.51s18.75,7.85,28.51,8c5.25.08,10.67.16,13.94,1.52,3.56,1.47,7.63,5.37,11.57,9.14C109.72,232.49,117.56,240,128,240s18.27-7.51,25.18-14.14c3.94-3.77,8-7.67,11.57-9.14,3.27-1.36,8.69-1.44,13.94-1.52,9.76-.15,20.82-.31,28.51-8s7.85-18.75,8-28.51c.08-5.25.16-10.67,1.52-13.94,1.47-3.56,5.37-7.63,9.14-11.57C232.49,146.28,240,138.44,240,128S232.49,109.73,225.86,102.82Zm-52.2,6.84-56,56a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35a8,8,0,0,1,11.32,11.32Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn shield_check(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M208,40H48A16,16,0,0,0,32,56v56c0,52.72,25.52,84.67,46.93,102.19,23.06,18.86,46,25.26,47,25.53a8,8,0,0,0,4.2,0c1-.27,23.91-6.67,47-25.53C198.48,196.67,224,164.72,224,112V56A16,16,0,0,0,208,40Zm-34.32,69.66-56,56a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35a8,8,0,0,1,11.32,11.32Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn shopping_cart(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M230.14,58.87A8,8,0,0,0,224,56H62.68L56.6,22.57A8,8,0,0,0,48.73,16H24a8,8,0,0,0,0,16h18L67.56,172.29a24,24,0,0,0,5.33,11.27,28,28,0,1,0,44.4,8.44h45.42A27.75,27.75,0,0,0,160,204a28,28,0,1,0,28-28H91.17a8,8,0,0,1-7.87-6.57L80.13,152h116a24,24,0,0,0,23.61-19.71l12.16-66.86A8,8,0,0,0,230.14,58.87ZM104,204a12,12,0,1,1-12-12A12,12,0,0,1,104,204Zm96,0a12,12,0,1,1-12-12A12,12,0,0,1,200,204Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
39
fluxer_marketing/src/fluxer_marketing/icons/smiley.gleam
Normal file
39
fluxer_marketing/src/fluxer_marketing/icons/smiley.gleam
Normal file
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn smiley(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24ZM92,96a12,12,0,1,1-12,12A12,12,0,0,1,92,96Zm82.92,60c-10.29,17.79-27.39,28-46.92,28s-36.63-10.2-46.92-28a8,8,0,1,1,13.84-8c7.47,12.91,19.21,20,33.08,20s25.61-7.1,33.08-20a8,8,0,1,1,13.84,8ZM164,120a12,12,0,1,1,12-12A12,12,0,0,1,164,120Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
39
fluxer_marketing/src/fluxer_marketing/icons/sparkle.gleam
Normal file
39
fluxer_marketing/src/fluxer_marketing/icons/sparkle.gleam
Normal file
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn sparkle(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M208,144a15.78,15.78,0,0,1-10.42,14.94L146,178l-19,51.62a15.92,15.92,0,0,1-29.88,0L78,178l-51.62-19a15.92,15.92,0,0,1,0-29.88L78,110l19-51.62a15.92,15.92,0,0,1,29.88,0L146,110l51.62,19A15.78,15.78,0,0,1,208,144ZM152,48h16V64a8,8,0,0,0,16,0V48h16a8,8,0,0,0,0-16H184V16a8,8,0,0,0-16,0V32H152a8,8,0,0,0,0,16Zm88,32h-8V72a8,8,0,0,0-16,0v8h-8a8,8,0,0,0,0,16h8v8a8,8,0,0,0,16,0V96h8a8,8,0,0,0,0-16Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn speaker_high(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M160,32.25V223.69a8.29,8.29,0,0,1-3.91,7.18,8,8,0,0,1-9-.56l-65.57-51A4,4,0,0,1,80,176.16V79.84a4,4,0,0,1,1.55-3.15l65.57-51a8,8,0,0,1,10,.16A8.27,8.27,0,0,1,160,32.25ZM60,80H32A16,16,0,0,0,16,96v64a16,16,0,0,0,16,16H60a4,4,0,0,0,4-4V84A4,4,0,0,0,60,80Zm126.77,20.84a8,8,0,0,0-.72,11.3,24,24,0,0,1,0,31.72,8,8,0,1,0,12,10.58,40,40,0,0,0,0-52.88A8,8,0,0,0,186.74,100.84Zm40.89-26.17a8,8,0,1,0-11.92,10.66,64,64,0,0,1,0,85.34,8,8,0,1,0,11.92,10.66,80,80,0,0,0,0-106.66Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
186
fluxer_marketing/src/fluxer_marketing/icons/swish.gleam
Normal file
186
fluxer_marketing/src/fluxer_marketing/icons/swish.gleam
Normal file
@@ -0,0 +1,186 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn swish(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("xmlns:xlink", "http://www.w3.org/1999/xlink"),
|
||||
attribute.attribute("viewBox", "0 0 420 420"),
|
||||
attribute.attribute("fill-rule", "evenodd"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.defs([], [
|
||||
svg.linear_gradient(
|
||||
[
|
||||
attribute.id("swish-grad-1"),
|
||||
attribute.attribute("x1", "-746"),
|
||||
attribute.attribute("y1", "822.6"),
|
||||
attribute.attribute("x2", "-746.2"),
|
||||
attribute.attribute("y2", "823.1"),
|
||||
attribute.attribute(
|
||||
"gradientTransform",
|
||||
"translate(224261.6 305063) scale(300.3 -370.5)",
|
||||
),
|
||||
attribute.attribute("gradientUnits", "userSpaceOnUse"),
|
||||
],
|
||||
[
|
||||
svg.stop([
|
||||
attribute.attribute("offset", "0"),
|
||||
attribute.attribute("stop-color", "#ef2131"),
|
||||
]),
|
||||
svg.stop([
|
||||
attribute.attribute("offset", "1"),
|
||||
attribute.attribute("stop-color", "#fecf2c"),
|
||||
]),
|
||||
],
|
||||
),
|
||||
svg.linear_gradient(
|
||||
[
|
||||
attribute.id("swish-grad-2"),
|
||||
attribute.attribute("x1", "-745.4"),
|
||||
attribute.attribute("y1", "823"),
|
||||
attribute.attribute("x2", "-745.9"),
|
||||
attribute.attribute("y2", "822.1"),
|
||||
attribute.attribute(
|
||||
"gradientTransform",
|
||||
"translate(204470.4 247194.2) scale(273.8 -300.2)",
|
||||
),
|
||||
attribute.attribute("gradientUnits", "userSpaceOnUse"),
|
||||
],
|
||||
[
|
||||
svg.stop([
|
||||
attribute.attribute("offset", "0"),
|
||||
attribute.attribute("stop-color", "#fbc52c"),
|
||||
]),
|
||||
svg.stop([
|
||||
attribute.attribute("offset", ".3"),
|
||||
attribute.attribute("stop-color", "#f87130"),
|
||||
]),
|
||||
svg.stop([
|
||||
attribute.attribute("offset", ".6"),
|
||||
attribute.attribute("stop-color", "#ef52e2"),
|
||||
]),
|
||||
svg.stop([
|
||||
attribute.attribute("offset", "1"),
|
||||
attribute.attribute("stop-color", "#661eec"),
|
||||
]),
|
||||
],
|
||||
),
|
||||
svg.linear_gradient(
|
||||
[
|
||||
attribute.id("swish-grad-3"),
|
||||
attribute.attribute("x1", "-746"),
|
||||
attribute.attribute("y1", "823"),
|
||||
attribute.attribute("x2", "-745.8"),
|
||||
attribute.attribute("y2", "822.5"),
|
||||
attribute.attribute(
|
||||
"gradientTransform",
|
||||
"translate(224142 305014) scale(300.3 -370.5)",
|
||||
),
|
||||
attribute.attribute("gradientUnits", "userSpaceOnUse"),
|
||||
],
|
||||
[
|
||||
svg.stop([
|
||||
attribute.attribute("offset", "0"),
|
||||
attribute.attribute("stop-color", "#78f6d8"),
|
||||
]),
|
||||
svg.stop([
|
||||
attribute.attribute("offset", ".3"),
|
||||
attribute.attribute("stop-color", "#77d1f6"),
|
||||
]),
|
||||
svg.stop([
|
||||
attribute.attribute("offset", ".6"),
|
||||
attribute.attribute("stop-color", "#70a4f3"),
|
||||
]),
|
||||
svg.stop([
|
||||
attribute.attribute("offset", "1"),
|
||||
attribute.attribute("stop-color", "#661eec"),
|
||||
]),
|
||||
],
|
||||
),
|
||||
svg.linear_gradient(
|
||||
[
|
||||
attribute.id("swish-grad-4"),
|
||||
attribute.attribute("x1", "-746.1"),
|
||||
attribute.attribute("y1", "822.3"),
|
||||
attribute.attribute("x2", "-745.6"),
|
||||
attribute.attribute("y2", "823.2"),
|
||||
attribute.attribute(
|
||||
"gradientTransform",
|
||||
"translate(204377.3 247074.5) scale(273.8 -300.2)",
|
||||
),
|
||||
attribute.attribute("gradientUnits", "userSpaceOnUse"),
|
||||
],
|
||||
[
|
||||
svg.stop([
|
||||
attribute.attribute("offset", "0"),
|
||||
attribute.attribute("stop-color", "#536eed"),
|
||||
]),
|
||||
svg.stop([
|
||||
attribute.attribute("offset", ".2"),
|
||||
attribute.attribute("stop-color", "#54c3ec"),
|
||||
]),
|
||||
svg.stop([
|
||||
attribute.attribute("offset", ".6"),
|
||||
attribute.attribute("stop-color", "#64d769"),
|
||||
]),
|
||||
svg.stop([
|
||||
attribute.attribute("offset", "1"),
|
||||
attribute.attribute("stop-color", "#fecf2c"),
|
||||
]),
|
||||
],
|
||||
),
|
||||
]),
|
||||
svg.g([], [
|
||||
svg.path([
|
||||
attribute.attribute("fill", "url(#swish-grad-1)"),
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M119.3,399.2c84.3,40.3,188.3,20.4,251.2-54.5,74.5-88.8,62.9-221.1-25.8-295.5l-59,70.3c69.3,58.2,78.4,161.5,20.2,230.9-46.4,55.3-122.8,73.7-186.5,48.9",
|
||||
),
|
||||
]),
|
||||
svg.path([
|
||||
attribute.attribute("fill", "url(#swish-grad-2)"),
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M119.3,399.2c84.3,40.3,188.3,20.4,251.2-54.5,7.7-9.2,14.5-18.8,20.3-28.8,9.9-61.7-11.9-126.9-63.2-169.9-13-10.9-27.2-19.8-41.9-26.5,69.3,58.2,78.4,161.5,20.2,230.9-46.4,55.3-122.8,73.7-186.5,48.9",
|
||||
),
|
||||
]),
|
||||
svg.path([
|
||||
attribute.attribute("fill", "url(#swish-grad-3)"),
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M300.3,20.4C216-19.9,111.9,0,49.1,74.9c-74.5,88.8-62.9,221.1,25.8,295.5l59-70.3c-69.3-58.2-78.4-161.5-20.2-230.9C160.2,14,236.6-4.5,300.3,20.4",
|
||||
),
|
||||
]),
|
||||
svg.path([
|
||||
attribute.attribute("fill", "url(#swish-grad-4)"),
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M300.3,20.4C216-19.9,111.9,0,49.1,74.9c-7.7,9.2-14.5,18.8-20.3,28.8-9.9,61.7,11.9,126.9,63.2,169.9,13,10.9,27.2,19.8,41.9,26.5-69.3-58.2-78.4-161.5-20.2-230.9C160.2,14,236.6-4.5,300.3,20.4",
|
||||
),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
58
fluxer_marketing/src/fluxer_marketing/icons/translate.gleam
Normal file
58
fluxer_marketing/src/fluxer_marketing/icons/translate.gleam
Normal file
@@ -0,0 +1,58 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn translate(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "none"),
|
||||
attribute.attribute("stroke", "currentColor"),
|
||||
attribute.attribute("stroke-linecap", "round"),
|
||||
attribute.attribute("stroke-linejoin", "round"),
|
||||
attribute.attribute("stroke-width", "24"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.polyline([attribute.attribute("points", "240,216 184,104 128,216")]),
|
||||
svg.line([
|
||||
attribute.attribute("x1", "144"),
|
||||
attribute.attribute("y1", "184"),
|
||||
attribute.attribute("x2", "224"),
|
||||
attribute.attribute("y2", "184"),
|
||||
]),
|
||||
svg.line([
|
||||
attribute.attribute("x1", "96"),
|
||||
attribute.attribute("y1", "32"),
|
||||
attribute.attribute("x2", "96"),
|
||||
attribute.attribute("y2", "56"),
|
||||
]),
|
||||
svg.line([
|
||||
attribute.attribute("x1", "32"),
|
||||
attribute.attribute("y1", "56"),
|
||||
attribute.attribute("x2", "160"),
|
||||
attribute.attribute("y2", "56"),
|
||||
]),
|
||||
svg.path([attribute.attribute("d", "M128,56a96,96,0,0,1-96,96")]),
|
||||
svg.path([attribute.attribute("d", "M72.7,96A96,96,0,0,0,160,152")]),
|
||||
],
|
||||
)
|
||||
}
|
||||
39
fluxer_marketing/src/fluxer_marketing/icons/tshirt.gleam
Normal file
39
fluxer_marketing/src/fluxer_marketing/icons/tshirt.gleam
Normal file
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn tshirt(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M247.59,61.22,195.83,33A8,8,0,0,0,192,32H160a8,8,0,0,0-8,8,24,24,0,0,1-48,0,8,8,0,0,0-8-8H64a8,8,0,0,0-3.84,1L8.41,61.22A15.76,15.76,0,0,0,1.82,82.48l19.27,36.81A16.37,16.37,0,0,0,35.67,128H56v80a16,16,0,0,0,16,16H184a16,16,0,0,0,16-16V128h20.34a16.37,16.37,0,0,0,14.58-8.71l19.27-36.81A15.76,15.76,0,0,0,247.59,61.22ZM35.67,112a.62.62,0,0,1-.41-.13L16.09,75.26,56,53.48V112Zm185.07-.14a.55.55,0,0,1-.41.14H200V53.48l39.92,21.78Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn user_circle(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M172,120a44,44,0,1,1-44-44A44.05,44.05,0,0,1,172,120Zm60,8A104,104,0,1,1,128,24,104.11,104.11,0,0,1,232,128Zm-16,0a88.09,88.09,0,0,0-91.47-87.93C77.43,41.89,39.87,81.12,40,128.25a87.65,87.65,0,0,0,22.24,58.16A79.71,79.71,0,0,1,84,165.1a4,4,0,0,1,4.83.32,59.83,59.83,0,0,0,78.28,0,4,4,0,0,1,4.83-.32,79.71,79.71,0,0,1,21.79,21.31A87.62,87.62,0,0,0,216,128Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
39
fluxer_marketing/src/fluxer_marketing/icons/user_plus.gleam
Normal file
39
fluxer_marketing/src/fluxer_marketing/icons/user_plus.gleam
Normal file
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn user_plus(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M256,136a8,8,0,0,1-8,8H232v16a8,8,0,0,1-16,0V144H200a8,8,0,0,1,0-16h16V112a8,8,0,0,1,16,0v16h16A8,8,0,0,1,256,136ZM144,157.68a68,68,0,1,0-71.9,0c-20.65,6.76-39.23,19.39-54.17,37.17A8,8,0,0,0,24,208H192a8,8,0,0,0,6.13-13.15C183.18,177.07,164.6,164.44,144,157.68Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn users_three(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M64.12,147.8a4,4,0,0,1-4,4.2H16a8,8,0,0,1-7.8-6.17,8.35,8.35,0,0,1,1.62-6.93A67.79,67.79,0,0,1,37,117.51a40,40,0,1,1,66.46-35.8,3.94,3.94,0,0,1-2.27,4.18A64.08,64.08,0,0,0,64,144C64,145.28,64,146.54,64.12,147.8Zm182-8.91A67.76,67.76,0,0,0,219,117.51a40,40,0,1,0-66.46-35.8,3.94,3.94,0,0,0,2.27,4.18A64.08,64.08,0,0,1,192,144c0,1.28,0,2.54-.12,3.8a4,4,0,0,0,4,4.2H240a8,8,0,0,0,7.8-6.17A8.33,8.33,0,0,0,246.17,138.89Zm-89,43.18a48,48,0,1,0-58.37,0A72.13,72.13,0,0,0,65.07,212,8,8,0,0,0,72,224H184a8,8,0,0,0,6.93-12A72.15,72.15,0,0,0,157.19,182.07Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
39
fluxer_marketing/src/fluxer_marketing/icons/video.gleam
Normal file
39
fluxer_marketing/src/fluxer_marketing/icons/video.gleam
Normal file
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn video(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M232,208a8,8,0,0,1-8,8H32a8,8,0,0,1,0-16H224A8,8,0,0,1,232,208Zm0-152V168a16,16,0,0,1-16,16H40a16,16,0,0,1-16-16V56A16,16,0,0,1,40,40H216A16,16,0,0,1,232,56Zm-68,56a8,8,0,0,0-3.41-6.55l-40-28A8,8,0,0,0,108,84v56a8,8,0,0,0,12.59,6.55l40-28A8,8,0,0,0,164,112Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn video_camera(attrs: List(attribute.Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attrs
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M192,72V184a16,16,0,0,1-16,16H32a16,16,0,0,1-16-16V72A16,16,0,0,1,32,56H176A16,16,0,0,1,192,72Zm58,.25a8.23,8.23,0,0,0-6.63,1.22L209.78,95.86A4,4,0,0,0,208,99.19v57.62a4,4,0,0,0,1.78,3.33l33.78,22.52a8,8,0,0,0,8.58.19,8.33,8.33,0,0,0,3.86-7.17V80A8,8,0,0,0,250,72.25Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
39
fluxer_marketing/src/fluxer_marketing/icons/windows.gleam
Normal file
39
fluxer_marketing/src/fluxer_marketing/icons/windows.gleam
Normal file
@@ -0,0 +1,39 @@
|
||||
//// Copyright (C) 2026 Fluxer Contributors
|
||||
////
|
||||
//// This file is part of Fluxer.
|
||||
////
|
||||
//// Fluxer is free software: you can redistribute it and/or modify
|
||||
//// it under the terms of the GNU Affero General Public License as published by
|
||||
//// the Free Software Foundation, either version 3 of the License, or
|
||||
//// (at your option) any later version.
|
||||
////
|
||||
//// Fluxer is distributed in the hope that it will be useful,
|
||||
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
//// GNU Affero General Public License for more details.
|
||||
////
|
||||
//// You should have received a copy of the GNU Affero General Public License
|
||||
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import lustre/attribute.{type Attribute}
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/svg
|
||||
|
||||
pub fn windows(attributes: List(Attribute(a))) -> Element(a) {
|
||||
svg.svg(
|
||||
[
|
||||
attribute.attribute("xmlns", "http://www.w3.org/2000/svg"),
|
||||
attribute.attribute("viewBox", "0 0 256 256"),
|
||||
attribute.attribute("fill", "currentColor"),
|
||||
..attributes
|
||||
],
|
||||
[
|
||||
svg.path([
|
||||
attribute.attribute(
|
||||
"d",
|
||||
"M104,144v51.64a8,8,0,0,1-8,8,8.54,8.54,0,0,1-1.43-.13l-64-11.64A8,8,0,0,1,24,184V144a8,8,0,0,1,8-8H96A8,8,0,0,1,104,144Zm-2.87-89.78a8,8,0,0,0-6.56-1.73l-64,11.64A8,8,0,0,0,24,72v40a8,8,0,0,0,8,8H96a8,8,0,0,0,8-8V60.36A8,8,0,0,0,101.13,54.22ZM208,136H128a8,8,0,0,0-8,8v57.45a8,8,0,0,0,6.57,7.88l80,14.54A7.61,7.61,0,0,0,208,224a8,8,0,0,0,8-8V144A8,8,0,0,0,208,136Zm5.13-102.14a8,8,0,0,0-6.56-1.73l-80,14.55A8,8,0,0,0,120,54.55V112a8,8,0,0,0,8,8h80a8,8,0,0,0,8-8V40A8,8,0,0,0,213.13,33.86Z",
|
||||
),
|
||||
]),
|
||||
],
|
||||
)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user