refactor: updated login screen and favicon
All checks were successful
deploy / deploy (push) Successful in 4m50s
BIN
assets/images/login_cover.jpg
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
assets/images/logo_coin.png
Normal file
|
After Width: | Height: | Size: 150 KiB |
@ -205,41 +205,22 @@ class _BrandPanel extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Positioned(
|
// A wall of ticket endings with a few gold ones, calmed in the
|
||||||
right: large ? -120 : -50,
|
// middle so the coin and name stay clear. Decorative only.
|
||||||
top: large ? -80 : -30,
|
Positioned.fill(
|
||||||
child: _ring(large ? 420 : 190, C.onNavy8),
|
child: Image.asset(
|
||||||
|
'assets/images/login_cover.jpg',
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
excludeFromSemantics: true,
|
||||||
|
errorBuilder: (_, _, _) => const SizedBox.shrink(),
|
||||||
),
|
),
|
||||||
Positioned(
|
|
||||||
left: large ? -90 : -40,
|
|
||||||
bottom: large ? -60 : 10,
|
|
||||||
child: _ring(large ? 300 : 120, C.onNavy5),
|
|
||||||
),
|
),
|
||||||
SafeArea(
|
SafeArea(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Container(
|
BrandCoin(size: coin),
|
||||||
width: coin,
|
|
||||||
height: coin,
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
color: C.gold,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: C.goldGlow,
|
|
||||||
blurRadius: 30,
|
|
||||||
offset: Offset(0, 10),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Icon(
|
|
||||||
Icons.confirmation_number_rounded,
|
|
||||||
color: C.navy,
|
|
||||||
size: coin * 0.48,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: large ? S.s24 : S.s18),
|
SizedBox(height: large ? S.s24 : S.s18),
|
||||||
Text(
|
Text(
|
||||||
'SRS',
|
'SRS',
|
||||||
@ -247,7 +228,7 @@ class _BrandPanel extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: S.s6),
|
const SizedBox(height: S.s6),
|
||||||
Text(
|
Text(
|
||||||
'Your sequences, at a glance',
|
'Your ticket sequences, at a glance',
|
||||||
style: ts(large ? 16 : 14, c: C.softOnNavy),
|
style: ts(large ? 16 : 14, c: C.softOnNavy),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -258,10 +239,4 @@ class _BrandPanel extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _ring(double size, Color color) => Container(
|
|
||||||
width: size,
|
|
||||||
height: size,
|
|
||||||
decoration: BoxDecoration(color: color, shape: BoxShape.circle),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,10 @@ import 'core/api.dart';
|
|||||||
import 'core/config.dart';
|
import 'core/config.dart';
|
||||||
import 'core/format.dart';
|
import 'core/format.dart';
|
||||||
import 'core/http_api.dart';
|
import 'core/http_api.dart';
|
||||||
|
import 'core/models.dart';
|
||||||
import 'core/providers.dart';
|
import 'core/providers.dart';
|
||||||
|
import 'core/theme.dart';
|
||||||
|
import 'widgets/ui.dart';
|
||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
@ -15,18 +18,56 @@ Future<void> main() async {
|
|||||||
baseUrl: AppConfig.apiBaseUrl,
|
baseUrl: AppConfig.apiBaseUrl,
|
||||||
prefs: await SharedPreferences.getInstance(),
|
prefs: await SharedPreferences.getInstance(),
|
||||||
);
|
);
|
||||||
|
runApp(_Boot(api));
|
||||||
|
}
|
||||||
|
|
||||||
final user = await api.restore();
|
class _Boot extends StatefulWidget {
|
||||||
final start = dateOnly(api.now);
|
const _Boot(this.api);
|
||||||
|
final Api api;
|
||||||
|
|
||||||
runApp(
|
@override
|
||||||
ProviderScope(
|
State<_Boot> createState() => _BootState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BootState extends State<_Boot> {
|
||||||
|
late final Future<AppUser?> _user = widget.api.restore().catchError(
|
||||||
|
(Object _) => null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => FutureBuilder<AppUser?>(
|
||||||
|
future: _user,
|
||||||
|
builder: (context, snap) {
|
||||||
|
if (snap.connectionState != ConnectionState.done) {
|
||||||
|
return const _Splash();
|
||||||
|
}
|
||||||
|
return ProviderScope(
|
||||||
overrides: [
|
overrides: [
|
||||||
apiProvider.overrideWithValue(api),
|
apiProvider.overrideWithValue(widget.api),
|
||||||
startDateProvider.overrideWithValue(start),
|
startDateProvider.overrideWithValue(dateOnly(widget.api.now)),
|
||||||
sessionProvider.overrideWith(() => SessionNotifier(user)),
|
sessionProvider.overrideWith(() => SessionNotifier(snap.data)),
|
||||||
],
|
],
|
||||||
child: const SrsApp(),
|
child: const SrsApp(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Splash extends StatelessWidget {
|
||||||
|
const _Splash();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: DecoratedBox(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
colors: [C.navy, C.navyDeep],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Center(child: BrandCoin(size: 96)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -237,16 +237,7 @@ class _Brand extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final coin = Container(
|
const coin = BrandCoin(size: 40, glow: false);
|
||||||
width: 40,
|
|
||||||
height: 40,
|
|
||||||
decoration: const BoxDecoration(color: C.gold, shape: BoxShape.circle),
|
|
||||||
child: const Icon(
|
|
||||||
Icons.confirmation_number_rounded,
|
|
||||||
color: C.navy,
|
|
||||||
size: 20,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (!extended) return coin;
|
if (!extended) return coin;
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@ -1400,3 +1400,32 @@ class DateStrip extends ConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class BrandCoin extends StatelessWidget {
|
||||||
|
const BrandCoin({super.key, required this.size, this.glow = true});
|
||||||
|
final double size;
|
||||||
|
final bool glow;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
boxShadow: glow
|
||||||
|
? [
|
||||||
|
BoxShadow(
|
||||||
|
color: C.goldGlow,
|
||||||
|
blurRadius: size * 0.36,
|
||||||
|
offset: Offset(0, size * 0.12),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/images/logo_coin.png',
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
excludeFromSemantics: true,
|
||||||
|
filterQuality: FilterQuality.medium,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@ -60,6 +60,8 @@ flutter:
|
|||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
assets:
|
assets:
|
||||||
- assets/icons/whatsapp.svg
|
- assets/icons/whatsapp.svg
|
||||||
|
- assets/images/login_cover.jpg
|
||||||
|
- assets/images/logo_coin.png
|
||||||
fonts:
|
fonts:
|
||||||
- family: Inter
|
- family: Inter
|
||||||
fonts:
|
fonts:
|
||||||
|
|||||||
BIN
tool/brand_art/logo_coin_master.png
Normal file
|
After Width: | Height: | Size: 376 KiB |
46
tool/brand_art/make_cover.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import random, math
|
||||||
|
from PIL import Image, ImageDraw, ImageFilter, ImageFont, ImageChops
|
||||||
|
import sys, os; sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
from make_icons import gradient, NAVY, DEEP, GOLD, FONT
|
||||||
|
|
||||||
|
W,H=1600,1600 # square: crops cleanly into the tall left panel and the phone banner
|
||||||
|
random.seed(74)
|
||||||
|
base=gradient((W,H),NAVY,DEEP).convert("RGBA")
|
||||||
|
|
||||||
|
# Wall of ticket endings, tilted, in two quiet tones with a few gold ones.
|
||||||
|
WW=2800
|
||||||
|
wall=Image.new("RGBA",(WW,WW),(0,0,0,0)); d=ImageDraw.Draw(wall)
|
||||||
|
big=ImageFont.truetype(FONT,138)
|
||||||
|
cw,ch=440,230
|
||||||
|
cols,rows=WW//cw+1,WW//ch+1
|
||||||
|
gold_cells={(2,3),(5,5),(3,8),(6,2),(1,10),(4,6)}
|
||||||
|
for r in range(rows):
|
||||||
|
for c in range(cols):
|
||||||
|
txt="%04d"%random.randint(0,9999)
|
||||||
|
x=c*cw+(cw//2 if r%2 else 0)-80; y=r*ch-40
|
||||||
|
if (c,r) in gold_cells:
|
||||||
|
fill=GOLD+(215,)
|
||||||
|
else:
|
||||||
|
fill=(255,255,255,20 if (c+r)%3 else 32)
|
||||||
|
d.text((x,y),txt,font=big,fill=fill)
|
||||||
|
wall=wall.rotate(-12,resample=Image.BICUBIC)
|
||||||
|
left=(WW-W)//2; top=(WW-H)//2
|
||||||
|
wall=wall.crop((left,top,left+W,top+H))
|
||||||
|
|
||||||
|
# Calm the middle, where the coin and wordmark sit, and darken the edges.
|
||||||
|
mask=Image.new("L",(W,H),255); md=ImageDraw.Draw(mask)
|
||||||
|
md.ellipse([W*0.08,H*0.18,W*0.92,H*0.82],fill=14)
|
||||||
|
mask=mask.filter(ImageFilter.GaussianBlur(200))
|
||||||
|
wall.putalpha(ImageChops.multiply(wall.getchannel("A"),mask))
|
||||||
|
img=Image.alpha_composite(base,wall)
|
||||||
|
|
||||||
|
vig=Image.new("L",(W,H),0); vd=ImageDraw.Draw(vig)
|
||||||
|
vd.ellipse([-W*0.15,-H*0.15,W*1.15,H*1.15],fill=255)
|
||||||
|
vig=vig.filter(ImageFilter.GaussianBlur(220))
|
||||||
|
dark=Image.new("RGBA",(W,H),DEEP+(255,))
|
||||||
|
dark.putalpha(Image.eval(vig,lambda v:int((255-v)*0.75)))
|
||||||
|
img=Image.alpha_composite(img,dark).convert("RGB")
|
||||||
|
out="C:/Project-1/srs/assets/images/login_cover.jpg"
|
||||||
|
import os; os.makedirs(os.path.dirname(out),exist_ok=True)
|
||||||
|
img.save(out,quality=84,optimize=True,progressive=True)
|
||||||
|
print(out,img.size,os.path.getsize(out)//1024,"KB")
|
||||||
116
tool/brand_art/make_icons.py
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import os, random, sys
|
||||||
|
from PIL import Image, ImageChops, ImageDraw, ImageFilter, ImageFont
|
||||||
|
|
||||||
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
ROOT = os.path.abspath(os.path.join(HERE, "..", ".."))
|
||||||
|
NAVY = (0x1B, 0x2A, 0x49); DEEP = (0x10, 0x1B, 0x33); GOLD = (0xD4, 0xA6, 0x4A)
|
||||||
|
FONT = os.path.join(ROOT, "assets", "fonts", "Inter-Bold.otf")
|
||||||
|
COIN = Image.open(os.path.join(HERE, "logo_coin_master.png")).convert("RGBA")
|
||||||
|
|
||||||
|
|
||||||
|
def gradient(size, c1, c2):
|
||||||
|
w, h = size
|
||||||
|
g = Image.new("RGB", (w, h))
|
||||||
|
px = g.load()
|
||||||
|
for y in range(h):
|
||||||
|
for x in range(w):
|
||||||
|
t = (x / w + y / h) / 2
|
||||||
|
px[x, y] = tuple(round(c1[i] + (c2[i] - c1[i]) * t) for i in range(3))
|
||||||
|
return g
|
||||||
|
|
||||||
|
|
||||||
|
def background(S, style):
|
||||||
|
bg = gradient((S, S), NAVY, DEEP).convert("RGBA")
|
||||||
|
ov = Image.new("RGBA", (S, S), (0, 0, 0, 0))
|
||||||
|
d = ImageDraw.Draw(ov)
|
||||||
|
if style == "circles":
|
||||||
|
# Large soft discs and one thin gold ring, partly off the tile.
|
||||||
|
d.ellipse([S * 0.46, -S * 0.42, S * 1.34, S * 0.46], fill=(255, 255, 255, 20))
|
||||||
|
d.ellipse([-S * 0.42, S * 0.52, S * 0.42, S * 1.36], fill=(255, 255, 255, 14))
|
||||||
|
d.ellipse([S * 0.58, S * 0.60, S * 1.18, S * 1.20], fill=(212, 166, 74, 22))
|
||||||
|
d.ellipse([S * 0.04, S * 0.04, S * 0.96, S * 0.96],
|
||||||
|
outline=(212, 166, 74, 46), width=max(1, round(S * 0.006)))
|
||||||
|
elif style == "endings":
|
||||||
|
rnd = random.Random(74)
|
||||||
|
f = ImageFont.truetype(FONT, round(S * 0.115))
|
||||||
|
cw, ch = S * 0.36, S * 0.19
|
||||||
|
gold = {(1, 1), (3, 3), (0, 4), (2, 5)}
|
||||||
|
layer = Image.new("RGBA", (S * 2, S * 2), (0, 0, 0, 0))
|
||||||
|
ld = ImageDraw.Draw(layer)
|
||||||
|
for r in range(int(S * 2 / ch) + 1):
|
||||||
|
for c in range(int(S * 2 / cw) + 1):
|
||||||
|
t = "%04d" % rnd.randint(0, 9999)
|
||||||
|
x = c * cw + (cw / 2 if r % 2 else 0) - S * 0.2
|
||||||
|
y = r * ch - S * 0.1
|
||||||
|
fill = GOLD + (150,) if (c, r) in gold else (255, 255, 255, 26)
|
||||||
|
ld.text((x, y), t, font=f, fill=fill)
|
||||||
|
layer = layer.rotate(-12, resample=Image.BICUBIC)
|
||||||
|
o = S // 2
|
||||||
|
layer = layer.crop((o, o, o + S, o + S))
|
||||||
|
ov = layer
|
||||||
|
return Image.alpha_composite(bg, ov)
|
||||||
|
|
||||||
|
|
||||||
|
def icon(px, style, ratio, shape, ss=4):
|
||||||
|
S = px * ss
|
||||||
|
bg = background(S, style)
|
||||||
|
cx = cy = S / 2
|
||||||
|
R = S * ratio / 2
|
||||||
|
if style == "endings":
|
||||||
|
# Calm the middle so the coin reads cleanly.
|
||||||
|
m = Image.new("L", (S, S), 255)
|
||||||
|
ImageDraw.Draw(m).ellipse([cx - R * 1.55, cy - R * 1.55, cx + R * 1.55, cy + R * 1.55], fill=70)
|
||||||
|
m = m.filter(ImageFilter.GaussianBlur(S * 0.07))
|
||||||
|
veil = Image.new("RGBA", (S, S), NAVY + (255,))
|
||||||
|
veil.putalpha(Image.eval(m, lambda v: 255 - v))
|
||||||
|
bg = Image.alpha_composite(bg, veil)
|
||||||
|
glow = Image.new("RGBA", (S, S), (0, 0, 0, 0))
|
||||||
|
ImageDraw.Draw(glow).ellipse([cx - R, cy - R + S * 0.02, cx + R, cy + R + S * 0.02], fill=GOLD + (130,))
|
||||||
|
glow = glow.filter(ImageFilter.GaussianBlur(S * 0.035))
|
||||||
|
bg = Image.alpha_composite(bg, glow)
|
||||||
|
coin = COIN.resize((round(R * 2), round(R * 2)), Image.LANCZOS)
|
||||||
|
bg.alpha_composite(coin, (round(cx - R), round(cy - R)))
|
||||||
|
if shape == "rounded":
|
||||||
|
mask = Image.new("L", (S, S), 0)
|
||||||
|
ImageDraw.Draw(mask).rounded_rectangle([0, 0, S - 1, S - 1], radius=S * 0.22, fill=255)
|
||||||
|
bg.putalpha(mask)
|
||||||
|
return bg.resize((px, px), Image.LANCZOS)
|
||||||
|
|
||||||
|
|
||||||
|
def write_icons(style):
|
||||||
|
web = os.path.join(ROOT, "web")
|
||||||
|
jobs = [
|
||||||
|
("icons/Icon-512.png", 512, 0.62, "rounded"),
|
||||||
|
("icons/Icon-192.png", 192, 0.62, "rounded"),
|
||||||
|
("icons/Icon-maskable-512.png", 512, 0.52, "full"),
|
||||||
|
("icons/Icon-maskable-192.png", 192, 0.52, "full"),
|
||||||
|
("icons/apple-touch-icon.png", 180, 0.62, "full"),
|
||||||
|
("favicon.png", 64, 0.78, "rounded"),
|
||||||
|
]
|
||||||
|
for name, px, ratio, shape in jobs:
|
||||||
|
icon(px, style, ratio, shape).save(os.path.join(web, name), optimize=True)
|
||||||
|
print(name, px)
|
||||||
|
|
||||||
|
|
||||||
|
def write_preview():
|
||||||
|
out = os.path.join(HERE, "preview")
|
||||||
|
os.makedirs(out, exist_ok=True)
|
||||||
|
for style in ("endings", "circles"):
|
||||||
|
sheet = Image.new("RGB", (512 + 40 + 192 + 40 + 64 + 40 + 32, 512 + 40), (244, 242, 236))
|
||||||
|
big = icon(512, style, 0.62, "rounded")
|
||||||
|
sheet.paste(big, (20, 20), big)
|
||||||
|
mid = icon(192, style, 0.62, "rounded")
|
||||||
|
sheet.paste(mid, (512 + 40, 20), mid)
|
||||||
|
small = icon(64, style, 0.78, "rounded")
|
||||||
|
sheet.paste(small, (512 + 40 + 192 + 20, 20), small)
|
||||||
|
tiny = icon(32, style, 0.78, "rounded")
|
||||||
|
sheet.paste(tiny, (512 + 40 + 192 + 20 + 64 + 20, 20), tiny)
|
||||||
|
sheet.save(os.path.join(out, f"icon_{style}.png"))
|
||||||
|
print("preview", style)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if len(sys.argv) > 1 and sys.argv[1] == "preview":
|
||||||
|
write_preview()
|
||||||
|
else:
|
||||||
|
write_icons(sys.argv[1] if len(sys.argv) > 1 else "circles")
|
||||||
BIN
tool/brand_art/preview/icon_circles.png
Normal file
|
After Width: | Height: | Size: 187 KiB |
BIN
tool/brand_art/preview/icon_endings.png
Normal file
|
After Width: | Height: | Size: 228 KiB |
BIN
web/favicon.png
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 109 KiB |
BIN
web/icons/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
web/icons/coin.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
@ -1,30 +1,17 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<!--
|
|
||||||
If you are serving your web app in a path other than the root, change the
|
|
||||||
href value below to reflect the base path you are serving from.
|
|
||||||
|
|
||||||
The path provided below has to start and end with a slash "/" in order for
|
|
||||||
it to work correctly.
|
|
||||||
|
|
||||||
For more details:
|
|
||||||
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
|
|
||||||
|
|
||||||
This is a placeholder for base href that will be replaced by the value of
|
|
||||||
the `--base-href` argument provided to `flutter build`.
|
|
||||||
-->
|
|
||||||
<base href="$FLUTTER_BASE_HREF">
|
<base href="$FLUTTER_BASE_HREF">
|
||||||
|
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
||||||
<meta name="description" content="Your sequences, at a glance">
|
<meta name="description" content="Your ticket sequences, at a glance">
|
||||||
|
|
||||||
<!-- iOS meta tags & icons -->
|
<!-- iOS meta tags & icons -->
|
||||||
<meta name="mobile-web-app-capable" content="yes">
|
<meta name="mobile-web-app-capable" content="yes">
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||||
<meta name="apple-mobile-web-app-title" content="SRS">
|
<meta name="apple-mobile-web-app-title" content="SRS">
|
||||||
<link rel="apple-touch-icon" href="icons/Icon-192.png">
|
<link rel="apple-touch-icon" href="icons/apple-touch-icon.png">
|
||||||
|
|
||||||
<!-- Favicon -->
|
<!-- Favicon -->
|
||||||
<link rel="icon" type="image/png" href="favicon.png"/>
|
<link rel="icon" type="image/png" href="favicon.png"/>
|
||||||
@ -32,16 +19,36 @@
|
|||||||
<meta name="theme-color" content="#1B2A49">
|
<meta name="theme-color" content="#1B2A49">
|
||||||
<title>SRS</title>
|
<title>SRS</title>
|
||||||
<link rel="manifest" href="manifest.json">
|
<link rel="manifest" href="manifest.json">
|
||||||
|
<style>
|
||||||
|
html, body { margin: 0; height: 100%; background: #1B2A49; }
|
||||||
|
#splash {
|
||||||
|
position: fixed; inset: 0; display: flex; align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: linear-gradient(135deg, #1B2A49, #101B33);
|
||||||
|
transition: opacity .2s ease-out;
|
||||||
|
}
|
||||||
|
#splash .coin {
|
||||||
|
width: 96px; height: 96px; border-radius: 50%;
|
||||||
|
box-shadow: 0 12px 34px rgba(212, 166, 74, .33);
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
#splash .coin { animation: breathe 1.8s ease-in-out infinite; }
|
||||||
|
}
|
||||||
|
@keyframes breathe { 50% { transform: scale(.94); } }
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!--
|
<div id="splash" aria-hidden="true">
|
||||||
You can customize the "flutter_bootstrap.js" script.
|
<img class="coin" src="icons/coin.png" width="96" height="96" alt="">
|
||||||
This is useful to provide a custom configuration to the Flutter loader
|
</div>
|
||||||
or to give the user feedback during the initialization process.
|
<script>
|
||||||
|
window.addEventListener('flutter-first-frame', function () {
|
||||||
For more details:
|
var el = document.getElementById('splash');
|
||||||
* https://docs.flutter.dev/platform-integration/web/initialization
|
if (!el) return;
|
||||||
-->
|
el.style.opacity = '0';
|
||||||
|
setTimeout(function () { el.remove(); }, 250);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
<script src="flutter_bootstrap.js" async></script>
|
<script src="flutter_bootstrap.js" async></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -3,9 +3,9 @@
|
|||||||
"short_name": "SRS",
|
"short_name": "SRS",
|
||||||
"start_url": ".",
|
"start_url": ".",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"background_color": "#F4F2EC",
|
"background_color": "#1B2A49",
|
||||||
"theme_color": "#1B2A49",
|
"theme_color": "#1B2A49",
|
||||||
"description": "Your sequences, at a glance",
|
"description": "Your ticket sequences, at a glance",
|
||||||
"orientation": "portrait-primary",
|
"orientation": "portrait-primary",
|
||||||
"prefer_related_applications": false,
|
"prefer_related_applications": false,
|
||||||
"icons": [
|
"icons": [
|
||||||
|
|||||||