mirror of
https://github.com/velocitatem/PHANTOM.git
synced 2026-07-16 01:53:37 +00:00
Compare commits
9 Commits
claude/set
...
copilot/re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0aed8e7311 | ||
|
|
90ba7588cc | ||
|
|
df3082cff5 | ||
| 6b7060450c | |||
|
|
f6e780fdf1 | ||
|
|
f427943ebc | ||
| aa98b2d169 | |||
| 5777437540 | |||
| e677170fd2 |
15
.github/workflows/latex.yml
vendored
15
.github/workflows/latex.yml
vendored
@@ -26,18 +26,3 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: thesis-pdf
|
name: thesis-pdf
|
||||||
path: paper/build/main.pdf
|
path: paper/build/main.pdf
|
||||||
|
|
||||||
- name: Deploy PDF to GitHub Pages
|
|
||||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
||||||
run: |
|
|
||||||
# Copy PDF to docs directory for GitHub Pages
|
|
||||||
mkdir -p docs/static/pdfs
|
|
||||||
cp paper/build/main.pdf docs/static/pdfs/thesis.pdf
|
|
||||||
|
|
||||||
# Configure git
|
|
||||||
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
|
||||||
git config --local user.name "github-actions[bot]"
|
|
||||||
|
|
||||||
# Commit and push if there are changes
|
|
||||||
git add docs/static/pdfs/thesis.pdf
|
|
||||||
git diff --quiet && git diff --staged --quiet || (git commit -m "Update thesis PDF [skip ci]" && git push)
|
|
||||||
|
|||||||
190
README.md
190
README.md
@@ -1,191 +1 @@
|
|||||||
# PHANTOM: Pricing Heuristics Against Non-human Transaction Orchestration Mechanisms
|
|
||||||
|
|
||||||
[](https://github.com/velocitatem/PHANTOM/actions/workflows/latex.yml)
|
[](https://github.com/velocitatem/PHANTOM/actions/workflows/latex.yml)
|
||||||
|
|
||||||
> Bachelor's Thesis Project by Daniel Rösel, IE University Madrid (2025)
|
|
||||||
> Advisor: Alberto Martín Izquierdo
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
PHANTOM is an academic research project investigating **pricing heuristics to protect e-commerce platforms from exploitation by LLM agents** in dynamic pricing environments. This project explores behavioral signature detection for agent identification and develops protection mechanisms against automated transaction orchestration.
|
|
||||||
|
|
||||||
**Research Focus Areas:**
|
|
||||||
- AI security in e-commerce systems
|
|
||||||
- Behavioral signature detection for autonomous agent identification
|
|
||||||
- Dynamic pricing protection mechanisms
|
|
||||||
- LLM agent behavior analysis and exploitation patterns
|
|
||||||
|
|
||||||
## Project Structure
|
|
||||||
|
|
||||||
This repository contains both the research thesis and a full-stack experimental platform:
|
|
||||||
|
|
||||||
```
|
|
||||||
PHANTOM/
|
|
||||||
├── paper/ # LaTeX Bachelor's Thesis
|
|
||||||
│ ├── src/ # LaTeX source files
|
|
||||||
│ ├── build/ # Compiled PDF output
|
|
||||||
│ └── concat_code.sh # Auto-concatenate code for appendix
|
|
||||||
│
|
|
||||||
├── docs/ # GitHub Pages academic project page
|
|
||||||
│ └── index.html # Academic project showcase
|
|
||||||
│
|
|
||||||
├── web/ # Next.js research platform dashboard
|
|
||||||
│ └── src/ # Frontend application
|
|
||||||
│
|
|
||||||
├── backend/ # Python backend services
|
|
||||||
│ ├── provider/ # Data provider service
|
|
||||||
│ └── worker/ # Kafka consumer worker
|
|
||||||
│
|
|
||||||
├── experiments/ # Research experiments and data analysis
|
|
||||||
│ └── data_export.ipynb
|
|
||||||
│
|
|
||||||
└── docker/ # Infrastructure configurations
|
|
||||||
```
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
|
|
||||||
- Docker & Docker Compose (for infrastructure)
|
|
||||||
- Node.js 18+ (for web application)
|
|
||||||
- Python 3.9+ (for backend services)
|
|
||||||
- LaTeX distribution (for building thesis paper)
|
|
||||||
|
|
||||||
### Infrastructure Setup
|
|
||||||
|
|
||||||
Start Kafka, Redis, and monitoring services:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker-compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
**Services:**
|
|
||||||
- Redis: `localhost:6379`
|
|
||||||
- Kafka: `localhost:9092`
|
|
||||||
- Zookeeper: `localhost:2181`
|
|
||||||
- Redpanda Console (Kafka UI): `http://localhost:8080`
|
|
||||||
|
|
||||||
### Web Application
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd web
|
|
||||||
npm install
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
Access at `http://localhost:3000`
|
|
||||||
|
|
||||||
### Backend Services
|
|
||||||
|
|
||||||
Install dependencies:
|
|
||||||
```bash
|
|
||||||
pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
Run worker:
|
|
||||||
```bash
|
|
||||||
cd backend/worker
|
|
||||||
python main.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## Building the Thesis Paper
|
|
||||||
|
|
||||||
### Using Make
|
|
||||||
|
|
||||||
```bash
|
|
||||||
make pdf # Compile LaTeX to PDF
|
|
||||||
make watch # Continuous compilation (live preview)
|
|
||||||
make clean # Remove build artifacts
|
|
||||||
```
|
|
||||||
|
|
||||||
### Manual Build
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd paper/src
|
|
||||||
latexmk -pdf main.tex
|
|
||||||
```
|
|
||||||
|
|
||||||
**Output**: `paper/build/main.pdf`
|
|
||||||
|
|
||||||
### Automated CI/CD
|
|
||||||
|
|
||||||
The thesis PDF is automatically built via GitHub Actions on every push to `main` that affects `paper/**`. The compiled PDF artifact is available in the Actions tab.
|
|
||||||
|
|
||||||
## Technical Architecture
|
|
||||||
|
|
||||||
**Frontend**: Next.js 14, React 18, TypeScript, Tailwind CSS
|
|
||||||
**Backend**: Python, Kafka (event streaming)
|
|
||||||
**Infrastructure**: Redis (cache), Kafka + Zookeeper
|
|
||||||
**Monitoring**: Redpanda Console
|
|
||||||
**Data Analysis**: Jupyter Notebooks, Pandas, Matplotlib
|
|
||||||
|
|
||||||
### Event-Driven Architecture
|
|
||||||
|
|
||||||
The platform uses Kafka for real-time event streaming, enabling:
|
|
||||||
- Asynchronous task processing
|
|
||||||
- Scalable data collection
|
|
||||||
- Experiment tracking and analysis
|
|
||||||
- Behavioral pattern detection
|
|
||||||
|
|
||||||
## Research Experiments
|
|
||||||
|
|
||||||
Jupyter notebooks in `experiments/` contain:
|
|
||||||
- Data exploration and analysis
|
|
||||||
- Behavioral pattern visualizations
|
|
||||||
- Statistical analysis of agent behaviors
|
|
||||||
- Experiment result processing
|
|
||||||
|
|
||||||
Run experiments:
|
|
||||||
```bash
|
|
||||||
cd experiments
|
|
||||||
jupyter notebook data_export.ipynb
|
|
||||||
```
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
- **Academic Project Page**: Hosted on GitHub Pages at `/docs`
|
|
||||||
- **Thesis Paper**: Latest PDF available via GitHub Actions artifacts
|
|
||||||
- **Web App README**: See `web/README.md`
|
|
||||||
- **Backend READMEs**: See `backend/provider/README.md` and `backend/worker/README.md`
|
|
||||||
|
|
||||||
## Development Workflow
|
|
||||||
|
|
||||||
1. **Paper Development**: Edit LaTeX files in `paper/src/`, use `make watch` for live preview
|
|
||||||
2. **Web Development**: Standard Next.js workflow in `web/`
|
|
||||||
3. **Backend Development**: Python services in `backend/`
|
|
||||||
4. **Experiments**: Jupyter notebooks in `experiments/`
|
|
||||||
|
|
||||||
### Code in Thesis Appendix
|
|
||||||
|
|
||||||
The `paper/concat_code.sh` script automatically generates a LaTeX appendix containing all source code from:
|
|
||||||
- `backend/` (Python, JavaScript, Shell, YAML)
|
|
||||||
- `experiments/` (Analysis scripts)
|
|
||||||
- `docker/` (Infrastructure configs)
|
|
||||||
- `web/src/` (TypeScript/React components)
|
|
||||||
|
|
||||||
This runs automatically during PDF compilation.
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
This is an academic thesis project. For questions or collaboration inquiries, please open an issue.
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
Academic research project - all rights reserved.
|
|
||||||
|
|
||||||
## Citation
|
|
||||||
|
|
||||||
```bibtex
|
|
||||||
@thesis{rosel2025phantom,
|
|
||||||
title={Pricing Heuristics Against Non-human Transaction Orchestration Mechanisms},
|
|
||||||
author={Rösel, Daniel},
|
|
||||||
year={2025},
|
|
||||||
school={IE University},
|
|
||||||
address={Madrid, Spain},
|
|
||||||
type={Bachelor's Thesis}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Acknowledgments
|
|
||||||
|
|
||||||
Special thanks to Alberto Martín Izquierdo for academic supervision and guidance throughout this research project.
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
services:
|
services:
|
||||||
redis:
|
redis:
|
||||||
container_name: "PHANTOM-redis"
|
container_name: "PHANTOM-redis"
|
||||||
image: redis:7-alpine
|
build:
|
||||||
|
context: ./docker
|
||||||
|
dockerfile: Redis.dockerfile
|
||||||
ports:
|
ports:
|
||||||
- "${REDIS_PORT:-6378}:6379"
|
- "${REDIS_PORT:-6378}:6379"
|
||||||
volumes:
|
volumes:
|
||||||
@@ -9,7 +11,9 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
zookeeper:
|
zookeeper:
|
||||||
container_name: "PHANTOM-zookeeper"
|
container_name: "PHANTOM-zookeeper"
|
||||||
image: confluentinc/cp-zookeeper:latest
|
build:
|
||||||
|
context: ./docker
|
||||||
|
dockerfile: Zookeeper.dockerfile
|
||||||
environment:
|
environment:
|
||||||
ZOOKEEPER_CLIENT_PORT: 2181
|
ZOOKEEPER_CLIENT_PORT: 2181
|
||||||
ports:
|
ports:
|
||||||
@@ -17,7 +21,9 @@ services:
|
|||||||
|
|
||||||
kafka:
|
kafka:
|
||||||
container_name: "PHANTOM-kafka"
|
container_name: "PHANTOM-kafka"
|
||||||
image: confluentinc/cp-kafka:7.5.0
|
build:
|
||||||
|
context: ./docker
|
||||||
|
dockerfile: Kafka.dockerfile
|
||||||
depends_on:
|
depends_on:
|
||||||
- zookeeper
|
- zookeeper
|
||||||
environment:
|
environment:
|
||||||
@@ -36,7 +42,9 @@ services:
|
|||||||
|
|
||||||
redpanda-console:
|
redpanda-console:
|
||||||
container_name: "PHANTOM-redpanda-console"
|
container_name: "PHANTOM-redpanda-console"
|
||||||
image: docker.redpanda.com/redpandadata/console:latest
|
build:
|
||||||
|
context: ./docker
|
||||||
|
dockerfile: RedpandaConsole.dockerfile
|
||||||
depends_on:
|
depends_on:
|
||||||
- kafka
|
- kafka
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
7
docker/Kafka.dockerfile
Normal file
7
docker/Kafka.dockerfile
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
FROM confluentinc/cp-kafka:7.5.0
|
||||||
|
|
||||||
|
# Expose Kafka ports
|
||||||
|
# 9092: External client connections
|
||||||
|
# 29092: Internal broker communication
|
||||||
|
# 9999: JMX monitoring port
|
||||||
|
EXPOSE 9092 29092 9999
|
||||||
4
docker/Redis.dockerfile
Normal file
4
docker/Redis.dockerfile
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
FROM redis:7-alpine
|
||||||
|
|
||||||
|
# Expose Redis port
|
||||||
|
EXPOSE 6379
|
||||||
4
docker/RedpandaConsole.dockerfile
Normal file
4
docker/RedpandaConsole.dockerfile
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
FROM docker.redpanda.com/redpandadata/console:latest
|
||||||
|
|
||||||
|
# Expose Redpanda Console web UI port
|
||||||
|
EXPOSE 8080
|
||||||
4
docker/Zookeeper.dockerfile
Normal file
4
docker/Zookeeper.dockerfile
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
FROM confluentinc/cp-zookeeper:latest
|
||||||
|
|
||||||
|
# Expose Zookeeper client port
|
||||||
|
EXPOSE 2181
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
<meta name="citation_author" content="Rösel, Daniel">
|
<meta name="citation_author" content="Rösel, Daniel">
|
||||||
<meta name="citation_publication_date" content="2025">
|
<meta name="citation_publication_date" content="2025">
|
||||||
<meta name="citation_conference_title" content="IE University Bachelor's Thesis">
|
<meta name="citation_conference_title" content="IE University Bachelor's Thesis">
|
||||||
<meta name="citation_pdf_url" content="static/pdfs/thesis.pdf">
|
<meta name="citation_pdf_url" content="TODO">
|
||||||
|
|
||||||
<!-- Additional SEO -->
|
<!-- Additional SEO -->
|
||||||
<meta name="theme-color" content="#2563eb">
|
<meta name="theme-color" content="#2563eb">
|
||||||
@@ -238,14 +238,14 @@
|
|||||||
|
|
||||||
<div class="column has-text-centered">
|
<div class="column has-text-centered">
|
||||||
<div class="publication-links">
|
<div class="publication-links">
|
||||||
<!-- Thesis PDF - automatically updated via GitHub Actions -->
|
<!-- TODO: Update with your arXiv paper ID -->
|
||||||
<span class="link-block">
|
<span class="link-block">
|
||||||
<a href="static/pdfs/thesis.pdf" target="_blank"
|
<a href="https://arxiv.org/pdf/<ARXIV PAPER ID>.pdf" target="_blank"
|
||||||
class="external-link button is-normal is-rounded is-dark">
|
class="external-link button is-normal is-rounded is-dark">
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
<i class="fas fa-file-pdf"></i>
|
<i class="fas fa-file-pdf"></i>
|
||||||
</span>
|
</span>
|
||||||
<span>Thesis PDF</span>
|
<span>Paper</span>
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@@ -270,8 +270,8 @@
|
|||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<!-- TODO: Update with your arXiv paper ID when available -->
|
<!-- TODO: Update with your arXiv paper ID -->
|
||||||
<!-- <span class="link-block">
|
<span class="link-block">
|
||||||
<a href="https://arxiv.org/abs/<ARXIV PAPER ID>" target="_blank"
|
<a href="https://arxiv.org/abs/<ARXIV PAPER ID>" target="_blank"
|
||||||
class="external-link button is-normal is-rounded is-dark">
|
class="external-link button is-normal is-rounded is-dark">
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
@@ -279,7 +279,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<span>arXiv</span>
|
<span>arXiv</span>
|
||||||
</a>
|
</a>
|
||||||
</span> -->
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,14 +6,19 @@
|
|||||||
(setq TeX-command-extra-options
|
(setq TeX-command-extra-options
|
||||||
"-file-line-error -interaction=nonstopmode")
|
"-file-line-error -interaction=nonstopmode")
|
||||||
(TeX-add-to-alist 'LaTeX-provided-class-options
|
(TeX-add-to-alist 'LaTeX-provided-class-options
|
||||||
'(("report" "12pt") ("article" "12pt") ("acmart" "sigconf" "nonacm")))
|
'(("report" "12pt") ("article" "12pt") ("acmart" "sigconf" "nonacm" "natbib=false")))
|
||||||
(TeX-run-style-hooks
|
(TeX-run-style-hooks
|
||||||
"latex2e"
|
"latex2e"
|
||||||
"preamble"
|
"preamble"
|
||||||
"chapters/01-intro"
|
"chapters/01-intro"
|
||||||
|
"chapters/02-literature-review"
|
||||||
|
"chapters/03-methodology"
|
||||||
|
"chapters/04-results"
|
||||||
|
"chapters/05-discussion"
|
||||||
|
"chapters/06-conclusion"
|
||||||
|
"../build/concatenated_code"
|
||||||
"acmart"
|
"acmart"
|
||||||
"acmart10")
|
"acmart10")
|
||||||
(TeX-add-symbols
|
(TeX-add-symbols
|
||||||
'("footnotetextcopyrightpermission" 1)))
|
'("footnotetextcopyrightpermission" 1)))
|
||||||
:latex)
|
:latex)
|
||||||
|
|
||||||
|
|||||||
@@ -6,5 +6,11 @@
|
|||||||
%% \label{fig:example}
|
%% \label{fig:example}
|
||||||
%% \end{figure}
|
%% \end{figure}
|
||||||
|
|
||||||
\section{Know They Enemy}
|
\section{Introduction}
|
||||||
To know how to overcome we need to
|
|
||||||
|
Research Objectives and Contribution: What are we making, why and who should care?
|
||||||
|
|
||||||
|
\subsection{Motivation and Market Context}
|
||||||
|
Current market dynamics and trends of dynamic pricing and AI agents. Future projections of AI agents. Key stakeholders that are discussing this and reporting on it (Thales). Who is most affected
|
||||||
|
\subsection{Solution Space Overview}
|
||||||
|
Different approaches and perspectives, here also add a preview of what will be developed and explored in the lit review.
|
||||||
|
|||||||
17
paper/src/chapters/02-literature-review.tex
Normal file
17
paper/src/chapters/02-literature-review.tex
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
\section{Literature Review}
|
||||||
|
|
||||||
|
\subsection{Foundational Concepts}
|
||||||
|
|
||||||
|
What is the taxonomy and definition of an agent and an actor in this case, a bit more about interaction models in sessions and about dynamic pricing algorithms.
|
||||||
|
|
||||||
|
\subsection{Problem Evidence and Market Impact}
|
||||||
|
Documented instances of agent-driven market disruptions - Quantitative evidence of pricing manipulation - Case studies from affected industries
|
||||||
|
|
||||||
|
\subsection{Theoretical Foundations: Economic Prallels}
|
||||||
|
|
||||||
|
Economic foundations: relating the problem to options pricing theory. Cost of Information (COI) concept and its relevance
|
||||||
|
|
||||||
|
\subsection{Landscape of Existing Work}
|
||||||
|
|
||||||
|
Previous efforts in adversarial computer use LLM agents, show how multi-faceted the whole problem is
|
||||||
|
Here we can show a market visualization (venn-like-diagram)
|
||||||
68
paper/src/chapters/03-methodology.tex
Normal file
68
paper/src/chapters/03-methodology.tex
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
\section{Methodology}
|
||||||
|
|
||||||
|
|
||||||
|
\subsection{Problem Formalization}
|
||||||
|
|
||||||
|
Mathematical formalization of agent-induced pricing distortions. Formal definition of potential loss mechanisms $\alpha D$
|
||||||
|
|
||||||
|
We consider a business across time during which we have an evolving vector $p_t \in \Re^N$ where $N$ is the number of products in our catalogue. our price vector is directly dependent on a demand function $q_t$ which we define as a linear method of a price elasticity matrix $B_t$. This is the same setup that Microsoft created in their research.
|
||||||
|
|
||||||
|
We gether interaction data from users interacting with a sample platform simulating a hotel/airline which generates interaction distributions $I_t = \{(p_t, q_t^\text{obs}, \pi_t)\}_{t=1}^T$
|
||||||
|
|
||||||
|
|
||||||
|
\subsection{Cost of Information Framework}
|
||||||
|
|
||||||
|
Mathematical demonstration and validation of the COI and citation backed evidence, and framework overview + show harm to user via other cost distortions. Maybe split into 3.2.1 (COI Theory) and 3.2.2 (Framework Design)
|
||||||
|
|
||||||
|
\subsection{System Architecture}
|
||||||
|
\begin{figure}[ht]
|
||||||
|
\centering
|
||||||
|
\begin{tikzpicture}[
|
||||||
|
node distance=1.5cm and 2.5cm,
|
||||||
|
box/.style={rectangle, draw, thick, minimum height=1cm, minimum width=3cm, align=center, fill=blue!10},
|
||||||
|
kafka/.style={rectangle, draw=orange, thick, minimum height=1cm, minimum width=3cm, align=center, fill=orange!15},
|
||||||
|
arrow/.style={thick,->,>=Stealth}
|
||||||
|
]
|
||||||
|
|
||||||
|
% Nodes
|
||||||
|
\node[box] (webapp) {Web Application \\ (Producer \& Consumer)};
|
||||||
|
\node[kafka, below=of webapp] (kafka) {Apache Kafka \\ Cluster};
|
||||||
|
\node[box, below=of kafka] (backend) {Backend Services / Microservices \\ (Producers and Consumers)};
|
||||||
|
|
||||||
|
% Connections
|
||||||
|
\draw[arrow] (webapp) to[out=210,in=150] node[above]{Publish} (kafka);
|
||||||
|
\draw[arrow] (kafka) to[out=50,in=330] node[below]{Consume} (webapp);
|
||||||
|
\draw[arrow] (backend) -- node[above]{Publish/Consume} (kafka);
|
||||||
|
|
||||||
|
% Optional: Kafka internal components
|
||||||
|
%\node[below=0.7cm of kafka, align=center] (topics) {Topics \\ Partitions};
|
||||||
|
|
||||||
|
% Optional background
|
||||||
|
\begin{scope}[on background layer]
|
||||||
|
\node[draw, rounded corners, fill=orange!5, fit=(kafka), inner sep=0.3cm] {};
|
||||||
|
\end{scope}
|
||||||
|
\end{tikzpicture}
|
||||||
|
\caption{Technical Diagram}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
High level overview of how it works
|
||||||
|
\subsection{Experimental Design}
|
||||||
|
Study methodology and approach. Data acquisition strategy. Defined objectives and success criteria. Observable metrics and KPIs
|
||||||
|
|
||||||
|
\subsection{Dynamic Pricing Algorithm Analysis}
|
||||||
|
Deep dive into how the algorithm works, different kinds and justification for chosen appraoches + agent impact modeling and quantification.
|
||||||
|
\subsection{Reinforcement Learning Formulation}
|
||||||
|
How do we define the state space, action space and reward function breakdown and algorithm benchmarking.
|
||||||
|
POSSIBLY: Expand into full subsections: 3.6.1 (State-Action Space), 3.6.2 (Reward Design), 3.6.3 (Benchmarking)
|
||||||
|
|
||||||
|
|
||||||
|
\begin{algorithm}[t]
|
||||||
|
\DontPrintSemicolon
|
||||||
|
\KwIn{stepsize $\eta$, smoothing $\delta$, rank $d$}
|
||||||
|
\For{$t=1$ \KwTo $T$}{
|
||||||
|
Sample $u_t$ on unit sphere; set $x_t^\prime=x_t+\delta u_t$\;
|
||||||
|
Set $p_t \gets U x_t^\prime$ and observe $q_t, R_t(p_t)$\;
|
||||||
|
$x_{t+1} \gets \Pi\_{\mathcal{X}}(x_t-\eta R_t(p_t) u_t)$\;
|
||||||
|
}
|
||||||
|
\caption{Online Pricing Optimization (template)}
|
||||||
|
\end{algorithm}
|
||||||
16
paper/src/chapters/04-results.tex
Normal file
16
paper/src/chapters/04-results.tex
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
\section{Results}
|
||||||
|
|
||||||
|
\subsection{Behavioral Analysis}
|
||||||
|
|
||||||
|
Include markov chains of transition matrices, compare distributions (look at Divergence metrics)
|
||||||
|
|
||||||
|
|
||||||
|
\subsection{Experimental Outcomes}
|
||||||
|
|
||||||
|
Align with defined objectives, show results and statistical significance (or not).
|
||||||
|
|
||||||
|
|
||||||
|
\subsection{Interpretation and Insights}
|
||||||
|
Inference from given patterns and show key findings.
|
||||||
|
|
||||||
|
\subsection{Anomalies}
|
||||||
9
paper/src/chapters/05-discussion.tex
Normal file
9
paper/src/chapters/05-discussion.tex
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
\section{Discussion}
|
||||||
|
|
||||||
|
\subsection{Risk Assessment and Limitations}
|
||||||
|
|
||||||
|
Acknowledge risks and constraints and data sizes.
|
||||||
|
|
||||||
|
\subsection{Implications of Findings}
|
||||||
|
|
||||||
|
Interpretation of results and altenrative scenarios with broader market implications.
|
||||||
8
paper/src/chapters/06-conclusion.tex
Normal file
8
paper/src/chapters/06-conclusion.tex
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
\section{Conclusion}
|
||||||
|
|
||||||
|
\subsection{Summary of contributions }
|
||||||
|
Restate the thesis and key findings with validation of research objectives.
|
||||||
|
|
||||||
|
\subsection{Future Works and Next Steps}
|
||||||
|
|
||||||
|
Identify the research gaps here and potential business implications and setup of business + Proposed extensions and a long term agenda.
|
||||||
3
paper/src/chapters/acknowledgements.tex
Normal file
3
paper/src/chapters/acknowledgements.tex
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
\section{Acknowledgements}
|
||||||
|
|
||||||
|
Eugene Bykovets, PhD - ETH
|
||||||
@@ -35,6 +35,12 @@ The primary objective of this thesis is to develop and validate pricing heuristi
|
|||||||
\maketitle
|
\maketitle
|
||||||
|
|
||||||
\input{chapters/01-intro}
|
\input{chapters/01-intro}
|
||||||
|
\input{chapters/02-literature-review}
|
||||||
|
\input{chapters/03-methodology}
|
||||||
|
\input{chapters/04-results}
|
||||||
|
\input{chapters/05-discussion}
|
||||||
|
\input{chapters/06-conclusion}
|
||||||
|
|
||||||
|
|
||||||
\printbibliography
|
\printbibliography
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,12 @@
|
|||||||
\usepackage{csquotes}
|
\usepackage{csquotes}
|
||||||
\usepackage{subcaption}
|
\usepackage{subcaption}
|
||||||
\usepackage{siunitx}
|
\usepackage{siunitx}
|
||||||
|
\usepackage{tikz}
|
||||||
\usepackage{listings}
|
\usepackage{listings}
|
||||||
\usepackage{xcolor}
|
\usepackage{xcolor}
|
||||||
|
\usepackage[ruled,vlined]{algorithm2e}
|
||||||
|
|
||||||
|
\usetikzlibrary{positioning, shapes, arrows.meta, fit, backgrounds}
|
||||||
\lstset{
|
\lstset{
|
||||||
basicstyle=\ttfamily\footnotesize,
|
basicstyle=\ttfamily\footnotesize,
|
||||||
breaklines=true,
|
breaklines=true,
|
||||||
|
|||||||
@@ -1,36 +1,14 @@
|
|||||||
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
||||||
|
|
||||||
## Getting Started
|
# Phantom Air/Hotels
|
||||||
|
|
||||||
First, run the development server:
|
Design Discovery Documentation: https://github.com/velocitatem/PHANTOM/wiki/Design-Discovery
|
||||||
|
|
||||||
```bash
|
> This webapp serves two modes `{HOTEL,AIRLINE}` which are given by an env variable
|
||||||
npm run dev
|
|
||||||
# or
|
|
||||||
yarn dev
|
|
||||||
# or
|
|
||||||
pnpm dev
|
|
||||||
# or
|
|
||||||
bun dev
|
|
||||||
```
|
|
||||||
|
|
||||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
The webapp should serve under the / route the landing page which for both platforms is very similar. We define a set of components like Hero, Card, Button, Link ... This we can then pass to specific components each mode might demand that makes it behave differently, hotel cards showing hotel rooms from database and airline cards showing flights from database and each fetching prices from the pricing provider with a different HTTP parameter.
|
||||||
|
|
||||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
- globally we define a middleware.ts which is our switcher for modes.
|
||||||
|
- /app will have (airline) and (hotel) children which each have a layout.tsx and page.tsx where /app also has a parent layout defining layout.tsx and globals.css for any shared styling to avoid repretition.
|
||||||
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
- /components/ is gonna have ui/ which defines things like Button, Card, DatePicker with generic definitions and any tracking or observation code. We then define feats/airline/ and feats/hotel/ as children of components with specific components like AirlineHero and HotelCard.
|
||||||
|
- in /styles/ we define airline.css and hotel.css to tailor accents and styling for each.
|
||||||
## Learn More
|
|
||||||
|
|
||||||
To learn more about Next.js, take a look at the following resources:
|
|
||||||
|
|
||||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
|
||||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
|
||||||
|
|
||||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
|
||||||
|
|
||||||
## Deploy on Vercel
|
|
||||||
|
|
||||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
|
||||||
|
|
||||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
|
||||||
|
|||||||
@@ -3,6 +3,15 @@
|
|||||||
:root {
|
:root {
|
||||||
--background: #ffffff;
|
--background: #ffffff;
|
||||||
--foreground: #171717;
|
--foreground: #171717;
|
||||||
|
--bg-primary: #ffffff;
|
||||||
|
--bg-secondary: #f5f5f5;
|
||||||
|
--text-primary: #333333;
|
||||||
|
--text-secondary: #666666;
|
||||||
|
--spacing-sm: 8px;
|
||||||
|
--spacing-md: 16px;
|
||||||
|
--spacing-lg: 32px;
|
||||||
|
--border-radius: 8px;
|
||||||
|
--shadow-card: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@theme inline {
|
@theme inline {
|
||||||
@@ -19,8 +28,71 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
color: var(--foreground);
|
color: var(--foreground);
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text-primary);
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 { font-size: 2.5rem; }
|
||||||
|
h2 { font-size: 2rem; }
|
||||||
|
h3 { font-size: 1.5rem; }
|
||||||
|
|
||||||
|
button {
|
||||||
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
font-family: inherit;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
input, select, textarea {
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 1rem;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: var(--bg-primary);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
box-shadow: var(--shadow-card);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
padding: 12px 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1rem;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-spacing {
|
||||||
|
margin-bottom: var(--spacing-lg);
|
||||||
}
|
}
|
||||||
|
|||||||
302
web/src/styles/airline.css
Normal file
302
web/src/styles/airline.css
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
/* Airline Platform - Sky Blue Theme */
|
||||||
|
|
||||||
|
:root[data-mode="airline"] {
|
||||||
|
--accent-primary: #007aff;
|
||||||
|
--accent-secondary: #4caf50;
|
||||||
|
--accent-warning: #ff3b30;
|
||||||
|
--accent-primary-hover: #0051d5;
|
||||||
|
--accent-primary-light: #e6f2ff;
|
||||||
|
--text-accent: #007aff;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] {
|
||||||
|
--primary-color: var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .btn-primary {
|
||||||
|
background-color: var(--accent-primary);
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .btn-primary:hover {
|
||||||
|
background-color: var(--accent-primary-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .btn-secondary {
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--accent-primary);
|
||||||
|
border: 2px solid var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .btn-secondary:hover {
|
||||||
|
background-color: var(--accent-primary-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .badge-value {
|
||||||
|
background-color: var(--accent-secondary);
|
||||||
|
color: #ffffff;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .badge-warning {
|
||||||
|
background-color: var(--accent-warning);
|
||||||
|
color: #ffffff;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .search-form {
|
||||||
|
background: var(--bg-primary);
|
||||||
|
padding: var(--spacing-lg);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
box-shadow: var(--shadow-card);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .flight-card {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 2fr 3fr 2fr;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
background: var(--bg-primary);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
box-shadow: var(--shadow-card);
|
||||||
|
margin-bottom: var(--spacing-md);
|
||||||
|
transition: box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .flight-card:hover {
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 122, 255, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .flight-timing {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .flight-time {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .flight-airport {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .flight-route {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .flight-duration {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .flight-stops {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .flight-pricing {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .fare-option {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: var(--spacing-sm);
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .fare-option:hover {
|
||||||
|
border-color: var(--accent-primary);
|
||||||
|
background-color: var(--accent-primary-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .fare-class {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .fare-price {
|
||||||
|
font-size: 1.125rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .date-price-bar {
|
||||||
|
display: flex;
|
||||||
|
overflow-x: auto;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
padding: var(--spacing-md) 0;
|
||||||
|
margin-bottom: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .date-option {
|
||||||
|
min-width: 100px;
|
||||||
|
padding: var(--spacing-sm);
|
||||||
|
text-align: center;
|
||||||
|
border: 2px solid #e0e0e0;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .date-option:hover {
|
||||||
|
border-color: var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .date-option.active {
|
||||||
|
border-color: var(--accent-primary);
|
||||||
|
background-color: var(--accent-primary-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .date-label {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .date-price {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .progress-wizard {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: var(--spacing-lg) auto;
|
||||||
|
padding: 0 var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .wizard-step {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .wizard-step::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
left: 50%;
|
||||||
|
width: 100%;
|
||||||
|
height: 2px;
|
||||||
|
background: #e0e0e0;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .wizard-step:last-child::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .wizard-number {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #e0e0e0;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .wizard-step.active .wizard-number {
|
||||||
|
background: var(--accent-primary);
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .wizard-step.completed .wizard-number {
|
||||||
|
background: var(--accent-secondary);
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .wizard-label {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .wizard-step.active .wizard-label {
|
||||||
|
color: var(--accent-primary);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] a {
|
||||||
|
color: var(--accent-primary);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .input-field {
|
||||||
|
border: 2px solid #e0e0e0;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 12px;
|
||||||
|
transition: border-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .input-field:focus {
|
||||||
|
border-color: var(--accent-primary);
|
||||||
|
box-shadow: 0 0 0 3px var(--accent-primary-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .filter-sidebar {
|
||||||
|
background: var(--bg-primary);
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
box-shadow: var(--shadow-card);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .filter-section {
|
||||||
|
margin-bottom: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .filter-title {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin-bottom: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .checkbox-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
padding: var(--spacing-sm) 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="airline"] .checkbox-label:hover {
|
||||||
|
color: var(--accent-primary);
|
||||||
|
}
|
||||||
400
web/src/styles/hotel.css
Normal file
400
web/src/styles/hotel.css
Normal file
@@ -0,0 +1,400 @@
|
|||||||
|
/* Hotel Platform - Action Blue Theme */
|
||||||
|
|
||||||
|
:root[data-mode="hotel"] {
|
||||||
|
--accent-primary: #007aff;
|
||||||
|
--accent-secondary: #4caf50;
|
||||||
|
--accent-warning: #d9534f;
|
||||||
|
--accent-primary-hover: #0051d5;
|
||||||
|
--accent-primary-light: #e6f2ff;
|
||||||
|
--text-accent: #007aff;
|
||||||
|
--bg-tertiary: #f5f5f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] {
|
||||||
|
--primary-color: var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .btn-primary {
|
||||||
|
background-color: var(--accent-primary);
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .btn-primary:hover {
|
||||||
|
background-color: var(--accent-primary-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .btn-secondary {
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--accent-primary);
|
||||||
|
border: 2px solid var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .btn-secondary:hover {
|
||||||
|
background-color: var(--accent-primary-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .badge-value {
|
||||||
|
background-color: var(--accent-secondary);
|
||||||
|
color: #ffffff;
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .badge-warning {
|
||||||
|
background-color: var(--accent-warning);
|
||||||
|
color: #ffffff;
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .badge-rating {
|
||||||
|
background-color: var(--accent-primary);
|
||||||
|
color: #ffffff;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .search-form {
|
||||||
|
background: var(--bg-primary);
|
||||||
|
padding: var(--spacing-lg);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
box-shadow: var(--shadow-card);
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .hotel-card {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 300px 1fr auto;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
background: var(--bg-primary);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
box-shadow: var(--shadow-card);
|
||||||
|
margin-bottom: var(--spacing-md);
|
||||||
|
overflow: hidden;
|
||||||
|
transition: box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .hotel-card:hover {
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 122, 255, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .hotel-image {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 220px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .hotel-image img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .image-carousel {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .carousel-nav {
|
||||||
|
position: absolute;
|
||||||
|
bottom: var(--spacing-sm);
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .carousel-dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(255, 255, 255, 0.5);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .carousel-dot.active {
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .hotel-info {
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .hotel-name {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .hotel-location {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .hotel-rating {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .rating-text {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .hotel-features {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
margin-top: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .feature-tag {
|
||||||
|
padding: 4px 8px;
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .hotel-pricing {
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .price-wrapper {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .price-label {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .price-amount {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .price-unit {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .price-original {
|
||||||
|
text-decoration: line-through;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-right: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .urgency-message {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--accent-warning);
|
||||||
|
font-weight: 600;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .free-cancellation {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--accent-secondary);
|
||||||
|
font-weight: 600;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .filter-sidebar {
|
||||||
|
background: var(--bg-primary);
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
box-shadow: var(--shadow-card);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .filter-section {
|
||||||
|
margin-bottom: var(--spacing-lg);
|
||||||
|
padding-bottom: var(--spacing-md);
|
||||||
|
border-bottom: 1px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .filter-section:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .filter-title {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin-bottom: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .checkbox-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: var(--spacing-sm) 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .checkbox-label:hover {
|
||||||
|
color: var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .checkbox-count {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .price-slider {
|
||||||
|
margin-top: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .slider-track {
|
||||||
|
width: 100%;
|
||||||
|
height: 6px;
|
||||||
|
background: #e0e0e0;
|
||||||
|
border-radius: 3px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .slider-range {
|
||||||
|
height: 100%;
|
||||||
|
background: var(--accent-primary);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .slider-values {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: var(--spacing-sm);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .map-toggle {
|
||||||
|
background: var(--bg-primary);
|
||||||
|
border: 2px solid var(--accent-primary);
|
||||||
|
color: var(--accent-primary);
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .map-toggle:hover {
|
||||||
|
background: var(--accent-primary);
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .results-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: var(--spacing-lg);
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
background: var(--bg-primary);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .sort-dropdown {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 2px solid #e0e0e0;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .sort-dropdown:focus {
|
||||||
|
border-color: var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .view-toggle {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .view-button {
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: transparent;
|
||||||
|
border: 2px solid #e0e0e0;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .view-button.active {
|
||||||
|
background: var(--accent-primary);
|
||||||
|
color: #ffffff;
|
||||||
|
border-color: var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] a {
|
||||||
|
color: var(--accent-primary);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .input-field {
|
||||||
|
border: 2px solid #e0e0e0;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 12px;
|
||||||
|
width: 100%;
|
||||||
|
transition: border-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .input-field:focus {
|
||||||
|
border-color: var(--accent-primary);
|
||||||
|
box-shadow: 0 0 0 3px var(--accent-primary-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .tab-navigation {
|
||||||
|
display: flex;
|
||||||
|
gap: 0;
|
||||||
|
margin-bottom: var(--spacing-lg);
|
||||||
|
border-bottom: 2px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .tab-item {
|
||||||
|
padding: 12px 24px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 3px solid transparent;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .tab-item:hover {
|
||||||
|
color: var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mode="hotel"] .tab-item.active {
|
||||||
|
color: var(--accent-primary);
|
||||||
|
border-bottom-color: var(--accent-primary);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user